Browse Source

all

yangrongze
hanyuqing 3 months ago
parent
commit
f4db29238d
  1. 1
      controller/__init__.py
  2. BIN
      vue/src/assets/blue.png
  3. 1
      vue/src/assets/close.svg
  4. 1
      vue/src/assets/word.svg
  5. BIN
      vue/src/assets/下拉.png
  6. BIN
      vue/src/assets/缩小.png
  7. 198
      vue/src/system/GraphBuilder.vue
  8. 49
      vue/src/system/GraphDemo.vue
  9. 2
      web_main.py

1
controller/__init__.py

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

BIN
vue/src/assets/blue.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

1
vue/src/assets/close.svg

@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1766738932137" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4330" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M816.872727 768c13.963636 13.963636 13.963636 34.909091 0 48.872727-6.981818 6.981818-16.290909 9.309091-25.6 9.309091s-18.618182-2.327273-25.6-9.309091l-256-256-256 256c-6.981818 6.981818-16.290909 9.309091-25.6 9.309091s-18.618182-2.327273-25.6-9.309091c-13.963636-13.963636-13.963636-34.909091 0-48.872727l256-256-256-256c-13.963636-13.963636-13.963636-34.909091 0-48.872727 13.963636-13.963636 34.909091-13.963636 48.872728 0l256 256 256-256c13.963636-13.963636 34.909091-13.963636 48.872727 0 13.963636 13.963636 13.963636 34.909091 0 48.872727l-256 256 260.654545 256z" fill="#707070" p-id="4331"></path></svg>

After

Width:  |  Height:  |  Size: 949 B

1
vue/src/assets/word.svg

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

BIN
vue/src/assets/下拉.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
vue/src/assets/缩小.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

198
vue/src/system/GraphBuilder.vue

