change
|
@ -160,37 +160,12 @@ cython_debug/
|
|||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||
#.idea/
|
||||
|
||||
# ---> Go
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
||||
# ---> Vue
|
||||
# gitignore template for Vue.js projects
|
||||
#
|
||||
# Recommended template: Node.gitignore
|
||||
|
||||
# TODO: where does this rule come from?
|
||||
docs/_book
|
||||
|
||||
# TODO: where does this rule come from?
|
||||
test/
|
||||
|
||||
./frontend/**node_modules**
|
||||
**/frontend/**static**
|
||||
**/old/**
|
||||
**/web-tom/**
|
||||
**/res/gen/**
|
||||
**/res/upload/**
|
||||
*/*.yaml
|
||||
*/*.spec
|
||||
*/*.log
|
68
README.md
|
@ -1,3 +1,67 @@
|
|||
# kmap
|
||||
# **知识图谱-后端**
|
||||
|
||||
基于ChatGPT + Vue 构建的知识图谱
|
||||
<div align="center">
|
||||
|
||||
**🌍基于知识图谱的软件功能点提取与分析**
|
||||
|
||||
🛠️ 简化数据获取流程(后端接口)
|
||||
|
||||
---
|
||||
|
||||
</div>
|
||||
|
||||
## 📑 功能特点
|
||||
|
||||
- 上传 软件文档 `type`: `[ docx | doc | txt ]`
|
||||
- 对接 `LLM` 并采用 `prompt` 进行微调
|
||||
|
||||
---
|
||||
|
||||
- 提供简单的 FastAPI 接口
|
||||
- 用户操作: `/api/v1/user`
|
||||
- 用户登录
|
||||
- `POST`
|
||||
- 用户注册
|
||||
- `PUT`
|
||||
- 更新用户
|
||||
- `/api/v1/user/update`
|
||||
- `POST`
|
||||
- 删除用户
|
||||
- `DELETE`
|
||||
|
||||
## 📦 安装
|
||||
|
||||
首先,确保您的 `Python` 版本为 **3.9** 或更高。然后,执行以下步骤来安装项目:
|
||||
|
||||
**通用安装:**
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
1. 填写配置文件 `res/config.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"OPEN_KEY": ""
|
||||
}
|
||||
```
|
||||
|
||||
2. 启动应用程序 `main.py`
|
||||
|
||||
```bash
|
||||
cd KnowledgeMap
|
||||
python main.py
|
||||
```
|
||||
|
||||
3. 访问项目文档:
|
||||
|
||||
- 打开浏览器访问 <http://localhost:8000/docs> 查看接口文档
|
||||
|
||||
- 访问 `http://host:port` 查看前端显示
|
||||
|
||||
## 🔗 示例请求
|
||||
|
||||
暂时还没写完
|
|
@ -0,0 +1,83 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@Desc : 基础路由📦
|
||||
"""
|
||||
import aiohttp
|
||||
from fastapi import FastAPI, HTTPException, Request
|
||||
from fastapi.responses import HTMLResponse
|
||||
|
||||
from api.api_auth import router as auth_router
|
||||
from api.api_upload import Config, router as upload_router
|
||||
|
||||
# Mount Path
|
||||
STATIC = Config.get_path("frontend", "static")
|
||||
|
||||
# Router 处理
|
||||
Base_api = FastAPI(redoc_url=None, title="知识图谱分析(后端)", version="2.0")
|
||||
Base_api.include_router(auth_router)
|
||||
Base_api.include_router(upload_router)
|
||||
|
||||
|
||||
@Base_api.get("/ping", tags=["测试"])
|
||||
async def ping(request: Request):
|
||||
"""
|
||||
### 测试程序是否正常运行
|
||||
|
||||
用于测试程序是否正常运行的简单路由。
|
||||
|
||||
**返回**:
|
||||
- `dict`: 包含应答信息的字典,格式示例:
|
||||
```json
|
||||
{
|
||||
"ping": "pong",
|
||||
"client_ip": "客户端的IP地址"
|
||||
}
|
||||
```
|
||||
"""
|
||||
return {
|
||||
"ping": "pong",
|
||||
"client_ip": request.client.host,
|
||||
}
|
||||
|
||||
|
||||
async def fetch_google_with_proxy(proxy_url: str):
|
||||
url = "https://www.google.com"
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url, proxy=proxy_url) as response:
|
||||
return await response.text()
|
||||
|
||||
|
||||
@Base_api.get("/google", tags=["测试"])
|
||||
async def access_google_with_proxy(proxy_url: str):
|
||||
"""
|
||||
测试访问谷歌, 验证代理连通性
|
||||
Args:
|
||||
`proxy_url`: 代理地址
|
||||
|
||||
Returns:
|
||||
状态
|
||||
"""
|
||||
try:
|
||||
result = await fetch_google_with_proxy(proxy_url)
|
||||
return {"status": "success", "content": result}
|
||||
except Exception as e:
|
||||
return {"status": "error", "error_message": str(e)}
|
||||
|
||||
|
||||
@Base_api.get("/home", response_class=HTMLResponse)
|
||||
async def read_home():
|
||||
try:
|
||||
index_path = str(STATIC.joinpath("index.html"))
|
||||
|
||||
# 读取index.html文件内容
|
||||
with open(index_path, "r", encoding="utf-8") as file:
|
||||
content = file.read()
|
||||
return HTMLResponse(content=content)
|
||||
except FileNotFoundError:
|
||||
# 如果文件不存在,抛出HTTP异常
|
||||
raise HTTPException(status_code=404, detail="Index.html not found")
|
||||
|
||||
|
||||
@Base_api.get("/", tags=["测试"])
|
||||
async def read_root():
|
||||
return {"message": "Hello, World!"}
|
|
@ -0,0 +1,107 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@Desc : 登录鉴权
|
||||
"""
|
||||
from fastapi import APIRouter, Form
|
||||
|
||||
from utils.auth import AuthParams
|
||||
from utils.config import Config, hash_password
|
||||
|
||||
router = APIRouter(prefix="/api/v1")
|
||||
auth_params = AuthParams(Config.get_select_config("mysql"))
|
||||
|
||||
|
||||
@router.put("/user", tags=["用户信息"])
|
||||
async def add_user(username: str = Form(...), password: str = Form(...)):
|
||||
"""
|
||||
**添加用户路由**
|
||||
|
||||
Args:
|
||||
- `params` (Form): 表单
|
||||
- `username`: 用户名
|
||||
- `password`: 密码
|
||||
|
||||
Returns:
|
||||
- `json`: 包含添加操作的结果和用户信息的字典
|
||||
"""
|
||||
# 查询用户是否存在
|
||||
query_result = auth_params.query(username)
|
||||
if query_result['msg'] == auth_params.Query.notfound:
|
||||
hashed_password = hash_password(password)
|
||||
return auth_params.insert(username, hashed_password)
|
||||
|
||||
return query_result
|
||||
|
||||
|
||||
@router.post("/user", tags=["用户信息"])
|
||||
async def user_login(username: str = Form(...), password: str = Form(...)):
|
||||
"""
|
||||
**用户登录路由**
|
||||
|
||||
Args:
|
||||
- `params` (Form): 表单
|
||||
- `username`: 用户名
|
||||
- `password`: 密码
|
||||
|
||||
Returns:
|
||||
- `json`: 包含用户登录操作的结果和用户信息的字典
|
||||
"""
|
||||
|
||||
# 用户不存在
|
||||
query_result = auth_params.query(username)
|
||||
if query_result['msg'] == auth_params.Query.notfound:
|
||||
return query_result
|
||||
|
||||
# 检查密码是否匹配
|
||||
stored_password = query_result["msg"][2]
|
||||
if hash_password(password) == stored_password:
|
||||
auth_params.base_msg["msg"] = "登录成功"
|
||||
else:
|
||||
auth_params.base_msg["msg"] = "登录失败"
|
||||
|
||||
return auth_params.base_msg
|
||||
|
||||
|
||||
@router.get("/user/{username}", tags=["用户信息"])
|
||||
async def get_user(username: str):
|
||||
"""
|
||||
**查询用户路由**
|
||||
|
||||
Args:
|
||||
- `params` username: 要查询的用户的用户名
|
||||
|
||||
Returns:
|
||||
- `json`: 包含查询操作的结果和用户信息的字典
|
||||
"""
|
||||
return auth_params.query(username)
|
||||
|
||||
|
||||
@router.post("/user/update", tags=["用户信息"])
|
||||
async def update_user(username: str = Form(...), password: str = Form(...)):
|
||||
"""
|
||||
**更新用户数据**
|
||||
|
||||
Args:
|
||||
- `params` (Form): 表单
|
||||
- `username`: 用户名
|
||||
- `password`: 密码
|
||||
|
||||
Returns:
|
||||
- `json`: 包含查询操作的结果和用户信息的字典
|
||||
"""
|
||||
return auth_params.update(username, hash_password(password))
|
||||
|
||||
|
||||
@router.delete("/user/{username}", tags=["用户信息"])
|
||||
async def delete_user(username: str):
|
||||
"""
|
||||
删除用户路由
|
||||
|
||||
Args:
|
||||
- `username` (str): 要删除的用户的用户名
|
||||
|
||||
Returns:
|
||||
- `json`: 包含删除操作的结果的字典
|
||||
"""
|
||||
# 查询用户是否存在
|
||||
return auth_params.delete(username)
|
|
@ -13,10 +13,9 @@ import openai
|
|||
from openai import OpenAI
|
||||
|
||||
from utils.config import Config, log
|
||||
from utils.model.data import ChatModel
|
||||
|
||||
|
||||
class BaseClientModel:
|
||||
class BaseClientModel_:
|
||||
|
||||
def __init__(self, chat_model: ChatModel):
|
||||
self.client: OpenAI = OpenAI(
|
||||
|
|
|
@ -0,0 +1,118 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# @File : api_upload.py
|
||||
# @Description : 文件上传 📦
|
||||
|
||||
|
||||
import tiktoken
|
||||
from fastapi import APIRouter, File, UploadFile
|
||||
from typing_extensions import Optional, Union
|
||||
|
||||
from utils.agents.executor import SummarizeRobot
|
||||
from utils.common import BaseClient
|
||||
from utils.config import Config, log
|
||||
from utils.model.adapter import Doc, DocumentAdapter, Docx, Txt
|
||||
from utils.model.agent import ChatModel, TaskModel
|
||||
from utils.model.error import UploadAPIError
|
||||
|
||||
router = APIRouter(prefix="/api/v1")
|
||||
|
||||
|
||||
def parser_upload_type(file: UploadFile) -> Union[str, dict]:
|
||||
"""解析上传文件
|
||||
|
||||
参数:
|
||||
- `file`: 上传文件
|
||||
|
||||
返回:
|
||||
- `str | None`: 结果
|
||||
"""
|
||||
document: Optional[DocumentAdapter]
|
||||
|
||||
if (file_type := file.filename.split('.')[-1]) not in ["docx", "doc", "txt"]:
|
||||
return UploadAPIError.type_upload_error
|
||||
|
||||
try:
|
||||
if file_type == "docx":
|
||||
document = DocumentAdapter(Docx())
|
||||
elif file_type == "doc":
|
||||
document = DocumentAdapter(Doc())
|
||||
else:
|
||||
document = DocumentAdapter(Txt())
|
||||
except (IOError, Exception):
|
||||
return UploadAPIError.parser_upload_error
|
||||
else:
|
||||
return document.parser(file)
|
||||
|
||||
|
||||
def statistical_token_len(text: str, size: int = 2000) -> Union[int, dict]:
|
||||
"""统计查询一次需要的 token 数量
|
||||
|
||||
参数:
|
||||
- `text`: 文本信息
|
||||
|
||||
返回:
|
||||
- `int`: token 数量
|
||||
"""
|
||||
num_tokens: int = 0
|
||||
|
||||
try:
|
||||
encoding = tiktoken.encoding_for_model("gpt-3.5-turbo")
|
||||
num_tokens = len(encoding.encode(text))
|
||||
except (Exception, RuntimeError):
|
||||
log.warn(f"统计 Tokens 错误: {num_tokens}")
|
||||
return num_tokens
|
||||
|
||||
if num_tokens <= size:
|
||||
log.info(f"预计消耗 Tokens: {num_tokens}")
|
||||
return num_tokens
|
||||
|
||||
return UploadAPIError.size_upload_error
|
||||
|
||||
|
||||
@router.post("/upload", tags=["文件上传"])
|
||||
async def upload_file(file: UploadFile = File(...)):
|
||||
"""文档上传
|
||||
|
||||
参数:
|
||||
- `file`: 文件(支持格式为: docx | doc | txt)
|
||||
|
||||
返回:
|
||||
- `json`: 解析参数
|
||||
"""
|
||||
|
||||
if file.filename is None: # 文件不存在
|
||||
return UploadAPIError.empty_upload_error
|
||||
|
||||
if isinstance((content := parser_upload_type(file)), dict):
|
||||
return content # 格式错误
|
||||
|
||||
if isinstance((tokens := statistical_token_len(content)), dict):
|
||||
return tokens # 超出大小
|
||||
|
||||
# TODO: 采用插件系统 | 代理模式 | 装饰器
|
||||
chat: BaseClient = BaseClient(
|
||||
ChatModel(**Config.get_select_config("chat"))
|
||||
)
|
||||
|
||||
robot = SummarizeRobot()
|
||||
|
||||
task = TaskModel(
|
||||
agent=robot.robot(chat),
|
||||
description=content,
|
||||
)
|
||||
|
||||
reply = await task.execute()
|
||||
|
||||
log.info(reply)
|
||||
|
||||
ddc = robot.complete_data(reply[task.agent.role]['result'])
|
||||
|
||||
log.info(ddc)
|
||||
|
||||
return ddc
|
||||
#
|
||||
# reply = await chat.send_to_chatgpt(content)
|
||||
# log.info(
|
||||
# reply
|
||||
# )
|
||||
# return reply
|
|
@ -0,0 +1,23 @@
|
|||
.DS_Store
|
||||
node_modules
|
||||
/dist
|
||||
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
|
||||
# Log files
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
.vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
|
@ -0,0 +1,24 @@
|
|||
# bs
|
||||
|
||||
## Project setup
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
### Compiles and hot-reloads for development
|
||||
```
|
||||
npm run serve
|
||||
```
|
||||
|
||||
### Compiles and minifies for production
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
### Lints and fixes files
|
||||
```
|
||||
npm run lint
|
||||
```
|
||||
|
||||
### Customize configuration
|
||||
See [Configuration Reference](https://cli.vuejs.org/config/).
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@vue/cli-plugin-babel/preset'
|
||||
]
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "esnext",
|
||||
"baseUrl": "./",
|
||||
"moduleResolution": "node",
|
||||
"paths": {
|
||||
"@/*": [
|
||||
"src/*"
|
||||
]
|
||||
},
|
||||
"lib": [
|
||||
"esnext",
|
||||
"dom",
|
||||
"dom.iterable",
|
||||
"scripthost"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"name": "bs",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"serve": "vue-cli-service serve",
|
||||
"build": "vue-cli-service build --dest ./static"
|
||||
},
|
||||
|
||||
"dependencies": {
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "^1.0.2",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^1.4.0",
|
||||
"core-js": "^3.8.3",
|
||||
"echarts": "^5.4.3",
|
||||
"element-ui": "^2.15.13",
|
||||
"moment": "^2.29.4",
|
||||
"three": "^0.157.0",
|
||||
"vue": "^2.6.14",
|
||||
"vue-router": "^3.5.1",
|
||||
"vuex": "^3.6.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@vue/cli-plugin-babel": "~5.0.0",
|
||||
"@vue/cli-plugin-router": "~5.0.0",
|
||||
"@vue/cli-plugin-vuex": "~5.0.0",
|
||||
"@vue/cli-service": "~5.0.0",
|
||||
"css-loader": "^6.7.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"less": "^4.1.3",
|
||||
"less-loader": "^11.1.0",
|
||||
"sass": "^1.32.7",
|
||||
"sass-loader": "^12.6.0",
|
||||
"style-loader": "^3.3.2",
|
||||
"vue-template-compiler": "^2.6.14"
|
||||
},
|
||||
"browserslist": [
|
||||
"> 1%",
|
||||
"last 2 versions",
|
||||
"not dead"
|
||||
]
|
||||
}
|
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 4.2 KiB |
|
@ -0,0 +1,20 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
||||
<title>基于软件功能点提示知识图谱网站</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled.
|
||||
Please enable it to continue.</strong>
|
||||
</noscript>
|
||||
<div id="app"></div>
|
||||
<!-- built files will be auto injected -->
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,24 @@
|
|||
<template>
|
||||
<div>
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Header from '../src/components/Header/Header.vue'
|
||||
import SideMenu from '../src/views/SideMenu/SideMenu.vue'
|
||||
export default {
|
||||
components: { Header, SideMenu },
|
||||
name: 'App',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
</style>
|
After Width: | Height: | Size: 4.2 KiB |
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 355 KiB |
After Width: | Height: | Size: 17 KiB |
After Width: | Height: | Size: 6.7 KiB |
|
@ -0,0 +1,29 @@
|
|||
<template>
|
||||
<div>
|
||||
1
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from '@/router'
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
|
||||
created(){
|
||||
|
||||
},
|
||||
mounted() {
|
||||
|
||||
},
|
||||
methods: {
|
||||
|
||||
},
|
||||
}
|
||||
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-menu
|
||||
:default-active="$route.path"
|
||||
class="el-menu-demo"
|
||||
mode="horizontal"
|
||||
@select="handleSelect"
|
||||
background-color="#545c64"
|
||||
:router="true"
|
||||
text-color="#fff"
|
||||
active-text-color="white">
|
||||
<el-menu-item index="/home" style="margin-left: 50px"
|
||||
>图谱分析</el-menu-item
|
||||
>
|
||||
<div style="display: flex; padding-left: 100px">
|
||||
<el-menu-item index="/Login" style="margin-left: 600px"
|
||||
>退出登录</el-menu-item
|
||||
>
|
||||
</div>
|
||||
</el-menu>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from '@/router'
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
activeIndex: '1',
|
||||
options: [],
|
||||
value: '',
|
||||
}
|
||||
},
|
||||
|
||||
methods: {
|
||||
handleSelect(key, keyPath) {
|
||||
console.log(key, keyPath)
|
||||
},
|
||||
change(e) {
|
||||
console.log(e)
|
||||
this.$router.push({ name: 'Content', query: { id: e } })
|
||||
this.$router.go(0)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style scoped lang="less"></style>
|
|
@ -0,0 +1,13 @@
|
|||
import Vue from 'vue'
|
||||
import App from './App.vue'
|
||||
import ElementUI from 'element-ui'
|
||||
import router from './router/index'
|
||||
import 'element-ui/lib/theme-chalk/index.css';
|
||||
import axios from 'axios';
|
||||
Vue.config.productionTip = false
|
||||
Vue.use(ElementUI)
|
||||
Vue.prototype.$axios=axios;
|
||||
new Vue({
|
||||
router,
|
||||
render: h => h(App),
|
||||
}).$mount('#app')
|
|
@ -0,0 +1,54 @@
|
|||
import Vue from "vue";
|
||||
import Router from 'vue-router'
|
||||
import Login from '../views/Login/Login.vue'
|
||||
import Home from '../views/home/home.vue'
|
||||
import Header from '../components/Header/Header.vue'
|
||||
import Body from '../components/Body/Body.vue'
|
||||
import Range from '../views/Range/Range.vue'
|
||||
import Content_1 from '../views/Content_1/Content_1.vue'
|
||||
import Register from '../views/Register/Register.vue'
|
||||
import NetworkGraph from '../views/NetworkGraph/NetworkGraph.vue'
|
||||
|
||||
Vue.use(Router)
|
||||
|
||||
export default new Router({
|
||||
mode: 'hash',
|
||||
routes: [{
|
||||
path: '/',
|
||||
name: 'Login',
|
||||
component: Login
|
||||
},{
|
||||
path: '/NetworkGraph',
|
||||
name: 'NetworkGraph',
|
||||
component: NetworkGraph
|
||||
},{
|
||||
path: '/Home',
|
||||
name: 'Home',
|
||||
component: Home
|
||||
},{
|
||||
path: '/Login',
|
||||
name: 'Login',
|
||||
component: Login
|
||||
},{
|
||||
path: '/Body',
|
||||
name: 'Body',
|
||||
component: Body
|
||||
},{
|
||||
path: '/Range',
|
||||
name: 'Range',
|
||||
component: Range
|
||||
},{
|
||||
path: '/Register',
|
||||
name: 'Register',
|
||||
component: Register
|
||||
},{
|
||||
path: '/Content_1',
|
||||
name: 'Content_1',
|
||||
component: Content_1
|
||||
},{
|
||||
path: '/Header',
|
||||
name: 'Header',
|
||||
component: Header
|
||||
}]
|
||||
})
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import Range from '../views/Range/Range.vue'
|
||||
import Content_1 from '../views/Content_1/Content_1.vue'
|
||||
|
||||
const routes = [
|
||||
// 其他路由规则...
|
||||
{ path: '/Range', component: Range },
|
||||
{ path: '/Content_1', component: Content_1 },
|
||||
]
|
||||
|
||||
const router = new VueRouter({
|
||||
routes,
|
||||
})
|
|
@ -0,0 +1,218 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-container>
|
||||
<el-aside width="200px"><SideMenu /></el-aside>
|
||||
|
||||
<el-container>
|
||||
<el-header>
|
||||
<el-menu
|
||||
:default-active="$route.path"
|
||||
class="el-menu-demo"
|
||||
mode="horizontal"
|
||||
@select="handleSelect"
|
||||
background-color="#545c64"
|
||||
:router="true"
|
||||
text-color="#fff"
|
||||
active-text-color="white">
|
||||
<el-menu-item index="/home" style="margin-left: 50px"
|
||||
>图谱分析</el-menu-item
|
||||
>
|
||||
<div style="display: flex; padding-left: 100px">
|
||||
<el-menu-item index="/Login" style="margin-left: 600px"
|
||||
>退出登录</el-menu-item
|
||||
>
|
||||
</div>
|
||||
</el-menu></el-header
|
||||
>
|
||||
<el-main style="height: 100vh">
|
||||
<el-tag type="warning" style="margin-left: -1260px; margin-top: 20px">
|
||||
文档介绍
|
||||
</el-tag>
|
||||
<el-card class="box-card">
|
||||
<div slot="header" class="clearfix" v-if="a != ''">
|
||||
{{ this.a }}
|
||||
</div>
|
||||
<div slot="header" class="clearfix" v-else>请分析文档后再查看</div>
|
||||
</el-card>
|
||||
<el-tag type="" style="margin-left: -1260px; margin-top: 30px">
|
||||
内容分析列
|
||||
</el-tag>
|
||||
<el-collapse v-model="activeNames" @change="handleChange">
|
||||
<el-collapse-item
|
||||
title="通过知识图谱分析,您的文档设计为您提供系统总结:"
|
||||
name="1">
|
||||
<div style="font-weight: 600" v-if="sum != ''">
|
||||
{{ sum }}
|
||||
</div>
|
||||
<div style="font-weight: 600" v-else>请分析文档后再查看</div>
|
||||
</el-collapse-item>
|
||||
<el-collapse-item
|
||||
title="通过知识图谱分析,您的文档设计有部分不足:"
|
||||
name="2">
|
||||
<div style="font-weight: 600" v-if="sums != ''">
|
||||
{{ sums }}
|
||||
</div>
|
||||
<div style="font-weight: 600" v-else>请分析文档后再查看</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
<el-tag type="danger" style="margin-left: -1260px; margin-top: 20px">
|
||||
表格分析栏
|
||||
</el-tag>
|
||||
<el-table :data="tableData" border style="width: 100%">
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="功能"
|
||||
width="200"></el-table-column>
|
||||
<el-table-column label="功能内容" width="700">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.relation.relation">
|
||||
{{ scope.row.relation.relation }}
|
||||
</span>
|
||||
<span v-else>系统暂时无法展示功能内容</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-tag type="warning" style="margin-left: -1260px; margin-top: 20px">
|
||||
文档图谱分析
|
||||
</el-tag>
|
||||
<Net />
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
|
||||
<!-- <Net /> -->
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Header from '../../components/Header/Header.vue'
|
||||
import SideMenu from '../SideMenu/SideMenu.vue'
|
||||
import Net from '../NetworkGraph/NetworkGraph.vue'
|
||||
export default {
|
||||
components: { Header, SideMenu, Net },
|
||||
data() {
|
||||
return {
|
||||
tableData: [],
|
||||
sum: '',
|
||||
sums: '',
|
||||
a: '',
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
op: {},
|
||||
},
|
||||
mounted() {
|
||||
const list = JSON.parse(localStorage.getItem('data')).origin.function
|
||||
// const dataArray = Object.entries(list)
|
||||
this.tableData = Object.entries(list).map(([name, relation]) => ({
|
||||
name,
|
||||
relation,
|
||||
}))
|
||||
this.sum = JSON.parse(localStorage.getItem('data')).origin.summarize
|
||||
console.log(this.sum)
|
||||
this.sums = JSON.parse(localStorage.getItem('data')).origin.new_summarize
|
||||
this.a = JSON.parse(localStorage.getItem('data')).origin.summarize
|
||||
},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
Page {
|
||||
color: white;
|
||||
height: 100vh;
|
||||
}
|
||||
::v-deep.el-header {
|
||||
padding: 0;
|
||||
}
|
||||
::v-deep.el-main[data-v-1b439646] {
|
||||
line-height: 30px;
|
||||
}
|
||||
::v-deep.el-table[data-v-1b439646] {
|
||||
margin-top: 0px;
|
||||
}
|
||||
::v-deep.summary-section .summary[data-v-1b439646] {
|
||||
height: 140px;
|
||||
}
|
||||
::v-deep.el-main {
|
||||
padding: 0;
|
||||
margin-left: -1px;
|
||||
}
|
||||
.el-header,
|
||||
.el-footer {
|
||||
background-color: #b3c0d1;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
line-height: 60px;
|
||||
}
|
||||
|
||||
.el-aside {
|
||||
background-color: #d3dce6;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
line-height: 200px;
|
||||
}
|
||||
|
||||
.el-main {
|
||||
background-color: #e9eef3;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
line-height: 160px;
|
||||
}
|
||||
|
||||
body > .el-container {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.el-container:nth-child(5) .el-aside,
|
||||
.el-container:nth-child(6) .el-aside {
|
||||
line-height: 260px;
|
||||
}
|
||||
|
||||
.el-container:nth-child(7) .el-aside {
|
||||
line-height: 320px;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
// Main dashboard container
|
||||
.dashboard {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
// Page header styling
|
||||
.page-header {
|
||||
font-weight: 300;
|
||||
font-size: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
// Table styling
|
||||
.el-table {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
// Summary section styling
|
||||
.summary-section {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-top: 20px;
|
||||
|
||||
// Each summary block styling
|
||||
.summary {
|
||||
margin-left: 200px;
|
||||
height: 400px;
|
||||
font-weight: 600;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
// Summary value styling
|
||||
.summary-value {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,135 @@
|
|||
<template>
|
||||
<div class="login_container">
|
||||
<el-row>
|
||||
<el-col :span="22" :xs="0"></el-col>
|
||||
</el-row>
|
||||
<el-col :span="12" :xs="24">
|
||||
<el-form class="login_form" ref="form" :model="form" :rules="rules">
|
||||
<h1>Hello</h1>
|
||||
<h2>欢迎登录知识图谱分析数据网站</h2>
|
||||
<el-form-item prop="name">
|
||||
<el-input v-model="form.name" prefix-icon="el-icon-user"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
prefix-icon="el-icon-lock"
|
||||
type="password"
|
||||
show-password></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
class="login_button"
|
||||
type="primary"
|
||||
size="default"
|
||||
@click="login(form)">
|
||||
登录
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
class="login_button"
|
||||
type="primary"
|
||||
size="default"
|
||||
@click="login_1()">
|
||||
注册
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import router from '@/router'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
name: '',
|
||||
password: '',
|
||||
},
|
||||
rules: {
|
||||
name: [{ required: true, message: '请输入账号', trigger: 'blur' }],
|
||||
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
login_1() {
|
||||
router.push('/Register')
|
||||
},
|
||||
login(form) {
|
||||
if (this.form.password === '' || this.form.name === '') {
|
||||
this.info()
|
||||
} else {
|
||||
const formData = new FormData()
|
||||
formData.append('username', this.form.name)
|
||||
formData.append('password', this.form.password)
|
||||
this.$axios.post('/api/v1/user', formData).then((res) => {
|
||||
if (res.data.msg === '登录失败') {
|
||||
this.warning()
|
||||
} else {
|
||||
this.success()
|
||||
localStorage.setItem('user', this.form.name)
|
||||
router.push('/Range')
|
||||
}
|
||||
console.log(res)
|
||||
})
|
||||
}
|
||||
},
|
||||
warning() {
|
||||
this.$message({
|
||||
message: '输入的密码或者账号错误',
|
||||
type: 'warning',
|
||||
})
|
||||
},
|
||||
info() {
|
||||
this.$message({
|
||||
message: '请输入密码或者账号',
|
||||
})
|
||||
},
|
||||
success() {
|
||||
this.$message({
|
||||
message: '登录成功!欢迎回来',
|
||||
type: 'success',
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.login_container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: url('https://pic1.zhimg.com/v2-66f210d04b273c6616f00bece467223a_1440w.jpg?source=172ae18b')
|
||||
no-repeat;
|
||||
background-size: cover;
|
||||
.login_form {
|
||||
animation-name: bounceIn;
|
||||
animation-duration: 3s;
|
||||
background-color: darkgrey;
|
||||
border-radius: 2%;
|
||||
opacity: 0.9;
|
||||
position: relative;
|
||||
width: 80%;
|
||||
top: 30vh;
|
||||
background-size: cover;
|
||||
padding: 10px 10px 40px 10px;
|
||||
margin-top: -100px;
|
||||
margin-left: 800px;
|
||||
h1 {
|
||||
color: white;
|
||||
font-size: 40px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 20px;
|
||||
color: white;
|
||||
margin: 20px 0px;
|
||||
}
|
||||
.login_button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,152 @@
|
|||
<template>
|
||||
<div>
|
||||
<div
|
||||
class="graph-container"
|
||||
ref="ecGraph"
|
||||
style="width: 100%; height: 70vh; margin-top: 10px"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import * as echarts from 'echarts'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
ecGraph: null,
|
||||
a: '',
|
||||
tableData: '',
|
||||
s: '',
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.initGraph()
|
||||
},
|
||||
created() {
|
||||
this.tableData = JSON.parse(localStorage.getItem('data')).origin.function
|
||||
const good = {
|
||||
name: '功能点',
|
||||
itemStyle: {
|
||||
color: 'rgb(140,69,40)',
|
||||
},
|
||||
}
|
||||
const array = JSON.parse(localStorage.getItem('data')).clean[0]
|
||||
array.push(good)
|
||||
this.s = array
|
||||
console.log(this.s)
|
||||
},
|
||||
methods: {
|
||||
initGraph() {
|
||||
this.ecGraph = echarts.init(this.$refs.ecGraph)
|
||||
const graphOption = {
|
||||
series: [
|
||||
{
|
||||
type: 'graph',
|
||||
layout: 'force',
|
||||
symbolSize: 70,
|
||||
animation: true,
|
||||
roam: true,
|
||||
label: {
|
||||
show: true,
|
||||
},
|
||||
edgeSymbol: ['none', 'arrow'],
|
||||
edgeSymbolSize: 10,
|
||||
force: {
|
||||
initLayout: 'circular',
|
||||
repulsion: 400,
|
||||
gravity: 0.1,
|
||||
edgeLength: 200,
|
||||
},
|
||||
data: this.s,
|
||||
links: [
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 0,
|
||||
},
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 1,
|
||||
},
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 2,
|
||||
},
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 3,
|
||||
},
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 4,
|
||||
},
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 5,
|
||||
},
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 6,
|
||||
},
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 7,
|
||||
},
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 8,
|
||||
},
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 9,
|
||||
},
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 10,
|
||||
},
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 11,
|
||||
},
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 12,
|
||||
},
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 13,
|
||||
},
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 14,
|
||||
},
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 15,
|
||||
},
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 16,
|
||||
},
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 17,
|
||||
},
|
||||
{
|
||||
source: this.s.length - 1,
|
||||
target: 18,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
this.ecGraph.setOption(graphOption)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.graph-container {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,213 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-container>
|
||||
<el-aside width="200px"><SideMenu /></el-aside>
|
||||
<el-container>
|
||||
<el-header>
|
||||
<el-menu
|
||||
:default-active="$route.path"
|
||||
class="el-menu-demo"
|
||||
mode="horizontal"
|
||||
@select="handleSelect"
|
||||
background-color="#545c64"
|
||||
:router="true"
|
||||
text-color="#fff"
|
||||
active-text-color="white"
|
||||
>
|
||||
<el-menu-item index="/home" style="margin-left: 50px"
|
||||
>图谱分析</el-menu-item
|
||||
>
|
||||
<div style="display: flex; padding-left: 100px">
|
||||
<el-menu-item index="/Login" style="margin-left: 600px"
|
||||
>退出登录</el-menu-item
|
||||
>
|
||||
</div>
|
||||
</el-menu></el-header
|
||||
>
|
||||
<el-main>
|
||||
<div>
|
||||
<el-carousel indicator-position="outside" height="400px">
|
||||
<el-carousel-item v-for="item in list" :key="item">
|
||||
<img :src="item.img" style="width: 100%; height: 400px" />
|
||||
</el-carousel-item>
|
||||
</el-carousel>
|
||||
</div>
|
||||
<div>
|
||||
<div style="font-weight: 700; font-size: 20px">
|
||||
为了帮助用户更好的分析文档,本网站设有知识图谱功能,只需要点击下方上传文件即可
|
||||
</div>
|
||||
<template>
|
||||
<div style="height: 100vh">
|
||||
<!-- 拖拽上传 -->
|
||||
<el-upload
|
||||
class="upload-demo"
|
||||
drag
|
||||
action="/api/v1/upload"
|
||||
:before-upload="beforeUpload"
|
||||
:on-success="handleSuccess"
|
||||
:on-error="handleError"
|
||||
limit="1"
|
||||
multiple
|
||||
>
|
||||
<i class="el-icon-upload"></i>
|
||||
<div class="el-upload__text">
|
||||
将文件拖到此处,或<em>点击上传</em>
|
||||
</div>
|
||||
<div class="el-upload__tip" slot="tip">
|
||||
<strong>只能上传 doc | docx | txt 文件</strong>
|
||||
</div>
|
||||
</el-upload>
|
||||
<!--
|
||||
<el-upload
|
||||
action="/api/v1/upload"
|
||||
:before-upload="beforeUpload"
|
||||
:on-success="handleSuccess"
|
||||
:on-error="handleError"
|
||||
>
|
||||
<el-button
|
||||
type="primary"
|
||||
v-loading.fullscreen.lock="fullscreenLoading"
|
||||
@click="openFullScreen1"
|
||||
>点击分析文档上传</el-button
|
||||
>
|
||||
</el-upload> -->
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</el-main>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import router from '@/router'
|
||||
import Header from '../../components/Header/Header.vue'
|
||||
import SideMenu from '../SideMenu/SideMenu.vue'
|
||||
export default {
|
||||
components: { Header, SideMenu },
|
||||
data() {
|
||||
return {
|
||||
fileList: [],
|
||||
fullscreenLoading: false,
|
||||
list: [
|
||||
{
|
||||
img: 'https://img0.baidu.com/it/u=2370989968,2150449142&fm=253&fmt=auto&app=138&f=JPEG?w=939&h=500',
|
||||
},
|
||||
{
|
||||
img: 'https://img1.baidu.com/it/u=1563923172,2779050201&fm=253&fmt=auto&app=120&f=JPEG?w=640&h=360',
|
||||
},
|
||||
{
|
||||
img: 'https://img2.baidu.com/it/u=653212776,213855603&fm=253&fmt=auto&app=138&f=JPEG?w=830&h=500',
|
||||
},
|
||||
],
|
||||
}
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
beforeUpload(file) {
|
||||
// 文件上传前的验证处理,如文件类型、大小等
|
||||
console.log(file)
|
||||
},
|
||||
handleSuccess(response, file) {
|
||||
// 文件上传成功的处理
|
||||
if (response.clean !== '') {
|
||||
this.fullscreenLoading = false
|
||||
console.log(response)
|
||||
if (response.err === '文件格式错误') {
|
||||
this.warning()
|
||||
} else if (response.err === '提取失败, 请查看日志!') {
|
||||
this.warning_1()
|
||||
} else {
|
||||
this.fullscreenLoading = false
|
||||
localStorage.setItem('data', JSON.stringify(response))
|
||||
router.push('/Content_1')
|
||||
}
|
||||
}
|
||||
},
|
||||
handleError(error, file) {
|
||||
// 文件上传失败的处理
|
||||
console.error(error)
|
||||
},
|
||||
warning() {
|
||||
this.$message({
|
||||
message: '上传的文件格式错误',
|
||||
type: 'warning',
|
||||
})
|
||||
},
|
||||
warning_1() {
|
||||
this.$message({
|
||||
message: '服务器繁忙,请等待几分钟',
|
||||
type: 'warning',
|
||||
})
|
||||
},
|
||||
openFullScreen1() {
|
||||
this.fullscreenLoading = true
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
::v-deep.el-header {
|
||||
padding: 0;
|
||||
}
|
||||
::v-deep.el-main {
|
||||
flex: none;
|
||||
}
|
||||
::v-deep.el-main[data-v-1e94e001] {
|
||||
line-height: 50px;
|
||||
}
|
||||
::v-deep.el-main {
|
||||
padding: 0;
|
||||
margin-left: -1px;
|
||||
}
|
||||
.el-header,
|
||||
.el-footer {
|
||||
background-color: #b3c0d1;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
line-height: 60px;
|
||||
}
|
||||
|
||||
.el-aside {
|
||||
background-color: #d3dce6;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
line-height: 200px;
|
||||
}
|
||||
|
||||
.el-main {
|
||||
background-color: #e9eef3;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
line-height: 160px;
|
||||
}
|
||||
|
||||
body > .el-container {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.el-container:nth-child(5) .el-aside,
|
||||
.el-container:nth-child(6) .el-aside {
|
||||
line-height: 260px;
|
||||
}
|
||||
|
||||
.el-container:nth-child(7) .el-aside {
|
||||
line-height: 320px;
|
||||
}
|
||||
.el-carousel__item h3 {
|
||||
color: #475669;
|
||||
font-size: 18px;
|
||||
opacity: 0.75;
|
||||
line-height: 10px;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.el-carousel__item:nth-child(2n) {
|
||||
background-color: #99a9bf;
|
||||
}
|
||||
|
||||
.el-carousel__item:nth-child(2n + 1) {
|
||||
background-color: #d3dce6;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,150 @@
|
|||
<template>
|
||||
<div class="login_container">
|
||||
<el-row>
|
||||
<el-col :span="22" :xs="0"></el-col>
|
||||
</el-row>
|
||||
<el-col :span="12" :xs="24">
|
||||
<el-form class="login_form" ref="form" :model="form" :rules="rules">
|
||||
<h1>Hello</h1>
|
||||
<h2>欢迎注册知识图谱分析数据网站</h2>
|
||||
<el-form-item prop="name">
|
||||
<el-input
|
||||
v-model="form.name"
|
||||
prefix-icon="el-icon-user"
|
||||
placeholder="请输入注册账号"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="password">
|
||||
<el-input
|
||||
v-model="form.password"
|
||||
placeholder="请输入注册密码"
|
||||
prefix-icon="el-icon-lock"
|
||||
type="password"
|
||||
show-password></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item prop="repassword">
|
||||
<el-input
|
||||
v-model="form.repassword"
|
||||
placeholder="请再次输入注册密码"
|
||||
prefix-icon="el-icon-lock"
|
||||
type="password"
|
||||
show-password></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item> </el-form-item>
|
||||
<el-form-item>
|
||||
<el-button
|
||||
class="login_button"
|
||||
type="primary"
|
||||
size="default"
|
||||
@click="login(form)">
|
||||
注册
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-col>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import router from '@/router'
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
form: {
|
||||
name: '',
|
||||
password: '',
|
||||
repassword: '',
|
||||
},
|
||||
rules: {
|
||||
name: [{ required: true, message: '请输入注册账号', trigger: 'blur' }],
|
||||
password: [
|
||||
{ required: true, message: '请输入注册密码', trigger: 'blur' },
|
||||
],
|
||||
repassword: [
|
||||
{ required: true, message: '请输入注册密码', trigger: 'blur' },
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
login(form) {
|
||||
if (this.form.password === '' || this.form.name === '') {
|
||||
this.info()
|
||||
} else if (this.form.password !== this.form.repassword) {
|
||||
this.warning()
|
||||
} else {
|
||||
const formData = new FormData()
|
||||
formData.append('username', this.form.name)
|
||||
formData.append('password', this.form.password)
|
||||
this.$axios.put('/api/v1/user', formData).then((res) => {
|
||||
if (res.data.msg !== '添加成功') {
|
||||
this.warning_1()
|
||||
} else {
|
||||
this.success()
|
||||
localStorage.setItem('user', 1)
|
||||
router.push('/Range')
|
||||
}
|
||||
console.log(res)
|
||||
})
|
||||
}
|
||||
},
|
||||
warning() {
|
||||
this.$message({
|
||||
message: '输入的密码与第二次输入的密码不一致',
|
||||
type: 'warning',
|
||||
})
|
||||
},
|
||||
warning_1() {
|
||||
this.$message({
|
||||
message: '该账号已经被注册!',
|
||||
type: 'warning',
|
||||
})
|
||||
},
|
||||
info() {
|
||||
this.$message({
|
||||
message: '请输入注册密码或者账号',
|
||||
})
|
||||
},
|
||||
success() {
|
||||
this.$message({
|
||||
message: '注册成功!欢迎回来',
|
||||
type: 'success',
|
||||
})
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.login_container {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: url('https://pic1.zhimg.com/v2-66f210d04b273c6616f00bece467223a_1440w.jpg?source=172ae18b')
|
||||
no-repeat;
|
||||
background-size: cover;
|
||||
.login_form {
|
||||
animation-name: bounceIn;
|
||||
animation-duration: 3s;
|
||||
background-color: darkgrey;
|
||||
border-radius: 2%;
|
||||
opacity: 0.9;
|
||||
position: relative;
|
||||
width: 80%;
|
||||
top: 30vh;
|
||||
background-size: cover;
|
||||
padding: 10px 10px 40px 10px;
|
||||
margin-top: -100px;
|
||||
margin-left: 800px;
|
||||
h1 {
|
||||
color: white;
|
||||
font-size: 40px;
|
||||
}
|
||||
h2 {
|
||||
font-size: 20px;
|
||||
color: white;
|
||||
margin: 20px 0px;
|
||||
}
|
||||
.login_button {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,68 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-container style="height: 1500px; border: 1px solid #eee">
|
||||
<el-aside width="200px" style="background-color: rgb(238, 241, 246)">
|
||||
<el-menu router>
|
||||
<el-submenu>
|
||||
<template slot="title"
|
||||
><i class="el-icon-message"></i>功能点</template
|
||||
>
|
||||
<el-menu-item-group>
|
||||
<el-menu-item index="/Range">上传word</el-menu-item>
|
||||
<el-menu-item index="/Content_1">图谱分析</el-menu-item>
|
||||
</el-menu-item-group>
|
||||
</el-submenu>
|
||||
</el-menu>
|
||||
</el-aside>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
const item = {
|
||||
date: '2016-05-02',
|
||||
name: '王小虎',
|
||||
address: '上海市普陀区金沙江路 1518 弄',
|
||||
}
|
||||
return {
|
||||
tableData: Array(10).fill(item),
|
||||
img: [
|
||||
{
|
||||
imgs: 'https://p2.itc.cn/images01/20210621/4998136c64fc45baa7c11b38b05165b7.jpeg',
|
||||
},
|
||||
{
|
||||
imgs: 'https://img.best73.com/20210324/zip_1616583234t3CRS6.jpg',
|
||||
},
|
||||
{
|
||||
imgs: 'https://nimg.ws.126.net/?url=http%3A%2F%2Fdingyue.ws.126.net%2F2021%2F0726%2F4d7d1f46j00qwugbb000vc000hs008lg.jpg&thumbnail=660x2147483647&quality=80&type=jpg',
|
||||
},
|
||||
{
|
||||
imgs: 'https://n.sinaimg.cn/spider2021612/155/w650h305/20210612/5751-krhvrxu0196238.jpg',
|
||||
},
|
||||
],
|
||||
labelPosition: 'right',
|
||||
}
|
||||
},
|
||||
|
||||
created() {},
|
||||
mounted() {},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.el-header {
|
||||
background-color: #b3c0d1;
|
||||
color: #333;
|
||||
line-height: 60px;
|
||||
}
|
||||
|
||||
.el-aside {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
::v-deep.el-table {
|
||||
line-height: 0;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,74 @@
|
|||
<template>
|
||||
<div>
|
||||
<el-container>
|
||||
<el-aside width="200px"><SideMenu /></el-aside>
|
||||
<el-container>
|
||||
<el-header><Header /></el-header>
|
||||
<el-main>
|
||||
<router-view />
|
||||
</el-main>
|
||||
<el-footer>Footer</el-footer>
|
||||
</el-container>
|
||||
</el-container>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Header from '../../components/Header/Header.vue'
|
||||
import SideMenu from '../SideMenu/SideMenu.vue'
|
||||
|
||||
export default {
|
||||
components: { Header, SideMenu },
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
mounted() {},
|
||||
methods: {},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
::v-deep.el-header {
|
||||
padding: 0;
|
||||
}
|
||||
::v-deep.el-main {
|
||||
flex: none;
|
||||
}
|
||||
::v-deep.el-main {
|
||||
padding: 0;
|
||||
margin-left: -1px;
|
||||
}
|
||||
.el-header,
|
||||
.el-footer {
|
||||
background-color: #b3c0d1;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
line-height: 60px;
|
||||
}
|
||||
|
||||
.el-aside {
|
||||
background-color: #d3dce6;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
line-height: 200px;
|
||||
}
|
||||
|
||||
.el-main {
|
||||
background-color: #e9eef3;
|
||||
color: #333;
|
||||
text-align: center;
|
||||
line-height: 160px;
|
||||
}
|
||||
|
||||
body > .el-container {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
.el-container:nth-child(5) .el-aside,
|
||||
.el-container:nth-child(6) .el-aside {
|
||||
line-height: 260px;
|
||||
}
|
||||
|
||||
.el-container:nth-child(7) .el-aside {
|
||||
line-height: 320px;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,21 @@
|
|||
const { defineConfig } = require('@vue/cli-service')
|
||||
module.exports = defineConfig({
|
||||
transpileDependencies: true,
|
||||
publicPath: '/static',
|
||||
lintOnSave: false,
|
||||
devServer: {
|
||||
host: '0.0.0.0', //可以忽略不写
|
||||
port: 8084,//它是用来修改你打开后的端口号的
|
||||
open: true,//值为 true的话,项目启动时自动打开到浏览器里边, false不会打开
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:8090',//跨域请求的公共地址
|
||||
ws: false, //也可以忽略不写,不写不会影响跨域
|
||||
changeOrigin: true, //是否开启跨域,值为 true 就是开启, false 不开启
|
||||
// pathRewrite: {
|
||||
// '^/api': ''//注册全局路径, 但是在你请求的时候前面需要加上 /api
|
||||
// }
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
|
@ -0,0 +1,195 @@
|
|||
# # -*- encoding: utf-8 -*-
|
||||
# # @File : main
|
||||
# # @Docs : 程序入口
|
||||
import multiprocessing
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
from starlette.staticfiles import StaticFiles
|
||||
from uvicorn import Config, Server
|
||||
|
||||
from api import Base_api, STATIC
|
||||
from webui import Handler
|
||||
|
||||
# 文件挂载
|
||||
Base_api.mount(
|
||||
path="/static",
|
||||
app=StaticFiles(directory=STATIC),
|
||||
name="static",
|
||||
)
|
||||
|
||||
|
||||
class ProcessExecutor:
|
||||
def __init__(self):
|
||||
self.tasks = []
|
||||
|
||||
def add_task(self, func, *args, **kwargs):
|
||||
self.tasks.append((func, args, kwargs))
|
||||
|
||||
def start(self):
|
||||
processes = []
|
||||
with multiprocessing.Pool() as pool:
|
||||
for task in self.tasks:
|
||||
func, args, kwargs = task
|
||||
process = pool.apply_async(func, args=args, kwds=kwargs)
|
||||
processes.append(process)
|
||||
|
||||
for process in processes:
|
||||
process.get()
|
||||
|
||||
|
||||
class WebModel(BaseModel):
|
||||
"""
|
||||
web 模型
|
||||
"""
|
||||
reload: bool = True
|
||||
host: Optional[str] = "localhost"
|
||||
port: Optional[int] = 8090
|
||||
|
||||
|
||||
def web_server(conf: WebModel = WebModel()) -> bool:
|
||||
srv: Server = Server(Config(
|
||||
app=Base_api,
|
||||
host=conf.host,
|
||||
port=conf.port,
|
||||
reload=conf.reload,
|
||||
))
|
||||
srv.run()
|
||||
return srv.started
|
||||
|
||||
|
||||
def web_ui():
|
||||
import webview.menu as wm
|
||||
|
||||
app = Handler()
|
||||
|
||||
app.add_menu(
|
||||
'服务选择',
|
||||
[
|
||||
wm.MenuAction('前端', app.frontend),
|
||||
wm.MenuSeparator(),
|
||||
wm.MenuAction("后端", app.backend),
|
||||
],
|
||||
)
|
||||
app.add_menu(
|
||||
'主页',
|
||||
[
|
||||
wm.MenuAction('运行状况', app.index),
|
||||
wm.MenuSeparator(),
|
||||
wm.MenuAction('自适应窗口', app.auto_resize),
|
||||
],
|
||||
)
|
||||
app.start_webview()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
executor = ProcessExecutor()
|
||||
executor.add_task(web_ui)
|
||||
executor.add_task(web_server)
|
||||
|
||||
try:
|
||||
executor.start()
|
||||
except (Exception, RuntimeError) as _:
|
||||
pass
|
||||
# import asyncio
|
||||
#
|
||||
# from utils.common import BaseClient
|
||||
# from utils.config import Config, log
|
||||
# from utils.model.agent import AgentModel, ChatModel, TaskModel
|
||||
#
|
||||
#
|
||||
# class AsyncQueue:
|
||||
# """
|
||||
# 异步队列
|
||||
# """
|
||||
#
|
||||
# def __init__(self):
|
||||
# self.queue = asyncio.Queue()
|
||||
#
|
||||
# async def put(self, item):
|
||||
# await self.queue.put(item)
|
||||
#
|
||||
# async def get(self):
|
||||
# return await self.queue.get()
|
||||
#
|
||||
# def empty(self):
|
||||
# return self.queue.empty()
|
||||
#
|
||||
# def size(self):
|
||||
# return self.queue.qsize()
|
||||
#
|
||||
#
|
||||
# async def process_queue(queue):
|
||||
# while not queue.empty():
|
||||
# item: TaskModel = await queue.get()
|
||||
# # 处理item
|
||||
# try:
|
||||
# reply = await item.execute()
|
||||
# except (Exception, RuntimeError) as err:
|
||||
# log.exception(err)
|
||||
# else:
|
||||
# log.info(reply)
|
||||
#
|
||||
#
|
||||
# print(Config.get_select_config())
|
||||
# client = BaseClient(
|
||||
# ChatModel(**Config.get_select_config())
|
||||
# )
|
||||
# researcher = AgentModel(
|
||||
# role='Researcher',
|
||||
# backstory="You're a world class researcher working on a major data science company",
|
||||
# client=client,
|
||||
# )
|
||||
# writer = AgentModel(
|
||||
# role='Writer',
|
||||
# backstory="You're a famous technical writer, specialized on writing data related content",
|
||||
# client=client,
|
||||
# )
|
||||
#
|
||||
# task1 = TaskModel(
|
||||
# agent=researcher,
|
||||
# description="你好"
|
||||
# )
|
||||
#
|
||||
# task2 = TaskModel(
|
||||
# agent=writer,
|
||||
# description="你是谁?"
|
||||
# )
|
||||
#
|
||||
#
|
||||
# async def task():
|
||||
# # 加入数据到队列
|
||||
# async_queue = AsyncQueue()
|
||||
# # 添加队列任务
|
||||
# await async_queue.put(
|
||||
# task1,
|
||||
# )
|
||||
# await async_queue.put(
|
||||
# task2,
|
||||
# )
|
||||
# # 异步处理队列
|
||||
# await process_queue(async_queue)
|
||||
#
|
||||
#
|
||||
# asyncio.run(task())
|
||||
|
||||
# if __name__ == '__main__':
|
||||
#
|
||||
# asyncio.run(
|
||||
# task()
|
||||
# )
|
||||
# if __name__ == '__main__':
|
||||
# import asyncio
|
||||
#
|
||||
# client = BaseClient(
|
||||
# ChatModel(**Config.get_select_config())
|
||||
# )
|
||||
# # log.info(client.send_to_chatgpt("你好"))
|
||||
# asyncio.run(client.send_to_chatgpt(
|
||||
# [
|
||||
# {
|
||||
# "role": "user",
|
||||
# "content": "你好!"
|
||||
# }
|
||||
# ]
|
||||
# ))
|
|
@ -0,0 +1,6 @@
|
|||
fastapi[all]
|
||||
pymysql
|
||||
docx2txt
|
||||
tiktoken
|
||||
pywebview
|
||||
dbutils
|
|
@ -0,0 +1,14 @@
|
|||
将软件设计文档中的功能、角色、实体以及它们之间的关系进行提取, 并转化为类似于以下示例的JSON格式。总内容不能超过400字。 `//`中包含一些特殊要求
|
||||
```json
|
||||
{
|
||||
"function": {
|
||||
"学生管理": {"relation": "可以创建和管理"},
|
||||
"房间分配": {"relation": "房间分配与管理"},
|
||||
"功能点": {"relation": "实体关系"},
|
||||
// 添加更多的功能点机器关系|操作对象,字段的值可以为""
|
||||
},
|
||||
"summarize": "简要总结一下设计文档中实现的内容,用作后续的上下文提示 prompt,控制在 200 字以内.",
|
||||
"new_summarize": "指出设计文档中还能继续增加的功能,并指出添加该功能的原因。分点输出 控制在 200 字内。"
|
||||
}
|
||||
```
|
||||
被```rule $设计文档 ``` 包裹的内容为需要转化的内容, 只需输出完整的json转换内容即可.
|
|
@ -2,9 +2,135 @@
|
|||
"""
|
||||
@Desc : 代理执行器
|
||||
"""
|
||||
import json
|
||||
import random
|
||||
import re
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from utils.common import BaseClient
|
||||
from utils.config import Config, log
|
||||
from utils.model.agent import AgentModel
|
||||
|
||||
|
||||
class Executor:
|
||||
"""
|
||||
执行器
|
||||
"""
|
||||
|
||||
|
||||
class SummarizeRobot:
|
||||
|
||||
def __init__(self):
|
||||
self.prompt = self.__load_prompt
|
||||
|
||||
def robot(self, client: BaseClient) -> AgentModel:
|
||||
return AgentModel(
|
||||
role='总结机器人',
|
||||
backstory=self.prompt,
|
||||
client=client,
|
||||
)
|
||||
|
||||
@property
|
||||
def __load_prompt(self) -> str:
|
||||
file = str(Config.get_path("res", "prompt.key"))
|
||||
with open(file, "r", encoding="utf-8") as file_obj:
|
||||
return file_obj.read()
|
||||
|
||||
@staticmethod
|
||||
def generate_frontend_data(keys: list) -> list:
|
||||
result = [
|
||||
{
|
||||
"name": key,
|
||||
"itemStyle": {
|
||||
"color": f'rgb({random.randint(0, 255)},{random.randint(0, 255)},{random.randint(0, 255)})'
|
||||
}
|
||||
} for key in keys
|
||||
]
|
||||
return result
|
||||
|
||||
def complete_data(self, resp: str):
|
||||
""" 数据补全 """
|
||||
result = {"clean": [], 'origin': self.clean_parse_data(resp)}
|
||||
|
||||
if result['origin'] is None:
|
||||
raise RuntimeError(f"数据为空")
|
||||
|
||||
try:
|
||||
# 提取function中所有的第一层键
|
||||
func_keys = list(result['origin']['function'].keys())
|
||||
|
||||
# 基础功能点
|
||||
result['clean'].append(self.generate_frontend_data(func_keys))
|
||||
|
||||
except Exception as err:
|
||||
log.debug(err)
|
||||
raise RuntimeError(err)
|
||||
|
||||
return result
|
||||
|
||||
def clean_parse_data(self, input_str: str):
|
||||
""" 数据清洗 """
|
||||
|
||||
parsed_content: Optional[dict] = None
|
||||
|
||||
if len(result := re.findall('```json(.*?)```', input_str)):
|
||||
reply = json.loads(result[0])
|
||||
log.info(reply)
|
||||
self.save_to_file(reply)
|
||||
|
||||
try:
|
||||
try:
|
||||
parsed_content = json.loads(input_str)
|
||||
except json.JSONDecodeError as _:
|
||||
|
||||
input_str = input_str.replace("`", "").strip("\n")
|
||||
try:
|
||||
# 寻找第一个 "{" 的位置
|
||||
first_brace_index = input_str.find("{")
|
||||
|
||||
# 寻找最后一个 "}" 的位置
|
||||
last_brace_index = input_str.rfind("}")
|
||||
input_str = input_str[first_brace_index:last_brace_index + 1]
|
||||
except IndexError or Exception as err:
|
||||
log.warn(err)
|
||||
pass
|
||||
|
||||
# 使用正则表达式提取大括号内的内容
|
||||
matches = re.findall(r'\{(.*)}', input_str)
|
||||
|
||||
if matches:
|
||||
# 获取匹配到的内容
|
||||
extracted = matches[0] # 假设只有一个匹配,你可以根据实际情况调整
|
||||
|
||||
# 尝试将提取的内容反序列化为JSON
|
||||
parsed_content = json.loads("{" + extracted + "}")
|
||||
else:
|
||||
# 如果没有匹配到大括号,抛出异常
|
||||
raise Exception("未找到大括号内的内容")
|
||||
finally:
|
||||
log.warn(parsed_content)
|
||||
if isinstance(parsed_content, dict):
|
||||
self.save_to_file(parsed_content)
|
||||
# 返回反序列化后的JSON
|
||||
return parsed_content
|
||||
|
||||
except Exception as err:
|
||||
log.exception(f"发生异常: {err}")
|
||||
raise RuntimeError(err)
|
||||
|
||||
@staticmethod
|
||||
def save_to_file(parsed_content):
|
||||
try:
|
||||
# 获取当前时间作为文件名
|
||||
date_line = datetime.now().strftime('%Y%m%d%H%M%S')
|
||||
filename = Config.get_path("res", "gen", f"{date_line}.json")
|
||||
|
||||
with open(filename, "w", encoding="utf-8") as file_obj:
|
||||
file_obj.write(json.dumps(parsed_content, ensure_ascii=False))
|
||||
|
||||
log.info(f"文件 '{date_line}.json' 已保存至 res/gen 目录中")
|
||||
|
||||
except Exception as e:
|
||||
# 捕获写入文件时的异常并抛出
|
||||
raise Exception(f"写入文件时发生异常: {str(e)}")
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
用户鉴权
|
||||
|
||||
>>> from utils.auth.auth import AuthParams
|
||||
>>> username = "demo"
|
||||
>>> AuthParams.Delete(username)
|
||||
"""
|
||||
from .auth import AuthParams
|
|
@ -0,0 +1,116 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@GitHub: https://github.com/Fromsko
|
||||
@Author: Fromsko
|
||||
@Desc : 鉴权
|
||||
"""
|
||||
import pymysql
|
||||
|
||||
from utils.auth.model import JsonMsg
|
||||
from utils.database.storage import MySQLStorage
|
||||
|
||||
|
||||
class AuthParams(MySQLStorage, JsonMsg):
|
||||
|
||||
def __init__(self, conf: dict):
|
||||
super().__init__(conf)
|
||||
self.create_user_table()
|
||||
|
||||
def query(self, user: str):
|
||||
"""查询数据
|
||||
|
||||
参数:
|
||||
`user`: 用户
|
||||
|
||||
返回:
|
||||
- `msg`: 统一格式数据
|
||||
"""
|
||||
result = self.execute_query(
|
||||
"SELECT * FROM users WHERE username = %s",
|
||||
(user,)
|
||||
)
|
||||
|
||||
if not result:
|
||||
self.base_msg['msg'] = self.Query.notfound
|
||||
else:
|
||||
self.base_msg['msg'] = result[0]
|
||||
|
||||
return self.base_msg
|
||||
|
||||
def insert(self, user, hash_passwd):
|
||||
"""插入数据
|
||||
|
||||
参数:
|
||||
- `user`: 用户
|
||||
- `hash_password`: 哈希后的密码
|
||||
|
||||
返回:
|
||||
- `msg`: 统一格式数据
|
||||
"""
|
||||
|
||||
try:
|
||||
self.execute_update(
|
||||
"INSERT INTO users (username, password) VALUES (%s, %s)",
|
||||
(user, hash_passwd)
|
||||
)
|
||||
self.base_msg['msg'] = self.Insert.succeed
|
||||
|
||||
except pymysql.IntegrityError as _:
|
||||
self.base_msg['msg'] = self.Query.exits
|
||||
|
||||
return self.base_msg
|
||||
|
||||
def update(self, user, new_password):
|
||||
"""更新数据
|
||||
|
||||
参数:
|
||||
- `user`: 用户
|
||||
- `new_password`: 新密码
|
||||
|
||||
返回:
|
||||
- `msg`: 统一格式数据
|
||||
"""
|
||||
query_result = self.query(user)
|
||||
if query_result['msg'] == self.Query.notfound:
|
||||
return query_result
|
||||
|
||||
try:
|
||||
self.execute_update(
|
||||
"UPDATE users SET password = %s WHERE username = %s",
|
||||
(new_password, user)
|
||||
)
|
||||
self.base_msg['msg'] = self.Update.succeed
|
||||
|
||||
except Exception as err:
|
||||
self.base_msg['msg'] = self.Update.failed.format(err)
|
||||
|
||||
return self.base_msg
|
||||
|
||||
def delete(self, user):
|
||||
"""删除数据
|
||||
|
||||
参数:
|
||||
`user`: 待删除用户
|
||||
|
||||
返回:
|
||||
- `msg`: 统一格式数据
|
||||
"""
|
||||
query_result = self.query(user)
|
||||
if query_result['msg'] == self.Query.notfound:
|
||||
return query_result
|
||||
|
||||
try:
|
||||
self.execute_update(
|
||||
"DELETE FROM users WHERE username = %s",
|
||||
(user,)
|
||||
)
|
||||
self.base_msg['msg'] = self.Delete.succeed
|
||||
|
||||
except Exception as err:
|
||||
self.base_msg['msg'] = self.Delete.failed.format(err)
|
||||
|
||||
return self.base_msg
|
||||
|
||||
__all__ = [
|
||||
"AuthParams",
|
||||
]
|
|
@ -0,0 +1,27 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@GitHub: https://github.com/Fromsko
|
||||
@Author: Fromsko
|
||||
@Desc : 数据模型
|
||||
"""
|
||||
import time
|
||||
|
||||
|
||||
class JsonMsg:
|
||||
base_msg = {"code": 200, "msg": "", "dateline": time.time()}
|
||||
|
||||
class Query:
|
||||
exits = "用户已存在"
|
||||
notfound = "用户不存在"
|
||||
|
||||
class Insert:
|
||||
succeed = "添加成功"
|
||||
failed = "添加失败"
|
||||
|
||||
class Update:
|
||||
succeed = "更新成功"
|
||||
failed = "更新失败 {err}"
|
||||
|
||||
class Delete:
|
||||
succeed = "删除成功"
|
||||
failed = "删除失败 {err}"
|
|
@ -0,0 +1,9 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
GPT模型
|
||||
"""
|
||||
from .chatgpt import BaseClient
|
||||
|
||||
__all__ = [
|
||||
"BaseClient"
|
||||
]
|
|
@ -0,0 +1,62 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@Desc : GPTModel
|
||||
"""
|
||||
import json
|
||||
from typing import Any, Optional, Union
|
||||
|
||||
import httpx
|
||||
|
||||
from utils.config import log
|
||||
from utils.model.agent import ChatAbstractModel, ChatModel
|
||||
from utils.model.error import GPTAPIError
|
||||
|
||||
|
||||
class BaseClient(ChatAbstractModel):
|
||||
"""
|
||||
基础模型
|
||||
"""
|
||||
|
||||
def __init__(self, chat_model: ChatModel):
|
||||
self.api_key = chat_model.api_key
|
||||
self.api_base = chat_model.api_base
|
||||
self.proxy = chat_model.proxy if chat_model.status else None
|
||||
|
||||
async def _fetch(self, payload) -> Optional[dict]:
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json',
|
||||
'Authorization': f'Bearer {self.api_key}',
|
||||
}
|
||||
|
||||
async with httpx.AsyncClient(proxies=self.proxy, timeout=120) as client:
|
||||
resp = await client.post(
|
||||
f'{self.api_base}/chat/completions',
|
||||
headers=headers,
|
||||
data=payload,
|
||||
)
|
||||
return resp.json()
|
||||
|
||||
async def send_to_chatgpt(self, prompt: list[dict]) -> Union[dict, str]:
|
||||
"""发送信息"""
|
||||
payload = json.dumps({
|
||||
"model": "gpt-3.5-turbo-16k",
|
||||
"messages": prompt,
|
||||
"temperature": 0.2,
|
||||
"presence_penalty": 0,
|
||||
"max_tokens": 3000,
|
||||
})
|
||||
try:
|
||||
resp = await self._fetch(payload)
|
||||
except (TimeoutError, ConnectionError) as err:
|
||||
log.exception(err)
|
||||
return GPTAPIError.timeout_error
|
||||
|
||||
try:
|
||||
reply = resp['choices'][0]['message']['content']
|
||||
except (IndexError, RuntimeError, TypeError, KeyError) as err:
|
||||
log.error(err)
|
||||
return GPTAPIError.server_error
|
||||
else:
|
||||
log.info(f"实际消耗: {resp['usage']['total_tokens']}")
|
||||
return reply
|
|
@ -0,0 +1,55 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@Desc : 模型自定义
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
|
||||
|
||||
class AsyncQueue:
|
||||
"""
|
||||
异步队列
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
self.queue = asyncio.Queue()
|
||||
|
||||
async def put(self, item):
|
||||
await self.queue.put(item)
|
||||
|
||||
async def get(self):
|
||||
return await self.queue.get()
|
||||
|
||||
def empty(self):
|
||||
return self.queue.empty()
|
||||
|
||||
def size(self):
|
||||
return self.queue.qsize()
|
||||
|
||||
|
||||
|
||||
"""
|
||||
async def process_queue(queue):
|
||||
while not queue.empty():
|
||||
item = await queue.get()
|
||||
# 处理item
|
||||
print(item)
|
||||
|
||||
|
||||
async def task():
|
||||
# 加入数据到队列
|
||||
await async_queue.put('item1')
|
||||
await async_queue.put('item2')
|
||||
await async_queue.put('item3')
|
||||
|
||||
# 异步处理队列
|
||||
await process_queue(async_queue)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
async_queue = AsyncQueue()
|
||||
|
||||
asyncio.run(
|
||||
task()
|
||||
)
|
||||
"""
|
|
@ -0,0 +1,19 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
配置
|
||||
|
||||
>>> from utils.config import Config
|
||||
>>> Config.config
|
||||
>>> Config.get_path("res")
|
||||
>>> Config.get_select_config("chat")
|
||||
"""
|
||||
from .base import BaseConfig, hash_password, log
|
||||
from .easy import EasyTool
|
||||
|
||||
Config = BaseConfig()
|
||||
|
||||
__all__ = [
|
||||
"hash_password",
|
||||
"Config",
|
||||
"log",
|
||||
]
|
|
@ -0,0 +1,81 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@Desc : 基础配置
|
||||
"""
|
||||
import hashlib
|
||||
|
||||
import yaml
|
||||
|
||||
from utils.config.easy import EasyTool
|
||||
from utils.log import Logger
|
||||
|
||||
log = Logger("KnowledgeMap")
|
||||
|
||||
|
||||
class BaseConfig(EasyTool):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.config = self.load_config()
|
||||
|
||||
def load_config(self):
|
||||
"""导入配置文件
|
||||
|
||||
Returns:
|
||||
_type_: dict
|
||||
"""
|
||||
if self.config_file.exists():
|
||||
with open(f"{self.config_file}", "r", encoding="utf-8") as file:
|
||||
config = yaml.safe_load(file)
|
||||
else:
|
||||
self.save_config(self.yaml_config)
|
||||
log.exception("未找到配置文件, 正在生成!")
|
||||
exit()
|
||||
return config
|
||||
|
||||
def save_config(self, config):
|
||||
with open(f"{self.config_file}", "w", encoding="utf-8") as file:
|
||||
yaml.dump(config, file, default_flow_style=False)
|
||||
|
||||
@property
|
||||
def yaml_config(self):
|
||||
config = {
|
||||
"mysql": {
|
||||
"host": "127.0.0.1",
|
||||
"user": "root",
|
||||
"password": "123456",
|
||||
"database": "knowlege",
|
||||
},
|
||||
"chat": {
|
||||
"status": False,
|
||||
"api_key": "default_api_key",
|
||||
"proxy": "http://localhost:7890",
|
||||
"api_base": "https://api.aigcbest.top/v1",
|
||||
}
|
||||
}
|
||||
return config
|
||||
|
||||
def get_select_config(self, select_key: str = "chat") -> dict:
|
||||
"""获取选择的配置
|
||||
|
||||
参数:
|
||||
- `select_key`: 选择的键
|
||||
|
||||
返回:
|
||||
- `dict`: 字典
|
||||
"""
|
||||
conf = self.config[select_key]
|
||||
if select_key != "chat": # 转换密码
|
||||
conf['password'] = f"{conf['password']}"
|
||||
return conf
|
||||
|
||||
|
||||
def hash_password(password):
|
||||
""" hash for password """
|
||||
return hashlib.sha256(password.encode()).hexdigest()
|
||||
|
||||
|
||||
__all__ = [
|
||||
"hash_password",
|
||||
"BaseConfig",
|
||||
"log"
|
||||
]
|
|
@ -0,0 +1,35 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@Desc : 基类
|
||||
"""
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
class EasyTool(object):
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._gen_dir(["res", "frontend", "res/prompts"])
|
||||
self.config_file = self.get_path("config.yaml")
|
||||
|
||||
@property
|
||||
def work_dir(self):
|
||||
""" Return to work path """
|
||||
return Path.cwd()
|
||||
|
||||
def get_path(self, *args) -> Path:
|
||||
""" get dir/file path """
|
||||
local = self.work_dir.joinpath(*args)
|
||||
if len(local.name.split('.')) == 0:
|
||||
if not local.exists():
|
||||
local.mkdir()
|
||||
else:
|
||||
if not local.parent.exists():
|
||||
local.parent.mkdir()
|
||||
return local
|
||||
|
||||
def _gen_dir(self, dir_list=None):
|
||||
""" generate dirs """
|
||||
for dir_ in dir_list:
|
||||
if (dir_ := self.work_dir.joinpath(dir_)).exists():
|
||||
continue
|
||||
dir_.mkdir()
|
|
@ -0,0 +1,14 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
数据库存储
|
||||
|
||||
>>> from utils.config import Config
|
||||
>>> from utils.database import MySQLStorage
|
||||
>>> db = MySQLStorage(Config.get_select_config("mysql"))
|
||||
>>> db.execute_query("SELECT * FROM Users;")
|
||||
"""
|
||||
from .storage import MySQLStorage
|
||||
|
||||
__all__ = [
|
||||
"MySQLStorage"
|
||||
]
|
|
@ -0,0 +1,68 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@Desc : 数据存储
|
||||
"""
|
||||
import pymysql
|
||||
from dbutils.pooled_db import PooledDB
|
||||
|
||||
from utils.config import log
|
||||
|
||||
|
||||
class MySQLStorage:
|
||||
_pool = None
|
||||
|
||||
def __init__(self, conf: dict):
|
||||
self.conn = self.load_config(conf)
|
||||
self.cursor = self.conn.cursor()
|
||||
|
||||
@classmethod
|
||||
def get_connection_pool(cls, conf: dict):
|
||||
if not cls._pool:
|
||||
cls._pool = PooledDB(
|
||||
creator=pymysql,
|
||||
mincached=5,
|
||||
maxcached=20,
|
||||
**conf
|
||||
)
|
||||
return cls._pool
|
||||
|
||||
@staticmethod
|
||||
def load_config(conf):
|
||||
try:
|
||||
pool = MySQLStorage.get_connection_pool(conf)
|
||||
conn = pool.connection()
|
||||
except Exception as err:
|
||||
raise RuntimeError(f"连接 MYSQL 错误: {err}")
|
||||
return conn
|
||||
|
||||
def execute_query(self, query, params=None):
|
||||
self.cursor.execute(query, params)
|
||||
return self.cursor.fetchall()
|
||||
|
||||
def execute_update(self, query, params=None):
|
||||
self.cursor.execute(query, params)
|
||||
self.conn.commit()
|
||||
|
||||
def create_user_table(self):
|
||||
query = """
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
username VARCHAR(255) UNIQUE NOT NULL,
|
||||
password VARCHAR(255) NOT NULL,
|
||||
create_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
"""
|
||||
self.execute_update(query)
|
||||
|
||||
def __del__(self):
|
||||
try:
|
||||
self.cursor.close()
|
||||
self.conn.close()
|
||||
except RuntimeError as err:
|
||||
log.excption(err)
|
||||
exit()
|
||||
|
||||
|
||||
__all__ = [
|
||||
"MySQLStorage",
|
||||
]
|
|
@ -0,0 +1,50 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@Desc : 日志类
|
||||
"""
|
||||
import wrapt
|
||||
from loguru import logger
|
||||
|
||||
|
||||
class Logger:
|
||||
def __init__(self, name):
|
||||
self.__log = logger.bind(name=name)
|
||||
self.__log.add(
|
||||
sink=f"{name}.log",
|
||||
format="{time} | {level} | {message}",
|
||||
rotation="1 week",
|
||||
)
|
||||
|
||||
def __call__(self, func):
|
||||
@wrapt.decorator
|
||||
def wrapper(wrapped, instance, args, kwargs):
|
||||
resp = wrapped(*args, **kwargs)
|
||||
self.info(f"{wrapped.__name__}|=> {resp}")
|
||||
return resp
|
||||
|
||||
return wrapper(func)
|
||||
|
||||
@property
|
||||
def info(self):
|
||||
return self.__log.info
|
||||
|
||||
@property
|
||||
def warn(self):
|
||||
return self.__log.warning
|
||||
|
||||
@property
|
||||
def error(self):
|
||||
return self.__log.error
|
||||
|
||||
@property
|
||||
def debug(self):
|
||||
return self.__log.debug
|
||||
|
||||
@property
|
||||
def exception(self):
|
||||
return self.__log.exception
|
||||
|
||||
|
||||
__all__ = [
|
||||
'Logger',
|
||||
]
|
|
@ -0,0 +1,41 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
数据模型
|
||||
|
||||
```markdown
|
||||
## 分类
|
||||
1. 观察者模式
|
||||
2. 适配器模式
|
||||
3. 错误定义
|
||||
4. 数据模型
|
||||
```
|
||||
---
|
||||
使用
|
||||
```python
|
||||
>>> from utils.model.error import *
|
||||
>>> chat_model = ChatModel(**{"api_base": "", "api_key": ""})
|
||||
>>> chat_model.api_base
|
||||
>>> chat_model.api_key
|
||||
```
|
||||
"""
|
||||
from .adapter import DocumentAdapter, Docx, Doc, Txt
|
||||
from .agent import ChatModel
|
||||
from .error import GPTAPIError, UploadAPIError, UploadFileError
|
||||
from .observer import Notice, Observer
|
||||
|
||||
__all__ = [
|
||||
# GPT模型
|
||||
"ChatModel",
|
||||
# 文档
|
||||
"DocumentAdapter",
|
||||
"Doc",
|
||||
"Docx",
|
||||
"Txt",
|
||||
# 观察者模式
|
||||
"Observer",
|
||||
"Notice",
|
||||
# 错误
|
||||
"UploadAPIError",
|
||||
"UploadFileError",
|
||||
"GPTAPIError",
|
||||
]
|
|
@ -0,0 +1,136 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@Desc : 适配器模式
|
||||
|
||||
> 转化不同格式的文档 {docx | doc | txt} => txt
|
||||
"""
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from pathlib import Path
|
||||
from typing import BinaryIO, Union
|
||||
|
||||
from docx import Document
|
||||
from docx2txt import docx2txt
|
||||
from fastapi import UploadFile as File
|
||||
|
||||
from utils.config import Config, log
|
||||
from utils.model.error import UploadFileError
|
||||
|
||||
|
||||
class UploadFile(metaclass=ABCMeta):
|
||||
|
||||
@abstractmethod
|
||||
def parser(self, file: Union[str, Path, bytes, File]) -> str:
|
||||
"""解析器
|
||||
|
||||
参数:
|
||||
- `file`: 文件 | 文件路径
|
||||
|
||||
返回:
|
||||
- `str`: 文件内容
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def save_upload_file(file: File) -> str:
|
||||
file.filename = str(Config.get_path(
|
||||
"res", "upload",
|
||||
file.filename,
|
||||
))
|
||||
|
||||
try:
|
||||
with open(file.filename, "wb") as f:
|
||||
f.write(file.file.read())
|
||||
except (IOError, Exception):
|
||||
log.warn(UploadFileError.save_error)
|
||||
else:
|
||||
return file.filename
|
||||
|
||||
|
||||
def convert_doc_to_docx(doc_file, docx_file) -> str:
|
||||
doc = Document(doc_file)
|
||||
doc.save(docx_file)
|
||||
return docx_file
|
||||
|
||||
|
||||
def get_filename_info(file_path):
|
||||
path = Path(file_path)
|
||||
file_name = path.name
|
||||
file_name_without_extension = path.stem
|
||||
parent_directory = path.parent
|
||||
|
||||
return file_name, file_name_without_extension, parent_directory
|
||||
|
||||
|
||||
class Docx(UploadFile):
|
||||
"""
|
||||
解析 Docx 类型文档
|
||||
"""
|
||||
|
||||
def parser(self, file: Union[str, Path, bytes, File]) -> str:
|
||||
save_path: str = save_upload_file(file)
|
||||
|
||||
try:
|
||||
txt = docx2txt.process(save_path)
|
||||
except (IOError, Exception):
|
||||
log.warn(UploadFileError.parser_error)
|
||||
else:
|
||||
return txt
|
||||
return ""
|
||||
|
||||
|
||||
class Doc(UploadFile):
|
||||
"""
|
||||
解析 Doc 类型文档
|
||||
"""
|
||||
|
||||
def parser(self, file: Union[str, Path, bytes, File]) -> str:
|
||||
save_path: str = save_upload_file(file)
|
||||
_, filename, file_dir = get_filename_info(save_path)
|
||||
|
||||
try:
|
||||
save_path = convert_doc_to_docx(
|
||||
save_path,
|
||||
file_dir.joinpath(filename + ".docx"),
|
||||
)
|
||||
txt = docx2txt.process(save_path)
|
||||
except (IOError, Exception):
|
||||
log.warn(UploadFileError.parser_error)
|
||||
else:
|
||||
return txt
|
||||
return ""
|
||||
|
||||
|
||||
class Txt(UploadFile):
|
||||
"""
|
||||
解析 txt 类型文档
|
||||
"""
|
||||
|
||||
def parser(self, file: Union[str, Path, bytes, File]) -> str:
|
||||
file: BinaryIO = file.file
|
||||
|
||||
try:
|
||||
# 使用文件指针到首位
|
||||
file.seek(0)
|
||||
except (IOError, Exception):
|
||||
log.warn(UploadFileError.move_error)
|
||||
else:
|
||||
content: bytes = file.read()
|
||||
return content.decode('utf-8')
|
||||
return ""
|
||||
|
||||
|
||||
class DocumentAdapter(UploadFile):
|
||||
|
||||
def __init__(self, mode: UploadFile):
|
||||
self.mode: UploadFile = mode
|
||||
|
||||
def parser(self, file: Union[str, Path, bytes, File]) -> str:
|
||||
return self.mode.parser(file)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"DocumentAdapter",
|
||||
"Docx",
|
||||
"Doc",
|
||||
"Txt",
|
||||
]
|
|
@ -4,9 +4,11 @@
|
|||
|
||||
> 用于校验数据格式
|
||||
"""
|
||||
from typing import Optional
|
||||
import uuid
|
||||
from typing import Any, Optional, Union
|
||||
from pydantic import BaseModel, Field, UUID4
|
||||
|
||||
from pydantic import BaseModel
|
||||
from utils.config import log
|
||||
|
||||
|
||||
class ChatModel(BaseModel):
|
||||
|
@ -17,4 +19,97 @@ class ChatModel(BaseModel):
|
|||
# 请求代理
|
||||
proxy: Optional[str]
|
||||
# 是否代理
|
||||
flag: bool = False
|
||||
status: bool = False
|
||||
|
||||
|
||||
class ChatAbstractModel(object):
|
||||
|
||||
async def send_to_chatgpt(self, prompt: list[dict]) -> Union[dict, str]:
|
||||
"""发送至GPT模型
|
||||
|
||||
参数:
|
||||
- `prompt`: 请求载体
|
||||
|
||||
返回:
|
||||
- `Union[dict, str]`: 错误 | 信息
|
||||
"""
|
||||
|
||||
|
||||
class MsgModel(BaseModel):
|
||||
id: UUID4 = Field(
|
||||
default_factory=uuid.uuid4,
|
||||
frozen=True,
|
||||
description="Unique identifier for the object, not set by user.",
|
||||
)
|
||||
prompt: Optional[list[dict]] = Field(
|
||||
default=[{
|
||||
"role": "system",
|
||||
"content": "You are a large model robot that is very good at summarizing,"
|
||||
" which is suitable for software engineering and knowledge engineering"
|
||||
}],
|
||||
description="Prompt for the agent!",
|
||||
)
|
||||
content: Optional[str] = None
|
||||
|
||||
|
||||
class AgentModel(BaseModel):
|
||||
# 连接
|
||||
client: Optional[Any] = None
|
||||
# 角色
|
||||
role: str = Field(
|
||||
description="Role of the agent",
|
||||
)
|
||||
# 背景
|
||||
backstory: Optional[str] = Field(
|
||||
description="Backstory of the agent",
|
||||
)
|
||||
|
||||
async def execute_task(
|
||||
self, task: str,
|
||||
context: Optional[MsgModel] = None,
|
||||
) -> Union[dict, str]:
|
||||
self.client: ChatAbstractModel
|
||||
|
||||
if self.role is not None:
|
||||
log.info(f"Agent {self.role} is running...")
|
||||
|
||||
if self.backstory is not None:
|
||||
context.prompt.append({"role": "system", "content": self.backstory})
|
||||
context.prompt.append({"role": "user", "content": task})
|
||||
|
||||
if self.client is None:
|
||||
raise RuntimeError("Agent {role} client is not start!")
|
||||
|
||||
return await self.client.send_to_chatgpt(
|
||||
context.prompt,
|
||||
)
|
||||
|
||||
|
||||
class TaskModel(BaseModel):
|
||||
|
||||
__hash__ = object.__hash__
|
||||
description: str = Field(
|
||||
description="Description of the agent task.",
|
||||
)
|
||||
agent: Optional[AgentModel] = Field(
|
||||
description="Agent factory", default=None
|
||||
)
|
||||
result_collection: Optional[dict] = {}
|
||||
|
||||
async def execute(self, context: Optional[MsgModel] = MsgModel()) -> Union[dict, tuple]:
|
||||
if self.agent is not None:
|
||||
reply = await self.agent.execute_task(
|
||||
task=self.description,
|
||||
context=context,
|
||||
)
|
||||
|
||||
if isinstance(reply, dict): # 错误信息
|
||||
return reply
|
||||
|
||||
self.result_collection[self.agent.role] = {
|
||||
"id": context.id,
|
||||
"result": reply,
|
||||
"content": context.content,
|
||||
}
|
||||
return self.result_collection
|
||||
raise Exception(f"{self.description} is not a task.")
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@Desc : 错误模型定义
|
||||
"""
|
||||
from typing import Optional, Union
|
||||
|
||||
|
||||
class UploadFileError:
|
||||
move_error: Optional[Union[IOError, str]] = "文件指针移动失败"
|
||||
parser_error: Optional[Union[IOError, str]] = "解析文档异常"
|
||||
save_error: Optional[Union[IOError, str]] = "文件存储错误"
|
||||
|
||||
|
||||
class UploadAPIError:
|
||||
empty_upload_error: dict = {
|
||||
"code": 400,
|
||||
"err": "",
|
||||
}
|
||||
type_upload_error: dict = {
|
||||
"code": 400,
|
||||
"err": "文件格式错误",
|
||||
}
|
||||
parser_upload_error: dict = {
|
||||
"code": 400,
|
||||
"err": "文档解析失败",
|
||||
}
|
||||
size_upload_error: dict = {
|
||||
"code": 400,
|
||||
"err": "文件超过大小",
|
||||
}
|
||||
|
||||
|
||||
class GPTAPIError:
|
||||
timeout_error: dict = {
|
||||
"code": 400,
|
||||
"err": "请求超时",
|
||||
}
|
||||
server_error: dict = {
|
||||
"code": 500,
|
||||
"err": "服务器异常",
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
# -*- encoding: utf-8 -*-
|
||||
"""
|
||||
@Desc : 观察者模式
|
||||
|
||||
> 用来通知事件是否成功运行 | 处理对应的事情
|
||||
"""
|
||||
from abc import ABCMeta, abstractmethod
|
||||
|
||||
|
||||
class Observer(metaclass=ABCMeta): # 观察者
|
||||
|
||||
@abstractmethod
|
||||
def update(self, notice): # Notice类对象
|
||||
pass
|
||||
|
||||
|
||||
class Notice: # 发布者
|
||||
def __init__(self):
|
||||
self.observers: list[Observer] = []
|
||||
|
||||
def attach(self, obs: Observer):
|
||||
self.observers.append(obs)
|
||||
|
||||
def detach(self, obs: Observer):
|
||||
self.observers.remove(obs)
|
||||
|
||||
def notify(self):
|
||||
for obs in self.observers:
|
||||
obs.update(self)
|
||||
|
||||
|
||||
class StaffNotice(Notice):
|
||||
def __init__(self, company_info=None):
|
||||
super().__init__()
|
||||
self.__company_info = company_info
|
||||
|
||||
@property
|
||||
def company_info(self):
|
||||
return self.__company_info
|
||||
|
||||
@company_info.setter
|
||||
def company_info(self, info):
|
||||
self.__company_info = info
|
||||
self.notify()
|
||||
|
||||
|
||||
class Staff(Observer):
|
||||
def __init__(self):
|
||||
self.company_info = None
|
||||
|
||||
def update(self, notice: StaffNotice):
|
||||
self.company_info = notice.company_info
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
notify = StaffNotice("小米")
|
||||
|
||||
s1 = Staff()
|
||||
s2 = Staff()
|
||||
notify.attach(s1)
|
||||
notify.attach(s2)
|
||||
|
||||
notify.company_info = "Hello World!"
|
||||
|
||||
print(s1.company_info, s2.company_info)
|
||||
|
||||
notify.company_info = "Hello!"
|
||||
|
||||
print(s1.company_info, s2.company_info)
|
|
@ -0,0 +1,236 @@
|
|||
"""
|
||||
@Author: skong
|
||||
@File : webui.py
|
||||
@notes : Web UI with backend
|
||||
"""
|
||||
from typing import Any, List, Optional
|
||||
|
||||
import webview
|
||||
import webview.menu as wm
|
||||
from pydantic import BaseModel
|
||||
from webview import Window
|
||||
|
||||
from utils.log import Logger
|
||||
|
||||
|
||||
class Config:
|
||||
"""
|
||||
配置基类
|
||||
"""
|
||||
_version = 3.6
|
||||
_base_name: str = "知识图谱"
|
||||
_base_url: str = "http://localhost:8090"
|
||||
|
||||
_web: webview = webview
|
||||
_menu_items: List = []
|
||||
_default_view: Optional[Window] = None
|
||||
|
||||
log: Logger = Logger("webui")
|
||||
|
||||
__html = """
|
||||
<head>
|
||||
<style>
|
||||
body {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
background-color: #f2f2f2;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
.container {
|
||||
text-align: center;
|
||||
width: auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
font-size: 18px;
|
||||
background-color: #4CAF50;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h1>服务已经运行!!!</h1>
|
||||
<button onclick="pywebview.api.close_window()">关闭程序</button>
|
||||
</div>
|
||||
</body>
|
||||
"""
|
||||
|
||||
Router: dict[Any] = {
|
||||
"frontend": {
|
||||
"title": f"{_base_name}-前端",
|
||||
"url": f"{_base_url}/home",
|
||||
"html": "",
|
||||
},
|
||||
"backend": {
|
||||
"title": f"{_base_name}-后端",
|
||||
"url": f"{_base_url}/docs",
|
||||
"html": "",
|
||||
},
|
||||
"index": {
|
||||
"title": f"{_base_name}-{_version}",
|
||||
"url": "",
|
||||
"html": __html,
|
||||
},
|
||||
}
|
||||
|
||||
def get_config(self, key: str, view: Optional[Window]) -> Any:
|
||||
"""获取配置信息
|
||||
|
||||
参数:
|
||||
- `key`: 需要获取的键
|
||||
- `view`: 视图(不存在则获取本地配置)
|
||||
|
||||
示例:
|
||||
```python
|
||||
# 默认视图
|
||||
self.get_config('window.screen.width', default_view)
|
||||
# 配置信息
|
||||
self.get_config('version')
|
||||
```
|
||||
"""
|
||||
|
||||
if view is not None:
|
||||
return view.evaluate_js(key)
|
||||
else:
|
||||
...
|
||||
|
||||
def auto_resize(
|
||||
self,
|
||||
width: Optional[int] = None,
|
||||
height: Optional[int] = None,
|
||||
view: Optional[Window] = None,
|
||||
):
|
||||
"""自适应窗口大小
|
||||
|
||||
参数:
|
||||
- `view`: 视图
|
||||
- `width`: 宽度
|
||||
- `height`: 高度
|
||||
|
||||
示例:
|
||||
```python
|
||||
self.auto_resize(view)
|
||||
```
|
||||
"""
|
||||
if view is None:
|
||||
view = self._default_view
|
||||
|
||||
if isinstance(view, Window):
|
||||
if width is None and height is None:
|
||||
screens = self._web.screens[0]
|
||||
width = int(screens.width * 0.85)
|
||||
height = int(screens.height * 0.85)
|
||||
view.resize(width, height)
|
||||
else:
|
||||
self.log.error("Window is not found")
|
||||
|
||||
def close_window(self, window: Window):
|
||||
"""
|
||||
关闭窗口
|
||||
"""
|
||||
self._default_view.destroy()
|
||||
|
||||
|
||||
class MenuModel(BaseModel):
|
||||
"""
|
||||
菜单数据模型
|
||||
"""
|
||||
title: Optional[str]
|
||||
url: Optional[str]
|
||||
html: Optional[str]
|
||||
|
||||
|
||||
class Handler(Config):
|
||||
|
||||
def add_menu(self, key: str, view: list[Any]):
|
||||
"""
|
||||
增加菜单
|
||||
"""
|
||||
try:
|
||||
self._menu_items.insert(0, wm.Menu(key, view))
|
||||
except Exception as err:
|
||||
self.log.error(err)
|
||||
|
||||
def frontend(self):
|
||||
"""
|
||||
前端
|
||||
"""
|
||||
return self.render(MenuModel(**self.Router['frontend']))
|
||||
|
||||
def backend(self):
|
||||
"""
|
||||
后端
|
||||
"""
|
||||
return self.render(MenuModel(**self.Router['backend']))
|
||||
|
||||
def index(self):
|
||||
"""
|
||||
主页
|
||||
"""
|
||||
return self.render(MenuModel(**self.Router['index']))
|
||||
|
||||
def render(self, info: MenuModel):
|
||||
"""渲染页面
|
||||
|
||||
参数:
|
||||
- `info`: 数据模型或者空类型
|
||||
|
||||
示例:
|
||||
```python
|
||||
def index(self):
|
||||
return self.render(info=self.Router['index'])
|
||||
```
|
||||
"""
|
||||
self.auto_resize()
|
||||
self._default_view.title = info.title
|
||||
|
||||
if info.html != "":
|
||||
self._default_view.load_html(info.html)
|
||||
else:
|
||||
self._default_view.load_url(info.url)
|
||||
|
||||
def start_webview(self):
|
||||
"""
|
||||
启动 `WebView` 程序
|
||||
"""
|
||||
self._default_view = self._web.create_window(
|
||||
self.Router['index']['title'],
|
||||
html=self.Router['index']['html'],
|
||||
# confirm_close=True,
|
||||
)
|
||||
self._default_view.expose(self.close_window)
|
||||
self._web.start(menu=self._menu_items)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = Handler()
|
||||
app.add_menu(
|
||||
'服务选择',
|
||||
[
|
||||
wm.MenuAction('前端', app.frontend),
|
||||
wm.MenuSeparator(),
|
||||
wm.MenuAction("后端", app.backend),
|
||||
],
|
||||
)
|
||||
app.add_menu(
|
||||
'主页',
|
||||
[
|
||||
wm.MenuAction('运行状况', app.index),
|
||||
wm.MenuSeparator(),
|
||||
wm.MenuAction('自适应窗口', app.auto_resize),
|
||||
],
|
||||
)
|
||||
app.start_webview()
|