Browse Source

all

yangrongze
hanyuqing 3 months ago
parent
commit
20de262a2c
  1. 10
      controller/BuilderController.py
  2. 2
      controller/LoginController.py
  3. 16
      controller/QAController.py
  4. 2
      controller/__init__.py
  5. 3
      vue/src/App.vue
  6. BIN
      vue/src/assets/upload.png
  7. BIN
      vue/src/assets/放大.png
  8. 57
      vue/src/components/Menu.vue
  9. 133
      vue/src/system/GraphBuilder.vue
  10. 4
      vue/vue.config.js

10
controller/BuilderController.py

@ -1,9 +1,11 @@
# 全局 client(可复用)
import traceback
import httpx
from robyn import jsonify, Response
from app import app
from controller import client
from controller.client import client
@app.post("/api/analyze")
@ -33,4 +35,8 @@ async def analyze(request):
"detail": resp.text
}), resp.status_code
except Exception as e:
return jsonify({"error": str(e)}), 500
error_trace = traceback.format_exc()
print("❌ 发生异常:")
print(error_trace)
return jsonify({"error": str(e), "traceback": error_trace}), 500

2
controller/LoginController.py

@ -1,3 +1,5 @@
import os
from robyn import jsonify, Response, Request
from app import app
from datetime import datetime, timedelta

16
controller/QAController.py

