@@ -0,0 +1,36 @@
< script setup lang = "ts" >
import { computed , onMounted , ref } from 'vue'
import { getRetrievalRuns , type RetrievalRunResult } from '../api'
import { exportRetrievalHistory } from '../services/exports'
const runs = ref < RetrievalRunResult [ ] > ( [ ] ) , selected = ref < string [ ] > ( [ ] ) , page = ref ( 1 ) , total = ref ( 0 ) , loading = ref ( true ) , exporting = ref ( false ) , error = ref ( '' ) , notice = ref ( '' ) , includeContexts = ref ( false )
const pageSize = 20
const selectedSet = computed ( ( ) => new Set ( selected . value ) )
function runLabel ( run : RetrievalRunResult ) { return ` ${ new Date ( run . created _at ) . toLocaleString ( ) } · ${ run . mode } · ${ run . query } ` }
function toggle ( runId : string ) { if ( selectedSet . value . has ( runId ) ) { selected . value = selected . value . filter ( id => id !== runId ) ; return } if ( selected . value . length >= 100 ) { error . value = '一次最多选择 100 条检索历史。' ; return } selected . value = [ ... selected . value , runId ] }
async function refresh ( next = page . value ) { loading . value = true ; error . value = '' ; try { const result = await getRetrievalRuns ( next , pageSize ) ; runs . value = result . items ; total . value = result . total ; page . value = result . page } catch ( cause ) { error . value = cause instanceof Error ? cause . message : '无法读取检索历史' } finally { loading . value = false } }
async function download ( ) { error . value = '' ; notice . value = '' ; if ( ! selected . value . length ) { error . value = '请先选择至少一条检索历史。' ; return } exporting . value = true ; try { const file = await exportRetrievalHistory ( { run _ids : selected . value , include _contexts : includeContexts . value } ) ; const url = URL . createObjectURL ( file . blob ) ; const link = document . createElement ( 'a' ) ; link . href = url ; link . download = file . filename ; link . style . display = 'none' ; document . body . append ( link ) ; link . click ( ) ; link . remove ( ) ; window . setTimeout ( ( ) => URL . revokeObjectURL ( url ) , 0 ) ; notice . value = ` 已开始下载 ${ file . filename } ${ file . recordsSha256 ? ` (记录摘要 ${ file . recordsSha256 } ) ` : '' } 。 ` } catch ( cause ) { error . value = cause instanceof Error ? cause . message : '导出失败' } finally { exporting . value = false } }
onMounted ( ( ) => refresh ( 1 ) )
< / script >
< template >
< main >
< RouterLink to = "/admin" > 返回管理员工作台 < / RouterLink >
< h1 > 导出检索历史 < / h1 >
< p > 从已保存的历史快照选择记录 , 导出 JSONL ; 不会重新检索 、 调用模型或导出认证信息 。 < / p >
< p v-if = "error" role="alert">{{error}}</p><p v-if="notice" role="status" > {{ notice }} < / p >
< form @submit.prevent ="download" >
< label > < input v-model = "includeContexts" type="checkbox" /> 同时包含关联的 RAG 准备上下文 < / label >
< button :disabled = "exporting||!selected.length" > { { exporting ? '正在准备…' : ` 导出已选 ${ selected . length } 条 ` } } < / button >
< p > 最多选择 100 条记录 ; 导出上限为 8 MiB 。 文件仅表示当时历史快照 , 并不代表当前知识仍有效 。 < / p >
< / form >
< p v-if = "loading" > 正在读取检索历史 … < / p >
< template v-else >
< table >
< thead > < tr > < th > 选择 < / th > < th > 时间 < / th > < th > 模式 < / th > < th > 查询 < / th > < th > 状态 < / th > < th > 命中数 < / th > < / tr > < / thead >
< tbody > < tr v-for = "run in runs" :key="run.run_id"><td><input :checked="selectedSet.has(run.run_id)" type="checkbox" :aria-label="runLabel(run)" @change="toggle(run.run_id)"/></td><td>{{new Date(run.created_at).toLocaleString()}}</td><td>{{run.mode}}</td><td>{{run.query}}</td><td>{{run.status}}</td><td>{{run.total_hits}}</td></tr><tr v-if="!runs.length"><td colspan="6" > 暂无可导出的检索历史 。 < / td > < / tr > < / tbody >
< / table >
< div > < button :disabled = "page===1" @click ="refresh(page-1)" > 上一页 < / button > < span > 第 { { page } } 页 , 共 { { total } } 条 < / span > < button :disabled = "page*pageSize>=total" @click ="refresh(page+1)" > 下一页 < / button > < / div >
< / template >
< / main >
< / template >