@@ -1,9 +1,9 @@
< script setup lang = "ts" >
< script setup lang = "ts" >
import { computed , onMounted , ref , watch } from 'vue'
import { computed , onMounted , ref , watch } from 'vue'
import { Activity , BookOpen , Bot , CheckCircle2 , ChevronRight , Database , FileText , Globe2 , Link2 , MessageSquareText , Plus , Search , Send , ShieldCheck , Sparkles , AlertCircle } from 'lucide-vue-next'
import { Activity , BookOpen , Bot , CheckCircle2 , ChevronRight , Database , FileText , Globe2 , Link2 , MessageSquareText , Plus , Search , Send , ShieldCheck , Sparkles , AlertCircle } from 'lucide-vue-next'
import { createKnowledge , createSource , getHealth , getKnowledge , getSources , retrieveKnowledge , type SourceItem } from './api'
import { createKnowledge , createSource , getHealth , getKnowledge , getSources , retrieveKnowledge , retrieveVectorKnowledge , type SourceItem } from './api'
type Page = 'chat' | 'knowledge' | 'sources' | 'evaluation'
type Page = 'chat' | 'knowledge' | 'sources' | 'evaluation'
const page = ref < Page > ( 'chat' ) , apiStatus = ref ( '检测中' ) , question = ref ( '' ) , busy = ref ( false ) , search = ref ( '' ) , showForm = ref ( false ) , error = ref ( '' ) , notice = ref ( '' )
const page = ref < Page > ( 'chat' ) , apiStatus = ref ( '检测中' ) , question = ref ( '' ) , busy = ref ( false ) , retrievalMode = ref < 'keyword' | 'vector' > ( 'keyword' ) , search = ref ( '' ) , showForm = ref ( false ) , error = ref ( '' ) , notice = ref ( '' )
const messages = ref ( [ { role : 'assistant' , content : '你好!我是边疆红驿文旅问答原型。当前后端仅为基础接口,尚未接入知识检索和大语言模型。' } ] as { role : string ; content : string ; sources ? : { title : string ; source _url ? : string | null } [ ] } [ ] )
const messages = ref ( [ { role : 'assistant' , content : '你好!我是边疆红驿文旅问答原型。当前后端仅为基础接口,尚未接入知识检索和大语言模型。' } ] as { role : string ; content : string ; sources ? : { title : string ; source _url ? : string | null } [ ] } [ ] )
const items = ref < any [ ] > ( [ ] ) , form = ref ( { title : '' , category : '旅游资源' , content : '' , source _url : '' } )
const items = ref < any [ ] > ( [ ] ) , form = ref ( { title : '' , category : '旅游资源' , content : '' , source _url : '' } )
const sources = ref < SourceItem [ ] > ( [ ] ) , sourceSearch = ref ( '' ) , showSourceForm = ref ( false ) , sourceForm = ref ( { name : '' , source _url : '' , notes : '' } )
const sources = ref < SourceItem [ ] > ( [ ] ) , sourceSearch = ref ( '' ) , showSourceForm = ref ( false ) , sourceForm = ref ( { name : '' , source _url : '' , notes : '' } )
@@ -15,7 +15,7 @@ async function saveSource(){error.value='';notice.value='';if(!sourceForm.value.
let sourceSearchTimer : number | undefined
let sourceSearchTimer : number | undefined
watch ( sourceSearch , ( ) => { window . clearTimeout ( sourceSearchTimer ) ; sourceSearchTimer = window . setTimeout ( refreshSources , 260 ) } )
watch ( sourceSearch , ( ) => { window . clearTimeout ( sourceSearchTimer ) ; sourceSearchTimer = window . setTimeout ( refreshSources , 260 ) } )
onMounted ( async ( ) => { try { await getHealth ( ) ; apiStatus . value = '已连接' } catch { apiStatus . value = '未连接' } await Promise . all ( [ refresh ( ) , refreshSources ( ) ] ) } )
onMounted ( async ( ) => { try { await getHealth ( ) ; apiStatus . value = '已连接' } catch { apiStatus . value = '未连接' } await Promise . all ( [ refresh ( ) , refreshSources ( ) ] ) } )
async function send ( ) { const q = question . value . trim ( ) ; if ( ! q || busy . value ) return ; messages . value . push ( { role : 'user' , content : q } ) ; question . value = '' ; busy . value = true ; try { const r = await retrieveKnowledge ( { query : q , top _k : 5 } ) ; const content = r . status === 'no_evidence' ? '未检索到可用的已核验知识依据。请调整问题或补充审核后的资料。' : ` 已检索到 ${ r . total _hits } 条已核验依据( ${ r . elapsed _ms . toFixed ( 1 ) } ms)。当前仅展示检索证据,尚未接入大语言模型生成回答。 ` ; messages . value . push ( { role : 'assistant' , content , sources : r . results . map ( hit => ( { title : ` ${ hit . title } : ${ hit . snippet } ` , source _url : hit . source _url } ) ) } ) } catch ( e ) { messages . value . push ( { role : 'assistant' , content : e instanceof Error ? e . message : '暂时无法连接检索服务。' } ) } finally { busy . value = false } }
async function send ( ) { const q = question . value . trim ( ) ; if ( ! q || busy . value ) return ; messages . value . push ( { role : 'user' , content : q } ) ; question . value = '' ; busy . value = true ; try { const r = retrievalMode . value === 'vector' ? await retrieveVectorKnowledge ( { query : q , top _k : 5 } ) : await retrieveKnowledge ( { query : q , top _k : 5 } ) ; const content = r . status === 'no_evidence' ? '未检索到可用的已核验知识依据。请调整问题或补充审核后的资料。' : ` 已通过 ${ r . mode === 'vector' ? '向量' : '关键词' } 检索命中 ${ r . total _hits } 条已核验依据( ${ r . elapsed _ms . toFixed ( 1 ) } ms)。当前仅展示检索证据,尚未接入大语言模型生成回答。 ` ; messages . value . push ( { role : 'assistant' , content , sources : r . results . map ( hit => ( { title : ` ${ hit . title } : ${ hit . snippet } ` , source _url : hit . source _url } ) ) } ) } catch ( e ) { messages . value . push ( { role : 'assistant' , content : e instanceof Error ? e . message : '暂时无法连接检索服务。' } ) } finally { busy . value = false } }
function validUrl ( value : string ) { try { if ( value ) new URL ( value ) ; return true } catch { return false } }
function validUrl ( value : string ) { try { if ( value ) new URL ( value ) ; return true } catch { return false } }
async function save ( ) { error . value = '' ; notice . value = '' ; if ( ! form . value . title . trim ( ) || ! form . value . content . trim ( ) ) { error . value = '请填写知识标题和正文。' ; return } if ( ! validUrl ( form . value . source _url ) ) { error . value = '来源网址必须是有效的 http(s) 地址。' ; return } try { await createKnowledge ( { ... form . value , title : form . value . title . trim ( ) , content : form . value . content . trim ( ) , ... ( form . value . source _url ? { source _url : form . value . source _url } : { } ) } ) ; form . value = { title : '' , category : '旅游资源' , content : '' , source _url : '' } ; showForm . value = false ; notice . value = '已保存为待审核条目,尚未进入检索索引。' ; await refresh ( ) } catch ( e ) { error . value = e instanceof Error ? e . message : '保存失败' } }
async function save ( ) { error . value = '' ; notice . value = '' ; if ( ! form . value . title . trim ( ) || ! form . value . content . trim ( ) ) { error . value = '请填写知识标题和正文。' ; return } if ( ! validUrl ( form . value . source _url ) ) { error . value = '来源网址必须是有效的 http(s) 地址。' ; return } try { await createKnowledge ( { ... form . value , title : form . value . title . trim ( ) , content : form . value . content . trim ( ) , ... ( form . value . source _url ? { source _url : form . value . source _url } : { } ) } ) ; form . value = { title : '' , category : '旅游资源' , content : '' , source _url : '' } ; showForm . value = false ; notice . value = '已保存为待审核条目,尚未进入检索索引。' ; await refresh ( ) } catch ( e ) { error . value = e instanceof Error ? e . message : '保存失败' } }
< / script >
< / script >