30 lines
3.7 KiB
Vue
30 lines
3.7 KiB
Vue
<script setup lang="ts">
|
||
import { computed, ref, watch } from 'vue'
|
||
import { CodeOutlined, SaveOutlined } from '@ant-design/icons-vue'
|
||
import { message } from 'ant-design-vue'
|
||
import { api, apiMessage } from '@/api/client'
|
||
import type { ScenarioField } from '@/types'
|
||
|
||
interface KnowledgeItem { id?:number; key?:string; item_key?:string; name:string; type:string; content:Record<string,unknown>; status:string; sort_order:number }
|
||
interface KnowledgeRelation { from?:string; to?:string; relation_type:string; from_knowledge_id?:number; to_knowledge_id?:number; condition?:Record<string,unknown>; sort_order:number }
|
||
const props = defineProps<{ scenarioId:number; fields:ScenarioField[]; editable:boolean }>()
|
||
const loading=ref(false);const saving=ref(false);const jsonText=ref('{\n "items": [],\n "relations": []\n}')
|
||
const parsed=computed(()=>{try{return JSON.parse(jsonText.value)}catch{return null}})
|
||
async function load(){loading.value=true;try{const data=await api.get<{items:KnowledgeItem[];relations:KnowledgeRelation[]}>(`/scenarios/${props.scenarioId}/knowledge-graph`);const keyById=new Map(data.items.map(item=>[item.id,item.item_key]));jsonText.value=JSON.stringify({items:data.items.map(item=>({key:item.item_key,name:item.name,type:item.type,content:item.content||{},status:item.status,sort_order:item.sort_order})),relations:data.relations.map(rel=>({from:keyById.get(rel.from_knowledge_id),relation_type:rel.relation_type,to:keyById.get(rel.to_knowledge_id),condition:rel.condition||{},sort_order:rel.sort_order}))},null,2)}catch(error){message.error(apiMessage(error))}finally{loading.value=false}}
|
||
watch(()=>props.scenarioId,load,{immediate:true})
|
||
async function save(){if(!parsed.value)return message.warning('知识 JSON 格式不正确');saving.value=true;try{await api.put(`/scenarios/${props.scenarioId}/knowledge-graph`,parsed.value);message.success('场景知识已保存');await load()}catch(error){message.error(apiMessage(error))}finally{saving.value=false}}
|
||
</script>
|
||
|
||
<template>
|
||
<section class="surface knowledge-editor">
|
||
<header><div><span class="icon"><CodeOutlined /></span><div><b>场景知识 JSON</b><small>定义知识实体与关系;知识类型和关系类型均由场景决定</small></div></div><a-button v-if="editable" type="primary" :loading="saving" @click="save"><SaveOutlined />保存知识</a-button></header>
|
||
<a-alert type="info" show-icon message="通用格式:items + relations。宠物场景也支持 symptoms[].diseases[],一个症状可关联多个疾病。" />
|
||
<a-spin :spinning="loading"><a-textarea v-model:value="jsonText" class="json-input" :disabled="!editable" :auto-size="{minRows:22,maxRows:38}" spellcheck="false" /></a-spin>
|
||
<footer><span>{{ parsed ? 'JSON 格式有效' : 'JSON 格式错误' }}</span><span>{{ fields.length }} 个场景输入字段可供规则和话术引用</span></footer>
|
||
</section>
|
||
</template>
|
||
|
||
<style scoped>
|
||
.knowledge-editor{padding:18px}.knowledge-editor header{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-bottom:14px}.knowledge-editor header>div{display:flex;align-items:center;gap:10px}.knowledge-editor header div div{display:flex;flex-direction:column}.knowledge-editor small{margin-top:3px;color:var(--muted)}.icon{width:34px;height:34px;display:grid;place-items:center;color:var(--green);background:#e8f3ef;border-radius:4px}.json-input{margin-top:14px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:12px;line-height:1.6}.knowledge-editor footer{display:flex;justify-content:space-between;margin-top:10px;color:var(--muted);font-size:11px}@media(max-width:700px){.knowledge-editor header{align-items:flex-start;flex-direction:column}.knowledge-editor footer{gap:8px;flex-direction:column}}
|
||
</style>
|