@@ -6,6 +6,7 @@ import { api, clearToken, getToken, saveToken } from "./services/api";
import type {
AdminProfile ,
AdminUser ,
AgentDebugResult ,
AiLogRecord ,
ChatDetail ,
ChatRecord ,
@@ -33,6 +34,10 @@ const chatDetail = ref<ChatDetail | null>(null);
const chatDetailOpen = ref ( false ) ;
const recordTab = ref ( "chats" ) ;
const editingModelId = ref < number | null > ( null ) ;
const agentDebugging = ref ( false ) ;
const agentPreviewMessages = ref < { role : "user" | "assistant" | "system" ; content : string } [ ] > ( [
{ role : "assistant" , content : "选择模型和知识库后,可以在这里调试 Agent 的真实问答效果。" } ,
] ) ;
const knowledgeForm = reactive ( {
name : "" ,
@@ -52,12 +57,12 @@ const modelForm = reactive({
apiKey : "" ,
authType : "bearer" ,
apiVersion : "" ,
temperature : 0.2 as number | null ,
temperature : null as number | null ,
topP : null as number | null ,
topK : null as number | null ,
presencePenalty : null as number | null ,
frequencyPenalty : null as number | null ,
maxToken : 1024 ,
maxToken : null as number | null ,
contextWindow : null as number | null ,
streamEnabled : 1 ,
responseFormat : "" ,
@@ -80,10 +85,10 @@ const quickModelProviders = [
baseUrl : "https://api.deepseek.com" ,
authType : "bearer" ,
apiVersion : "" ,
temperature : 0.2 ,
temperature : null ,
topP : null ,
maxToken : 1024 ,
contextWindow : 128000 ,
maxToken : null ,
contextWindow : null ,
models : [
{ label : "deepseek-v4-pro" , value : "deepseek-v4-pro" } ,
{ label : "deepseek-v4-flash" , value : "deepseek-v4-flash" } ,
@@ -98,8 +103,8 @@ const quickModelProviders = [
apiVersion : "" ,
temperature : null ,
topP : null ,
maxToken : 1024 ,
contextWindow : 1000000 ,
maxToken : null ,
contextWindow : null ,
models : [
{ label : "MiniMax-M3" , value : "MiniMax-M3" } ,
{ label : "MiniMax-M2.7" , value : "MiniMax-M2.7" } ,
@@ -161,6 +166,18 @@ const chatFilters = reactive({
dateRange : [ ] as string [ ] ,
} ) ;
const agentForm = reactive ( {
modelId : undefined as number | undefined ,
knowledgeIds : [ ] as number [ ] ,
temperature : 0.2 as number | null ,
topP : null as number | null ,
topK : null as number | null ,
presencePenalty : null as number | null ,
frequencyPenalty : null as number | null ,
maxToken : 1024 as number | null ,
question : "" ,
} ) ;
onMounted ( async ( ) => {
if ( ! getToken ( ) ) return ;
try {
@@ -201,7 +218,15 @@ async function loadCurrentMenu() {
if ( activeMenu . value === "dashboard" ) stats . value = await api . dashboard ( ) ;
if ( activeMenu . value === "users" ) users . value = await api . users ( userKeyword . value ) ;
if ( activeMenu . value === "knowledge" ) knowledge . value = await api . knowledge ( ) ;
if ( activeMenu . value === "prompt" ) promptContent . value = ( await api . prompt ( ) ) . promptContent ;
if ( activeMenu . value === "prompt" ) {
const [ prompt , modelRows , knowledgeRows ] = await Promise . all ( [ api . prompt ( ) , api . models ( ) , api . knowledge ( ) ] ) ;
promptContent . value = prompt . promptContent ;
models . value = modelRows ;
knowledge . value = knowledgeRows ;
if ( ! agentForm . modelId && modelRows . length ) {
agentForm . modelId = modelRows . find ( ( model ) => model . enabled === 1 ) ? . id ? ? modelRows [ 0 ] . id ;
}
}
if ( activeMenu . value === "models" ) models . value = await api . models ( ) ;
if ( activeMenu . value === "configs" ) configs . value = await api . configs ( ) ;
if ( activeMenu . value === "records" ) {
@@ -260,6 +285,18 @@ function buildModelPayload() {
payload [ field ] = null ;
}
} ) ;
Object . assign ( payload , {
temperature : null ,
topP : null ,
topK : null ,
presencePenalty : null ,
frequencyPenalty : null ,
maxToken : null ,
contextWindow : null ,
streamEnabled : 1 ,
responseFormat : null ,
extraParams : null ,
} ) ;
return payload ;
}
@@ -281,13 +318,13 @@ async function quickAddModel() {
apiKey : quickModelForm . apiKey ,
authType : provider . authType ,
apiVersion : provider . apiVersion ,
temperature : provider . temperature ,
topP : provider . topP ,
temperature : null ,
topP : null ,
topK : null ,
presencePenalty : null ,
frequencyPenalty : null ,
maxToken : provider . maxToken ,
contextWindow : provider . contextWindow ,
maxToken : null ,
contextWindow : null ,
streamEnabled : 1 ,
responseFormat : null ,
extraParams : null ,
@@ -332,7 +369,7 @@ function applyModelPreset(label: string) {
{
apiVersion : "" ,
authType : "bearer" ,
temperature : 0.2 ,
temperature : null ,
topP : null ,
topK : null ,
presencePenalty : null ,
@@ -356,13 +393,13 @@ function editModel(row: ModelItem) {
apiKey : "" ,
authType : row . authType ,
apiVersion : row . apiVersion ? ? "" ,
temperature : row . temperature ? ? null ,
topP : row . topP ? ? null ,
topK : row . topK ? ? null ,
presencePenalty : row . presencePenalty ? ? null ,
frequencyPenalty : row . frequencyPenalty ? ? null ,
maxToken : row . maxToken ? ? 1024 ,
contextWindow : row . contextWindow ? ? null ,
temperature : null ,
topP : null ,
topK : null ,
presencePenalty : null ,
frequencyPenalty : null ,
maxToken : null ,
contextWindow : null ,
streamEnabled : row . streamEnabled ,
responseFormat : row . responseFormat ? ? "" ,
extraParams : row . extraParams ? ? "" ,
@@ -383,12 +420,12 @@ function resetModelForm() {
apiKey : "" ,
authType : "bearer" ,
apiVersion : "" ,
temperature : 0.2 ,
temperature : null ,
topP : null ,
topK : null ,
presencePenalty : null ,
frequencyPenalty : null ,
maxToken : 1024 ,
maxToken : null ,
contextWindow : null ,
streamEnabled : 1 ,
responseFormat : "" ,
@@ -427,6 +464,52 @@ async function saveConfig() {
await loadCurrentMenu ( ) ;
}
async function debugAgent ( ) {
if ( ! agentForm . modelId ) {
ElMessage . error ( "请选择调试模型" ) ;
return ;
}
if ( ! agentForm . question . trim ( ) ) {
ElMessage . error ( "请输入调试问题" ) ;
return ;
}
agentPreviewMessages . value . push ( { role : "user" , content : agentForm . question . trim ( ) } ) ;
agentDebugging . value = true ;
try {
const result : AgentDebugResult = await api . debugAgent ( {
promptContent : promptContent . value ,
modelId : agentForm . modelId ,
knowledgeIds : agentForm . knowledgeIds ,
question : agentForm . question . trim ( ) ,
temperature : agentForm . temperature ,
topP : agentForm . topP ,
topK : agentForm . topK ,
presencePenalty : agentForm . presencePenalty ,
frequencyPenalty : agentForm . frequencyPenalty ,
maxToken : agentForm . maxToken ,
} ) ;
if ( ! result . ok ) {
ElMessage . error ( result . message ) ;
agentPreviewMessages . value . push ( { role : "assistant" , content : result . message } ) ;
return ;
}
agentPreviewMessages . value . push ( { role : "assistant" , content : result . answer } ) ;
ElMessage . success ( result . message ) ;
} catch ( error ) {
const message = error instanceof Error ? error . message : "Agent 调试失败" ;
ElMessage . error ( message ) ;
agentPreviewMessages . value . push ( { role : "assistant" , content : message } ) ;
} finally {
agentDebugging . value = false ;
}
}
function clearAgentPreview ( ) {
agentPreviewMessages . value = [
{ role : "assistant" , content : "预览已清空,可以继续发起新的 Agent 调试。" } ,
] ;
}
async function searchChats ( ) {
chats . value = await api . chats ( buildChatQuery ( ) ) ;
}
@@ -461,7 +544,7 @@ function buildChatQuery() {
< section class = "login-box" >
< div class = "brand" > AI < / div >
< h1 > AI 知识库后台 < / h1 >
< p > 用于维护用户 、 知识库 、 Promp t、 模型和审计记录 。 < / p >
< p > 用于维护用户 、 知识库 、 Agen t、 模型和审计记录 。 < / p >
< el-form label -position = " top " @submit.prevent ="login" >
< el -form -item label = "管理员账号" >
< el-input v-model = "loginForm.username" / >
@@ -480,7 +563,7 @@ function buildChatQuery() {
< button : class = "{ active: activeMenu === 'dashboard' }" @click ="switchMenu('dashboard')" > Dashboard < / button >
< button : class = "{ active: activeMenu === 'users' }" @click ="switchMenu('users')" > 用户管理 < / button >
< button : class = "{ active: activeMenu === 'knowledge' }" @click ="switchMenu('knowledge')" > 知识库管理 < / button >
< button : class = "{ active: activeMenu === 'prompt' }" @click ="switchMenu('prompt')" > Promp t 管理 < / button >
< button : class = "{ active: activeMenu === 'prompt' }" @click ="switchMenu('prompt')" > Agen t 管理 < / button >
< button : class = "{ active: activeMenu === 'models' }" @click ="switchMenu('models')" > 模型管理 < / button >
< button : class = "{ active: activeMenu === 'configs' }" @click ="switchMenu('configs')" > 系统配置 < / button >
< button : class = "{ active: activeMenu === 'records' }" @click ="switchMenu('records')" > 记录审计 < / button >
@@ -549,11 +632,83 @@ function buildChatQuery() {
< template v-if = "activeMenu === 'prompt'" >
< div class = "page-head inline" >
< div > < h2 > Promp t 管理 < / h2 > < p > 保存后会影响后续问答 Prompt 组装 。 < / p > < / div >
< el-button @click ="resetPrompt" > 恢复默认 < / el -button >
< div > < h2 > Agen t 管理 < / h2 > < p > 配置提示词 、 模型 、 知识库和调试参数 , 并在右侧实时预览对话效果 。 < / p > < / div >
< el-button @click ="resetPrompt" > 恢复默认提示词 < / el -button >
< / div >
< el-input v-model = "promptContent" type="textarea" :rows="16" / >
< div class = "actions" > < el-button type = "primary" @click ="savePrompt" > 保存 Prompt < / el -button > < / div >
< section class = "agent-workbench" >
< div class = "agent-config-panel" >
< el-form label -position = " top " >
< el-form-item label = "Agent 提示词" >
< el-input v-model = "promptContent" type="textarea" :rows="10" / >
< / el-form-item >
< div class = "agent-config-grid" >
< el-form-item label = "调试模型" >
< el-select v-model = "agentForm.modelId" placeholder="选择已接入模型" >
< el -option
v-for = "model in models"
:key = "model.id"
: label = "model.displayName || model.modelName"
:value = "model.id"
/ >
< / el-select >
< / el-form-item >
< el-form-item label = "调试知识库" >
< el-select v-model = "agentForm.knowledgeIds" multiple collapse-tags placeholder="默认使用全部启用知识库" >
< el -option v-for = "item in knowledge" :key="item.id" :label="item.name" :value="item.id" / >
< / el-select >
< / el-form-item >
< el-form-item label = "Temperature" >
< el-input-number v-model = "agentForm.temperature" :min="0" :max="2" :step="0.1" / >
< / el-form-item >
< el-form-item label = "Top P" >
< el-input-number v-model = "agentForm.topP" :min="0" :max="1" :step="0.05" / >
< / el-form-item >
< el-form-item label = "Top K" >
< el-input-number v-model = "agentForm.topK" :min="1" :max="1000" / >
< / el-form-item >
< el-form-item label = "Max Tokens" >
< el-input-number v-model = "agentForm.maxToken" :min="1" :max="100000" / >
< / el-form-item >
< el-form-item label = "Presence Penalty" >
< el-input-number v-model = "agentForm.presencePenalty" :min="-2" :max="2" :step="0.1" / >
< / el-form-item >
< el-form-item label = "Frequency Penalty" >
< el-input-number v-model = "agentForm.frequencyPenalty" :min="-2" :max="2" :step="0.1" / >
< / el-form-item >
< / div >
< el-form-item label = "调试问题" >
< el-input
v-model = "agentForm.question"
type = "textarea"
:rows = "3"
placeholder = "输入一个真实用户可能会问的问题"
/ >
< / el-form-item >
< div class = "actions" >
< el-button type = "primary" @click ="savePrompt" > 保存 Agent 提示词 < / el -button >
< el-button type = "primary" :loading = "agentDebugging" @click ="debugAgent" > 发送调试 < / el -button >
< el-button @click ="clearAgentPreview" > 清空预览 < / el -button >
< / div >
< / el-form >
< / div >
< aside class = "agent-preview-panel" >
< div class = "agent-preview-head" >
< h3 > 调试预览 < / h3 >
< span > { { models . find ( ( model ) => model . id === agentForm . modelId ) ? . modelName || "未选择模型" } } < / span >
< / div >
< div class = "agent-preview-chat" >
< article
v-for = "(message, index) in agentPreviewMessages"
:key = "`${message.role}-${index}`"
class = "agent-preview-message"
:class = "message.role"
>
< strong > { { message . role === "user" ? "用户" : "Agent" } } < / strong >
< pre > { { message . content } } < / pre >
< / article >
< / div >
< / aside >
< / section >
< / template >
< template v-if = "activeMenu === 'models'" >
@@ -639,48 +794,10 @@ function buildChatQuery() {
< el-form-item label = "API Version" >
< el-input v-model = "modelForm.apiVersion" placeholder="Anthropic 默认 2023-06-01" / >
< / el-form-item >
< el-form-item label = "Temperature" >
< el-input-number v-model = "modelForm.temperature" :min="0" :max="2" :step="0.1" / >
< / el-form-item >
< el-form-item label = "Top P" >
< el-input-number v-model = "modelForm.topP" :min="0" :max="1" :step="0.05" / >
< / el-form-item >
< el-form-item label = "Top K" >
< el-input-number v-model = "modelForm.topK" :min="1" :max="1000" / >
< / el-form-item >
< el-form-item label = "Max Tokens" >
< el-input-number v-model = "modelForm.maxToken" :min="1" :max="100000" / >
< / el-form-item >
< el-form-item label = "上下文窗口" >
< el-input-number v-model = "modelForm.contextWindow" :min="1" :max="2000000" / >
< / el-form-item >
< el-form-item label = "Presence Penalty" >
< el-input-number v-model = "modelForm.presencePenalty" :min="-2" :max="2" :step="0.1" / >
< / el-form-item >
< el-form-item label = "Frequency Penalty" >
< el-input-number v-model = "modelForm.frequencyPenalty" :min="-2" :max="2" :step="0.1" / >
< / el-form-item >
< el-form-item label = "流式输出" >
< el-switch v-model = "modelForm.streamEnabled" :active-value="1" :inactive-value="0" / >
< / el-form-item >
< el-form-item label = "响应格式" >
< el-select v-model = "modelForm.responseFormat" clearable >
< el-option label = "默认文本" value = "" / >
< el-option label = "JSON Object" value = "json_object" / >
< / el-select >
< / el-form-item >
< el-form-item label = "超时秒数" >
< el-input-number v-model = "modelForm.timeoutSecond" :min="1" :max="300" / >
< / el-form-item >
< / div >
< el-form-item label = "高级 JSON 参数" >
< el-input
v-model = "modelForm.extraParams"
type = "textarea"
:rows = "4"
placeholder = '例如:{"stop":["###"],"seed":42}'
/ >
< / el-form-item >
< el-form-item label = "备注" >
< el-input v-model = "modelForm.remark" placeholder="用途、额度、注意事项等" / >
< / el-form-item >
@@ -695,9 +812,7 @@ function buildChatQuery() {
< el-table-column prop = "apiType" label = "协议" width = "170" / >
< el-table-column prop = "modelName" label = "模型" / >
< el-table-column prop = "baseUrl" label = "Base URL" min -width = " 220 " show -overflow -tooltip / >
< el-table-column prop = "temperatur e" label = "温度 " width = "8 0" / >
< el-table-column prop = "topP" label = "Top P" width = "80" / >
< el-table-column prop = "maxToken" label = "Max" width = "90" / >
< el-table-column prop = "authTyp e" label = "鉴权 " width = "11 0" / >
< el-table-column prop = "enabled" label = "启用" width = "90" / >
< el-table-column label = "操作" width = "210" fixed = "right" >
< template # default = "{ row }" >
@@ -740,14 +855,18 @@ function buildChatQuery() {
< / el-select >
< el-date-picker
v-model = "chatFilters.dateRange"
class = "record-date-range"
type = "datetimerange"
unlink -panels
start -placeholder = " 开始时间 "
end -placeholder = " 结束时间 "
value -format = " YYYY -MM -DDTHH : mm : ss "
/ >
< el-button type = "primary" @click ="searchChats" > 搜索 < / el -button >
< el-button @click ="resetChatFilter s"> 重置 < / el -button >
< el-button @click ="exportChat s" > 导出 < / el -button >
< div class = "record-filter-actions" >
< el-button type = "primary" @click ="searchChat s"> 搜索 < / el -button >
< el-button @click ="resetChatFilter s" > 重置 < / el -button >
< el-button @click ="exportChats" > 导出 < / el -button >
< / div >
< / div >
< el-table :data = "chats" stripe >
< el-table-column prop = "id" label = "会话ID" width = "90" / >