@ -12,30 +12,30 @@
:key="index"
:class="['message', msg.role]"
>
<div>2025</div>
<div class="time">{{ msg.time }}</div>
<div v-if="msg.role === 'user'" class="bubble">
{{ msg.content }}
</div>
<div v-else-if="msg.role === 'assistant'">
<div v-if="msg.isKG" class="kg-card">
<div v-if="msg.content.entities?.length" class="kg-section">
<div v-if="msg.entities?.length" class="kg-section">
<h5 style="text-align: left">识别出的实体</h5>
<div class="entity-list">
<span
v-for="(ent, i) in msg.content.entities"
:key="i"
class="entity-tag"
:class="'tag-' + ent.t"
>
v-for="(ent, i) in msg.entities"
:key="i"
class="entity-tag"
:class="'tag-' + ent.t"
>
{{ ent.n }}<small>({{ ent.t }})</small>
</span>
</div>
</div>
<div v-if="msg.content.relations?.length" class="kg-section">
<div v-if="msg.relations?.length" class="kg-section">
<h5 style="text-align: left">识别出的关系</h5>
<ul class="relation-list">
<li v-for="(rel, i) in msg.content.relations" :key="i">
<li v-for="(rel, i) in msg.relations" :key="i">
<span class="rel-subject">{{ rel.e1 }}</span>
<span class="rel-predicate"> {{ rel.r }} </span>
<span class="rel-object">{{ rel.e2 }}</span>
@ -43,24 +43,40 @@
</ul>
</div>
<div v-if="!msg.content.entities?.length && !msg.content.relations?.length" class="empty-state">
未提取到有效医学实体或关系
</div>
<div v-if="!msg.entities?.length && !msg.relations?.length" style="font-size: 12px;
text-align: left;" >
未提取到有效医学实体或关系</div>
</div>
<div v-else class="bubble assistant-text">
{{ msg.content }}
</div>
</div>
</div>
</div>
<!-- 输入区域Qwen 风格 -->
<div class="input-area">
<div class="word" v-if="showFile">
<img src="../assets/word.svg" style="width: 28px;margin-right: 5px; flex-shrink: 0; flex-grow: 0;">
<div style="flex: 1; overflow: auto;">
<div class="wordName" style="text-align: left;margin-bottom: 1px;font-size: 11px;line-height: 14px;">{{file.fileName}}</div>
<div style=" align-items: center;height: 18px;font-size: 10px; line-height: 12px;display: flex;">{{file.size}}</div>
</div>
<img
src="../assets/close.svg"
alt="close"
class="close-btn"
@click="removeFile"
/>
</div>
<textarea
v-model="inputText"
placeholder="有什么可以帮助您?"
class="input-box"
@keyup.enter.exact.prevent="sendMessage"
rows="1"
:rows="rows"
></textarea>
@ -71,19 +87,35 @@
src="../assets/upload.png"
alt="用户头像"
class="avatar"
@click="upload"
style="margin-left: 10px;cursor:pointer;border-radius: 50%;width: 30px;box-shadow: rgb(0 0 0 / 18%) 0px 2px 8px; "
>
<div style="display: flex;align-items: center;">
<img
<img v-if="inputStatus"
src="../assets/放大.png"
alt="用户头像"
class="avatar"
style="margin-left: 10px;cursor:pointer;width: 15px;margin-right: 26px "
@click="adjustHeight(false)"
>
<img v-if="!inputStatus"
src="../assets/缩小.png"
alt="用户头像"
class="avatar"
style="margin-left: 10px;cursor:pointer;width: 15px;margin-right: 26px "
@click="adjustHeight(true)"
>
<button class="send-btn" @click="sendMessage" title="发送">
发送
</button>
<input
ref="fileInput"
type="file"
style="display: none;"
@change="handleFileChange"
/>
</div>
</div>
@ -108,10 +140,58 @@ export default {
messages: [
{ role: 'assistant', content: '你好!我是图谱构建助手,有什么可以帮你的吗?',isKG:false }
],
status:"wait"
status:"wait",
rows:3,
inputStatus:true,
fileName:"",
file:{},
showFile:false
};
},
methods: {
removeFile(){
this.file={}
this.showFile=false
},
upload(){
this.$refs.fileInput.click(); //
},
handleFileChange(event) {
const file = event.target.files[0]; //
if (file) {
this.fileName = file.name; //
const sizeInBytes = file.size; //
let fileSize;
if (sizeInBytes < 1024 * 1024) {
// 1MB KB
const sizeInKB = sizeInBytes / 1024; // KB
fileSize = `${sizeInKB.toFixed(2)} KB`;
} else {
// 1MB MB
const sizeInMB = sizeInBytes / (1024 * 1024); // MB
fileSize = `${sizeInMB.toFixed(2)} MB`;
}
this.file = {
fileName: file.name,
size: fileSize // KB MB
};
this.showFile = true;
console.log("文件信息:", this.file); //
}
},
adjustHeight(status){
if(status){
this.rows=3
this.inputStatus=true
}else{
this.rows=10
this.inputStatus=false
}
},
pollAIStatus() {
if (this.status !== "wait") return; //
try {
@ -129,6 +209,21 @@ export default {
alert("查询状态失败,请重试");
}
},
getNowDate(){
const currentDate = new Date();
//
const year = currentDate.getFullYear();
const month = String(currentDate.getMonth() + 1).padStart(2, '0'); // 01
const day = String(currentDate.getDate()).padStart(2, '0');
const hours = String(currentDate.getHours()).padStart(2, '0');
const minutes = String(currentDate.getMinutes()).padStart(2, '0');
const seconds = String(currentDate.getSeconds()).padStart(2, '0');
// YYYY-MM-DD HH:mm:ss
const formattedTime = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
return formattedTime;
},
sendMessage() {
const text = this.inputText.trim();
if (!text) return;
@ -137,15 +232,18 @@ export default {
}
// this.pollAIStatus()
//
let message={ role: 'user', content: text }
let message={ role: 'user', content: text,time:this.getNowDate() }
this.messages.push(message);
this.inputText = '';
analyze(data).then(res=>{
console.log(res)
let message={ role: 'assistant',
content: res,entities:res.entities,relations:res.relations,isKG:true }
content: res,entities:res.entities,relations:res.relations,isKG:true,time:this.getNowDate() }
this.messages.push(message);
this.scrollToBottom()
})
this.scrollToBottom()
// qaAnalyze(data).then(res=>{
//
// })
@ -161,11 +259,18 @@ export default {
scrollToBottom() {
this.$nextTick(() => {
const container = this.$refs.messagesContainer;
console.log(container)
if (container) {
container.scrollTop = container.scrollHeight;
// 使 setTimeout DOM
setTimeout(() => {
container.scrollTop = container.scrollHeight;
console.log(container.scrollTop)
console.log(container.scrollHeight)
}, 100); // 100ms
}
});
},
saveMessage(message) {
const messages = JSON.parse(localStorage.getItem('messages')) || [];
messages.push(message); //
@ -224,14 +329,16 @@ export default {
.message {
display: flex;
margin-bottom: 12px;
align-items: flex-start;
flex-direction: column; /* 垂直排列时间和内容 */
}
.message.user {
justify-content: flex-end;
align-items: flex-end;
}
.message.assistant {
justify-content: flex-start;
align-items: flex-start;
}
.bubble {
@ -267,7 +374,6 @@ export default {
font-weight: 500;
}
.input-box {
height: 75px;
width: 100%;
padding: 12px 16px;
border: none;
@ -454,4 +560,54 @@ export default {
background-color: #e9ecef;
color: #333;
}
.message.assistant .time{
text-align: left;
color: #A5A5A5;
margin-bottom: 5px;
font-size: 12px;
}
.message.user .time{
text-align: right;
color: #A5A5A5;
margin-bottom: 5px;
font-size: 12px;
}
.word{
border: 1px solid rgba(17,17,51,.1);
border-radius: 8px;
justify-content: flex-start;
align-items: center;
width: 24%;
min-width: 149px;
max-width: 206px;
padding: 9px 6px;
display: flex;
margin-left: 10px;
position: relative;
}
.wordName{
overflow: hidden; /* 隐藏超出部分 */
display: -webkit-box; /* 使用 webkit 的盒子模型 */
-webkit-box-orient: vertical; /* 设置为垂直方向 */
-webkit-line-clamp: 1; /* 限制为两行 */
text-overflow: ellipsis; /* 显示省略号 */
}
.word:hover .close-btn {
display: block; /* 悬浮时显示X按钮 */
}
.close-btn{
position: absolute;
width: 20px; /* 设置适合的图片大小 */
height: 20px;
cursor: pointer;
padding: 5px;
box-sizing: border-box;
top: -8px;
right: -7px;
border: 1px solid rgba(17,17,51,.1);
border-radius: 50%;
background-color: #fff;
display: none;
}
</style>

49
vue/src/system/GraphDemo.vue

@ -28,7 +28,11 @@
<section class="main-content">
<div class="disease-container">
<div class="disease-header" :style="headerStyle">
<div class="d-title"><img :src="iconSrc" class="d-icon"/><span>疾病信息</span></div>
<div class="d-title"><img :src="iconSrc" class="d-icon"/>
<span v-if="typeRadio=== 'Disease'">疾病信息</span>
<span v-if="typeRadio=== 'Drug'">药品信息</span>
<span v-if="typeRadio=== 'Check'">检查信息</span>
</div>
<div class="d-count" :style="headerLabel">12</div>
</div>
<div>
@ -132,10 +136,10 @@ export default {
diseaseTree:[],
checkTree:[],
legendItems: [
{ key: 'Drug', label: '药品', color: '#91cc75' },
{ key: 'Symptom', label: '症状', color: '#fac858' },
{ key: 'Disease', label: '疾病', color: '#EF4444' },
{ key: 'Drug', label: '药品', color: '#91cc75' },
{ key: 'Check', label: '检查', color: '#336eee' },
{ key: 'Symptom', label: '症状', color: '#fac858' },
{ key: 'Other', label: '其他', color: '#59d1d4' }
],
visibleCategories: new Set(), //
@ -169,9 +173,9 @@ export default {
case 'Drug':
return require('@/assets/green.png'); //
case 'Check':
return require('@/assets/red.png'); //
return require('@/assets/blue.png'); //
default:
return require('@/assets/red.png'); //
return require('@/assets/blue.png'); //
}
},
headerLabel() {
@ -1099,31 +1103,34 @@ button:hover {
/* 动态给每个 el-radio 设置不同的背景颜色 */
.radio-disease .el-radio__label {
color: #ff4d4f; /* 疾病的颜色 */
/deep/ .radio-disease .el-radio__input.is-checked .el-radio__inner {
background: rgb(153, 10, 0); /* 疾病的颜色 */
border-color: rgb(153, 10, 0);
}
.radio-drug .el-radio__label {
color: #1890ff; /* 药品的颜色 */
.el-radio__inner:hover {
border-color: rgb(153, 10, 0);
}
.radio-check .el-radio__label {
color: #52c41a; /* 检查的颜色 */
/deep/ .radio-drug .el-radio__input.is-checked .el-radio__inner {
background: #52c41a; /* 检查的颜色 */
border-color:#52c41a;
}
/* 自定义选中后的样式 */
.el-radio.is-checked.radio-disease .el-radio__label {
color: white;
background-color: #ff4d4f;
/deep/ .radio-check .el-radio__input.is-checked .el-radio__inner {
background: #1890ff; /* 药品的颜色 */
border-color:#1890ff;
}
.el-radio.is-checked.radio-drug .el-radio__label {
color: white;
background-color: #1890ff;
/* 自定义选中后的样式 */
/deep/ .radio-disease .el-radio__input.is-checked+.el-radio__label {
color: rgb(153, 10, 0);
}
.el-radio.is-checked.radio-check .el-radio__label {
color: white;
background-color: #52c41a;
/deep/ .radio-drug .el-radio__input.is-checked+.el-radio__label {
color: #52c41a;
}
/deep/ .radio-check .el-radio__input.is-checked+.el-radio__label {
color: #1890ff;
}
</style>

2
web_main.py

@ -1,3 +1,5 @@
from robyn import ALLOW_CORS
from app import app
import controller
from service.UserService import init_mysql_connection

Loading…
Cancel
Save