@ -28,16 +28,26 @@ async def analyze(request):
resp_json_data = json.loads(resp_json_data)
entities = resp_json_data.get("entities", [])
print(entities)
data = []
for name in entities:
Neo4jUtil.find_neighbors_with_relationships(
neighbors =neo4j_client.find_neighbors_with_relationshipsAI(
node_label=None,
direction="both",
node_properties={"name": name},
rel_type=None
)
data.append({
name:neighbors
})
resp = await client.post(
"/question_agent",
json={"neo4j_data": [],
"text": input_text},
timeout=1800.0 # 30分钟
)
return Response(
status_code=200,
description=jsonify(entities),
description=jsonify(resp.json()),
headers={"Content-Type": "text/plain; charset=utf-8"}
)
else:

2
controller/__init__.py

@ -5,7 +5,7 @@ from .BuilderController import *
from .GraphController import *
from .LoginController import *
from .QAController import *
from .RegisterController import *
# 可选:如果控制器里定义了 blueprint,也可以在这里统一导出
# from .BuilderController import builder_bp
# from .GraphController import graph_bp

3
vue/src/App.vue

@ -14,6 +14,7 @@ export default {
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
font-family: 'Noto Serif SC', "SimSun", "宋体", serif;
//font-family: 'Noto Serif SC', "SimSun", "", serif;
font-family: "Microsoft YaHei", "微软雅黑", sans-serif;
}
</style>

BIN
vue/src/assets/upload.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
vue/src/assets/放大.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 739 B

57
vue/src/components/Menu.vue

@ -45,9 +45,14 @@
'border-top': isCollapsed ? 'none' : '2px solid rgba(255, 255, 255, 0.1)'
}">
<div v-if="!isCollapsed" class="user-block">
<div class="avatar" @click="handleProfile">用户</div>
<img
:src="userProfile.avatar"
alt="用户头像"
class="avatar"
@click="handleProfile"
>
<div class="info">
<div class="name" @click="handleProfile">用户名字</div>
<div class="name" @click="handleProfile">{{ userProfile.username }}</div>
<div class="id">8866990099</div>
</div>
<div class="exit-wrap">
@ -65,12 +70,13 @@
</template>
<script setup>
import {ref} from 'vue';
import {onMounted, ref} from 'vue';
import {useRouter} from 'vue-router';
import i1 from '@/assets/图标1.png';
import i2 from '@/assets/图标2.png';
import i3 from '@/assets/图标3.png';
import i4 from '@/assets/图标4.png';
import {getUserProfile} from "@/api/profile";
const router = useRouter();
// const activeIndex = ref(0);
@ -86,7 +92,10 @@ const props = defineProps({
//
const emit = defineEmits(['menu-click']);
const userProfile = ref({
username: '用户',
avatar: '/resource/avatar/4.png'
});
//
const activeIndex = ref(props.initialActive);
@ -110,9 +119,46 @@ const handleProfile = () => {
router.push('/profile');
};
const handleLogout = () => {
localStorage.removeItem('messages');
// 使Vue Router
router.push('/login');
};
onMounted(async () => {
try {
// localStoragetoken
const token = localStorage.getItem('token');
console.log('Profile组件挂载,获取到的token:', token);
if (token) {
// API
const response = await getUserProfile(token);
if (response.success) {
//
//
let avatarUrl = response.user.avatar || '/resource/avatar/4.png';
if (avatarUrl.startsWith('/resource/')) {
avatarUrl = avatarUrl; // 使
}
console.log('设置头像URL:', avatarUrl);
userProfile.value = {
username: response.user.username,
avatar: avatarUrl
};
} else {
// tokentoken
if (response.message && response.message.includes('登录')) {
localStorage.removeItem('token');
}
}
} else {
console.log('用户未登录');
errorMessage.value = '用户未登录,请先登录';
}
} catch (error) {
console.error('获取用户信息失败:', error);
errorMessage.value = '获取用户信息时发生错误';
}
});
</script>
<style scoped>
@ -191,7 +237,6 @@ const handleLogout = () => {
line-height: 1.25;
white-space: nowrap;
font-weight: 600;
font-family: 'Noto Serif SC', "SimSun", "宋体", serif;
}
/* --- 菜单导航 --- */
@ -268,7 +313,6 @@ const handleLogout = () => {
margin-left: 10px;
font-weight: 500;
font-size: 12px;
font-family: 'Noto Serif SC', "SimSun", "宋体", serif;
}
.active-tag {
@ -327,7 +371,6 @@ const handleLogout = () => {
font-size: 11px;
color: #fff;
line-height: 1;
font-family: 'Noto Serif SC', "SimSun", "宋体", serif;
cursor: pointer;
}

133
vue/src/system/GraphBuilder.vue

@ -17,10 +17,8 @@
</div>
<div v-else-if="msg.role === 'assistant'">
<div v-if="msg.isKG" class="kg-card">
<h4>🔬 病历结构化分析结果</h4>
<div v-if="msg.content.entities?.length" class="kg-section">
<h5>识别出的实体</h5>
<h5 style="text-align: left">识别出的实体</h5>
<div class="entity-list">
<span
v-for="(ent, i) in msg.content.entities"
@ -34,7 +32,7 @@
</div>
<div v-if="msg.content.relations?.length" class="kg-section">
<h5>识别出的关系</h5>
<h5 style="text-align: left">识别出的关系</h5>
<ul class="relation-list">
<li v-for="(rel, i) in msg.content.relations" :key="i">
<span class="rel-subject">{{ rel.e1 }}</span>
@ -58,36 +56,23 @@
<div class="input-area">
<textarea
v-model="inputText"
placeholder="向千问提问"
placeholder="有什么可以帮助您?"
class="input-box"
@keyup.enter.exact.prevent="sendMessage"
rows="1"
></textarea>
<div class="action-buttons">
<button class="btn" @click="insertPrefix('深度思考:')">
<span></span> 深度思考
</button>
<button class="btn" @click="insertPrefix('深度研究:')">
<span>🔍</span> 深度研究
</button>
<button class="btn" @click="insertPrefix('```代码\n')">
<span><></span> 代码
</button>
<button class="btn" @click="insertPrefix('生成一张图片:')">
<span>🖼</span> 图像
</button>
<button class="btn" @click="insertPrefix('翻译成英文:')">
<span>🌐</span> 翻译
</button>
<button class="btn more-btn">...</button>
</div>
<img
src="../assets/upload.png"
alt="用户头像"
class="avatar"
style="border-radius: 50%;width: 30px;box-shadow: 0 2px 8px rgba(0,0,0,0.05); "
>
<div class="icon-group">
<span class="icon" title="切换语言">🌍</span>
<span class="icon" title="编辑模式"></span>
<button class="send-btn" @click="sendMessage" title="发送">
<span>📤</span>
发送
</button>
</div>
</div>
@ -140,16 +125,18 @@ export default {
}
// this.pollAIStatus()
//
this.messages.push({ role: 'user', content: text });
let message={ role: 'user', content: text }
this.messages.push(message);
this.inputText = '';
// analyze(data).then(res=>{
// console.log(res)
// this.messages.push({ role: 'assistant',
// content: res,entities:res.entities,relations:res.relations,isKG:true });
// })
qaAnalyze(data).then(res=>{
analyze(data).then(res=>{
console.log(res)
let message={ role: 'assistant',
content: res,entities:res.entities,relations:res.relations,isKG:true }
this.messages.push(message);
})
// qaAnalyze(data).then(res=>{
//
// })
},
insertPrefix(prefix) {
this.inputText += prefix;
@ -166,6 +153,23 @@ export default {
container.scrollTop = container.scrollHeight;
}
});
},
saveMessage(message) {
const messages = JSON.parse(localStorage.getItem('messages')) || [];
messages.push(message); //
localStorage.setItem('messages', JSON.stringify(messages));
},
loadMessages() {
const savedMessages = JSON.parse(localStorage.getItem('messages')) || [];
this.messages = savedMessages; //
},
handleBeforeUnload() {
localStorage.removeItem('messages');
//
console.log("页面刷新或关闭前,保存消息...");
this.messages.forEach((message) => {
this.saveMessage(message);
});
}
},
watch: {
@ -173,9 +177,13 @@ export default {
this.scrollToBottom();
}
},
mounted() {
this.loadMessages();
this.scrollToBottom();
}
window.addEventListener('beforeunload', this.handleBeforeUnload);
},
};
</script>
@ -189,6 +197,7 @@ export default {
border: 1px solid #e0e0e0;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background-color: #F3F3F3;
}
/* === 消息区域 === */
@ -196,7 +205,6 @@ export default {
flex: 1;
padding: 16px;
overflow-y: auto;
background-color: #f9f9f9;
display: flex;
flex-direction: column;
}
@ -220,33 +228,36 @@ export default {
max-width: 75%;
word-break: break-word;
line-height: 1.5;
font-size: 14px;
font-size: 12px;
text-align: left;
}
.message.user .bubble {
background-color: #007bff;
background-color: #155DFF;
color: white;
border-bottom-right-radius: 4px;
}
.message.assistant .bubble {
background-color: #e9ecef;
background-color: #fff;
color: #333;
border-bottom-left-radius: 4px;
}
/* === 输入区域 === */
.input-area {
padding: 16px;
background: white;
border-top: 1px solid #eee;
border-top-left-radius: 24px;
border-top-right-radius: 24px;
box-shadow: 2px -1px 14px 0px rgb(159 160 161 / 22%);
}
.input-box::placeholder {
color: #AFAFAF; /* 设置字体颜色为红色 */
font-weight: 500;
}
.input-box {
width: 100%;
padding: 12px 16px;
border: 1px solid #d9d9d9;
border: none;
border-radius: 12px;
font-size: 14px;
line-height: 1.5;
@ -264,22 +275,23 @@ export default {
display: flex;
gap: 8px;
margin: 12px 0;
overflow-x: auto;
white-space: nowrap;
padding: 0 4px;
}
.btn {
padding: 6px 12px;
background: #f5f5f5;
border: 1px solid #ddd;
border-radius: 12px;
font-size: 12px;
color: #333;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
display: flex; /* 使用flex布局 */
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
width: 20px; /* 设置按钮的宽度为20px */
height: 20px; /* 设置按钮的高度为20px */
border-radius: 50%; /* 圆形按钮 */
background-color: white; /* 背景色为白色 */
font-size: 18px; /* 设置加号的字体大小 */
line-height: 20px; /* 设置行高,确保加号垂直居中 */
color: #333; /* 加号的颜色 */
font-weight: bold; /* 加号字体加粗 */
cursor: pointer; /* 鼠标悬停时显示为点击效果 */
}
.btn:hover {
@ -317,21 +329,21 @@ export default {
}
.send-btn {
background: #9b68ff;
background: #B9CDFF;
border: none;
width: 32px;
height: 32px;
border-radius: 50%;
width: 50px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
color: white;
cursor: pointer;
transition: background-color 0.2s;
font-size: 11px;
}
.send-btn:hover {
background: #8a53e0;
background: #155DFF;
}
/* === 自定义助手 KG 卡片 === */
.kg-card {
@ -428,6 +440,5 @@ export default {
.assistant-text {
background-color: #e9ecef;
color: #333;
border-bottom-left-radius: 4px;
}
</style>

4
vue/vue.config.js

@ -15,6 +15,10 @@ module.exports = defineConfig({
target: 'http://localhost:8088',
changeOrigin: true,
// 不需要 pathRewrite,因为前后都是 /api/xxx
},
'/resource': {
target: 'http://localhost:8088', // 更新为8088端口
changeOrigin: true
}
},
historyApiFallback: {

Loading…
Cancel
Save