feat: 优化设置和展示逻辑

This commit is contained in:
digua
2026-05-06 20:04:26 +08:00
committed by digua
parent 99b4d3e275
commit f6cc5c8fb8
12 changed files with 160 additions and 163 deletions
+1
View File
@@ -500,6 +500,7 @@ interface ModelDefinition {
providerId: string
name: string
description?: string
contextWindow?: number
capabilities: ModelCapability[]
recommendedFor: ModelRecommendedFor[]
status: ModelStatus
+14 -18
View File
@@ -6,7 +6,7 @@ import { useToast } from '@/composables/useToast'
import { usePromptStore } from '@/stores/prompt'
import { useLayoutStore } from '@/stores/layout'
import { useLLMStore } from '@/stores/llm'
import { exportConversation, type ExportFormat } from '@/utils/conversationExport'
import { exportConversation, type ExportFormat, type ExportMessage } from '@/utils/conversationExport'
import type { AgentRuntimeStatus } from '@electron/shared/types'
const { t } = useI18n()
@@ -86,10 +86,11 @@ const contextTokens = computed(() => {
})
const modelContextWindow = computed(() => {
if (!defaultAssistantConfig.value) return 128000
const model =
llmStore.getModelById(defaultAssistantConfig.value.provider, defaultAssistantConfig.value.model) ||
llmStore.findModelAcrossProviders(defaultAssistantConfig.value.model)
const defaultConfig = defaultAssistantConfig.value
const modelId = defaultConfig?.model
if (!defaultConfig || !modelId) return 128000
const model = llmStore.getModelById(defaultConfig.provider, modelId) || llmStore.findModelAcrossProviders(modelId)
return model?.contextWindow ?? 128000
})
@@ -105,15 +106,6 @@ const contextBarColor = computed(() => {
return 'bg-emerald-500'
})
const contextBarTooltip = computed(() => {
const lines = []
lines.push(
`${t('ai.chat.statusBar.agent.contextTokens')}: ${formatNumber(contextTokens.value)} / ${formatNumber(modelContextWindow.value)} (${contextUsagePercent.value}%)`
)
lines.push(`${t('ai.chat.statusBar.tokenUsageTitle')}: ${totalTokenUsageText.value}`)
return lines.join('\n')
})
const agentCompactTitle = computed(() => {
if (!props.agentStatus) return ''
return [
@@ -168,10 +160,14 @@ async function handleExportConversation() {
user: t('ai.chat.conversation.export.user'),
assistant: t('ai.chat.conversation.export.assistant'),
}
const messagesWithMs = messages.map((msg) => ({
...msg,
timestamp: msg.timestamp * 1000,
}))
// 导出面向用户可见的问答内容,跳过压缩摘要等系统生成的内部消息。
const messagesWithMs: ExportMessage[] = messages
.filter((msg) => msg.role === 'user' || msg.role === 'assistant')
.map((msg) => ({
role: msg.role as ExportMessage['role'],
content: msg.content,
timestamp: msg.timestamp * 1000,
}))
const result = await exportConversation(title, messagesWithMs, conv.createdAt * 1000, format, labels)
@@ -174,9 +174,6 @@ onMounted(() => {
</div>
</div>
<!-- 分隔线 -->
<div class="border-t border-gray-200 dark:border-gray-700" />
<!-- 快速模型 -->
<div>
<div class="mb-2">
@@ -0,0 +1,81 @@
<script setup lang="ts">
import { computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useI18n } from 'vue-i18n'
import { usePromptStore } from '@/stores/prompt'
const { t } = useI18n()
const emit = defineEmits<{
'config-changed': []
}>()
const promptStore = usePromptStore()
const { aiGlobalSettings } = storeToRefs(promptStore)
// 导出格式选项(AI 对话)
const exportFormatTabs = computed(() => [
{ label: 'Markdown', value: 'markdown' },
{ label: t('settings.aiPrompt.exportFormat.txtLabel'), value: 'txt' },
])
// 当前选中的导出格式(AI 对话)
const exportFormat = computed({
get: () => aiGlobalSettings.value.exportFormat ?? 'markdown',
set: (val: string) => {
promptStore.updateAIGlobalSettings({ exportFormat: val as 'markdown' | 'txt' })
emit('config-changed')
},
})
// SQL Lab 导出格式选项
const sqlExportFormatTabs = computed(() => [
{ label: 'CSV', value: 'csv' },
{ label: 'JSON', value: 'json' },
])
// 当前选中的 SQL Lab 导出格式
const sqlExportFormat = computed({
get: () => aiGlobalSettings.value.sqlExportFormat ?? 'csv',
set: (val: string) => {
promptStore.updateAIGlobalSettings({ sqlExportFormat: val as 'csv' | 'json' })
emit('config-changed')
},
})
</script>
<template>
<div>
<h4 class="mb-3 flex items-center gap-2 text-sm font-semibold text-gray-900 dark:text-white">
<UIcon name="i-heroicons-arrow-down-tray" class="h-4 w-4 text-blue-500" />
{{ t('settings.aiPrompt.exportSettings.title') }}
</h4>
<div class="space-y-4 rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-800/50">
<!-- 导出格式AI 对话 -->
<div class="flex items-center justify-between">
<div class="flex-1 pr-4">
<p class="text-sm font-medium text-gray-900 dark:text-white">
{{ t('settings.aiPrompt.exportFormat.title') }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ t('settings.aiPrompt.exportFormat.description') }}
</p>
</div>
<UTabs v-model="exportFormat" :items="exportFormatTabs" size="xs" />
</div>
<!-- SQL Lab 导出格式 -->
<div class="flex items-center justify-between">
<div class="flex-1 pr-4">
<p class="text-sm font-medium text-gray-900 dark:text-white">
{{ t('settings.aiPrompt.sqlExportFormat.title') }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ t('settings.aiPrompt.sqlExportFormat.description') }}
</p>
</div>
<UTabs v-model="sqlExportFormat" :items="sqlExportFormatTabs" size="xs" />
</div>
</div>
</div>
</template>
@@ -112,9 +112,6 @@ function removeCustomRule(ruleId: string) {
<USwitch v-model="aiPreprocessConfig.dataCleaning" />
</div>
<!-- 分隔线 -->
<div class="border-t border-gray-200 dark:border-gray-600" />
<!-- 合并连续发言 -->
<div class="flex items-center justify-between">
<div class="flex-1 pr-4">
@@ -136,9 +133,6 @@ function removeCustomRule(ruleId: string) {
<UInputNumber v-model="aiPreprocessConfig.mergeWindowSeconds" :min="30" :max="600" :step="30" class="w-28" />
</div>
<!-- 分隔线 -->
<div class="border-t border-gray-200 dark:border-gray-600" />
<!-- 智能去噪 -->
<div class="flex items-center justify-between">
<div class="flex-1 pr-4">
@@ -152,9 +146,6 @@ function removeCustomRule(ruleId: string) {
<USwitch v-model="aiPreprocessConfig.denoise" />
</div>
<!-- 分隔线 -->
<div class="border-t border-gray-200 dark:border-gray-600" />
<!-- 昵称匿名化 -->
<div class="flex items-center justify-between">
<div class="flex-1 pr-4">
@@ -168,9 +159,6 @@ function removeCustomRule(ruleId: string) {
<USwitch v-model="aiPreprocessConfig.anonymizeNames" />
</div>
<!-- 分隔线 -->
<div class="border-t border-gray-200 dark:border-gray-600" />
<!-- 数据脱敏 -->
<div class="flex items-center justify-between">
<div class="flex-1 pr-4">
@@ -9,11 +9,19 @@ const { t } = useI18n()
const promptStore = usePromptStore()
const { aiGlobalSettings } = storeToRefs(promptStore)
const props = defineProps<{
setSectionRef?: (id: string, el: HTMLElement | null) => void
}>()
// Emits
const emit = defineEmits<{
'config-changed': []
}>()
function setPromptSectionRef(id: string, el: HTMLElement | null) {
props.setSectionRef?.(id, el)
}
// 发送条数限制
const globalMaxMessages = computed({
get: () => aiGlobalSettings.value.maxMessagesPerRequest,
@@ -24,36 +32,6 @@ const globalMaxMessages = computed({
},
})
// 导出格式选项(AI 对话)
const exportFormatTabs = computed(() => [
{ label: 'Markdown', value: 'markdown' },
{ label: t('settings.aiPrompt.exportFormat.txtLabel'), value: 'txt' },
])
// 当前选中的导出格式(AI 对话)
const exportFormat = computed({
get: () => aiGlobalSettings.value.exportFormat ?? 'markdown',
set: (val: string) => {
promptStore.updateAIGlobalSettings({ exportFormat: val as 'markdown' | 'txt' })
emit('config-changed')
},
})
// SQL Lab 导出格式选项
const sqlExportFormatTabs = computed(() => [
{ label: 'CSV', value: 'csv' },
{ label: 'JSON', value: 'json' },
])
// 当前选中的 SQL Lab 导出格式
const sqlExportFormat = computed({
get: () => aiGlobalSettings.value.sqlExportFormat ?? 'csv',
set: (val: string) => {
promptStore.updateAIGlobalSettings({ sqlExportFormat: val as 'csv' | 'json' })
emit('config-changed')
},
})
const enableAutoSkill = computed({
get: () => aiGlobalSettings.value.enableAutoSkill ?? true,
set: (val: boolean) => {
@@ -127,8 +105,8 @@ const maxToolResultPercent = computed({
<template>
<div class="space-y-6">
<!-- 对话设置 -->
<div>
<!-- 工具设置 -->
<div :ref="(el) => setPromptSectionRef('chat', el as HTMLElement | null)">
<h4 class="mb-3 flex items-center gap-2 text-sm font-semibold text-gray-900 dark:text-white">
<UIcon name="i-heroicons-chat-bubble-left-right" class="h-4 w-4 text-green-500" />
{{ t('settings.aiPrompt.chatSettings.title') }}
@@ -176,7 +154,7 @@ const maxToolResultPercent = computed({
</div>
<!-- 技能设置 -->
<div>
<div :ref="(el) => setPromptSectionRef('skill', el as HTMLElement | null)">
<h4 class="mb-3 flex items-center gap-2 text-sm font-semibold text-gray-900 dark:text-white">
<UIcon name="i-heroicons-bolt" class="h-4 w-4 text-amber-500" />
{{ t('settings.aiPrompt.skillSettings.title') }}
@@ -197,7 +175,7 @@ const maxToolResultPercent = computed({
</div>
<!-- 上下文压缩设置 -->
<div>
<div :ref="(el) => setPromptSectionRef('compression', el as HTMLElement | null)">
<h4 class="mb-3 flex items-center gap-2 text-sm font-semibold text-gray-900 dark:text-white">
<UIcon name="i-heroicons-archive-box-arrow-down" class="h-4 w-4 text-purple-500" />
{{ t('settings.aiPrompt.compression.title') }}
@@ -267,40 +245,5 @@ const maxToolResultPercent = computed({
</template>
</div>
</div>
<!-- 导出设置 -->
<div>
<h4 class="mb-3 flex items-center gap-2 text-sm font-semibold text-gray-900 dark:text-white">
<UIcon name="i-heroicons-arrow-down-tray" class="h-4 w-4 text-blue-500" />
{{ t('settings.aiPrompt.exportSettings.title') }}
</h4>
<div class="space-y-4 rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-800/50">
<!-- 导出格式AI 对话 -->
<div class="flex items-center justify-between">
<div class="flex-1 pr-4">
<p class="text-sm font-medium text-gray-900 dark:text-white">
{{ t('settings.aiPrompt.exportFormat.title') }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ t('settings.aiPrompt.exportFormat.description') }}
</p>
</div>
<UTabs v-model="exportFormat" :items="exportFormatTabs" size="xs" />
</div>
<!-- SQL Lab 导出格式 -->
<div class="flex items-center justify-between">
<div class="flex-1 pr-4">
<p class="text-sm font-medium text-gray-900 dark:text-white">
{{ t('settings.aiPrompt.sqlExportFormat.title') }}
</p>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ t('settings.aiPrompt.sqlExportFormat.description') }}
</p>
</div>
<UTabs v-model="sqlExportFormat" :items="sqlExportFormatTabs" size="xs" />
</div>
</div>
</div>
</div>
</template>
@@ -5,8 +5,7 @@ import AIModelConfigTab from './AI/AIModelConfigTab.vue'
import AIDefaultModelTab from './AI/AIDefaultModelTab.vue'
import AIPromptConfigTab from './AI/AIPromptConfigTab.vue'
import AIPreprocessTab from './AI/AIPreprocessTab.vue'
// TODO: 向量模型暂时隐藏,待功能完善后恢复
// import RAGConfigTab from './AI/RAGConfigTab.vue'
import AIExportSettingsTab from './AI/AIExportSettingsTab.vue'
import SubTabs from '@/components/UI/SubTabs.vue'
import { useSubTabsScroll } from '@/composables/useSubTabsScroll'
@@ -21,10 +20,11 @@ const emit = defineEmits<{
const navItems = computed(() => [
{ id: 'model', label: t('settings.tabs.aiConfig') },
{ id: 'defaultModel', label: t('settings.tabs.aiDefaultModel') },
// TODO: 向量模型暂时隐藏,待功能完善后恢复
// { id: 'rag', label: t('settings.tabs.aiRAG') },
{ id: 'chat', label: t('settings.tabs.aiPrompt') },
{ id: 'chat', label: t('settings.aiPrompt.chatSettings.title') },
{ id: 'skill', label: t('settings.aiPrompt.skillSettings.title') },
{ id: 'compression', label: t('settings.aiPrompt.compression.title') },
{ id: 'preprocess', label: t('settings.tabs.aiPreprocess') },
{ id: 'export', label: t('settings.aiPrompt.exportSettings.title') },
])
// 使用二级导航滚动联动 composable
@@ -68,37 +68,23 @@ void aiModelConfigRef.value
<AIModelConfigTab ref="aiModelConfigRef" @config-changed="handleAIConfigChanged" />
</div>
<!-- 分隔线 -->
<div class="border-t border-gray-200 dark:border-gray-700" />
<!-- 默认模型 -->
<div :ref="(el) => setSectionRef('defaultModel', el as HTMLElement)">
<AIDefaultModelTab @config-changed="handleAIConfigChanged" />
</div>
<!-- TODO: 向量模型暂时隐藏待功能完善后恢复 -->
<!--
<div class="border-t border-gray-200 dark:border-gray-700" />
<div :ref="(el) => setSectionRef('rag', el as HTMLElement)">
<RAGConfigTab @config-changed="handleAIConfigChanged" />
</div>
-->
<!-- 分隔线 -->
<div class="border-t border-gray-200 dark:border-gray-700" />
<!-- 对话配置 -->
<div :ref="(el) => setSectionRef('chat', el as HTMLElement)">
<AIPromptConfigTab @config-changed="handleAIConfigChanged" />
</div>
<!-- 分隔线 -->
<div class="border-t border-gray-200 dark:border-gray-700" />
<!-- 工具设置 -->
<AIPromptConfigTab :set-section-ref="setSectionRef" @config-changed="handleAIConfigChanged" />
<!-- 预处理配置 -->
<div :ref="(el) => setSectionRef('preprocess', el as HTMLElement)">
<AIPreprocessTab />
</div>
<!-- 导出设置 -->
<div :ref="(el) => setSectionRef('export', el as HTMLElement)">
<AIExportSettingsTab @config-changed="handleAIConfigChanged" />
</div>
</div>
</div>
</div>
+2 -2
View File
@@ -276,7 +276,7 @@
},
"aiPrompt": {
"chatSettings": {
"title": "Chat Settings"
"title": "Tool Settings"
},
"exportSettings": {
"title": "Export Settings"
@@ -382,7 +382,7 @@
"developer": {
"title": "Developer Options",
"debugMode": "DEBUG Mode",
"debugModeDesc": "When enabled, AI logs will include full raw message content without truncation. Log files may grow significantly"
"debugModeDesc": "When enabled, the debug panel and quick debugging tools will be shown. You can use them to view databases, run testing tools, and record more detailed debug logs."
}
},
"embedding": {
+2 -2
View File
@@ -276,7 +276,7 @@
},
"aiPrompt": {
"chatSettings": {
"title": "チャット設定"
"title": "ツール設定"
},
"exportSettings": {
"title": "エクスポート設定"
@@ -382,7 +382,7 @@
"developer": {
"title": "開発者オプション",
"debugMode": "DEBUG モード",
"debugModeDesc": "有効にすると、AI ログに完全な生メッセージ内容が切り詰められずに記録されます。ログファイルが大幅に増大する可能性があります"
"debugModeDesc": "有効にすると、デバッグパネルとクイックデバッグツールが表示されます。データベースの確認、テストツールの実行、より詳細なデバッグログの記録に使用できます"
}
},
"embedding": {
+2 -2
View File
@@ -276,7 +276,7 @@
},
"aiPrompt": {
"chatSettings": {
"title": "对话设置"
"title": "工具设置"
},
"exportSettings": {
"title": "导出设置"
@@ -382,7 +382,7 @@
"developer": {
"title": "开发者选项",
"debugMode": "DEBUG 模式",
"debugModeDesc": "开启后,AI 日志中将记录完整的原始消息内容,不截断。日志文件可能会显著增大"
"debugModeDesc": "开启后,将显示调试面板和快捷调试工具,可用于查看数据库、运行测试工具,并记录更详细的调试日志。"
}
},
"embedding": {
+2 -2
View File
@@ -276,7 +276,7 @@
},
"aiPrompt": {
"chatSettings": {
"title": "聊天設定"
"title": "工具設定"
},
"exportSettings": {
"title": "匯出設定"
@@ -382,7 +382,7 @@
"developer": {
"title": "開發者選項",
"debugMode": "DEBUG 模式",
"debugModeDesc": "開啟後,AI 日誌中將紀錄完整的原始訊息內容,不截斷。日誌檔案可能會顯著增大"
"debugModeDesc": "開啟後,將顯示除錯面板和快捷除錯工具,可用於查看資料庫、執行測試工具,並記錄更詳細的除錯日誌。"
}
},
"embedding": {
+32 -27
View File
@@ -2,6 +2,27 @@ import { defineStore } from 'pinia'
import { ref } from 'vue'
import type { KeywordTemplate } from '@/types/analysis'
interface ContextCompressionSettings {
enabled: boolean
tokenThresholdPercent: number
bufferSizePercent: number
maxToolResultPercent: number
}
interface AIGlobalSettings {
maxMessagesPerRequest: number
exportFormat: 'markdown' | 'txt'
sqlExportFormat: 'csv' | 'json'
enableAutoSkill: boolean
searchContextBefore: number
searchContextAfter: number
contextCompression: ContextCompressionSettings
}
type AIGlobalSettingsUpdate = Partial<Omit<AIGlobalSettings, 'contextCompression'>> & {
contextCompression?: Partial<ContextCompressionSettings>
}
/**
* AI 配置与关键词模板相关的全局状态
*/
@@ -9,10 +30,10 @@ export const usePromptStore = defineStore(
'prompt',
() => {
const aiConfigVersion = ref(0)
const aiGlobalSettings = ref({
const aiGlobalSettings = ref<AIGlobalSettings>({
maxMessagesPerRequest: 1000,
exportFormat: 'markdown' as 'markdown' | 'txt',
sqlExportFormat: 'csv' as 'csv' | 'json',
exportFormat: 'markdown',
sqlExportFormat: 'csv',
enableAutoSkill: true,
searchContextBefore: 2,
searchContextAfter: 2,
@@ -36,30 +57,14 @@ export const usePromptStore = defineStore(
/**
* 更新 AI 全局设置
*/
function updateAIGlobalSettings(
settings: Partial<{
maxMessagesPerRequest: number
exportFormat: 'markdown' | 'txt'
sqlExportFormat: 'csv' | 'json'
enableAutoSkill: boolean
searchContextBefore: number
searchContextAfter: number
contextCompression: {
enabled: boolean
tokenThresholdPercent: number
bufferSizePercent: number
maxToolResultPercent?: number
}
}>
) {
if (settings.contextCompression) {
aiGlobalSettings.value = {
...aiGlobalSettings.value,
...settings,
contextCompression: { ...aiGlobalSettings.value.contextCompression, ...settings.contextCompression },
}
} else {
aiGlobalSettings.value = { ...aiGlobalSettings.value, ...settings }
function updateAIGlobalSettings(settings: AIGlobalSettingsUpdate) {
const { contextCompression, ...baseSettings } = settings
aiGlobalSettings.value = {
...aiGlobalSettings.value,
...baseSettings,
contextCompression: contextCompression
? { ...aiGlobalSettings.value.contextCompression, ...contextCompression }
: aiGlobalSettings.value.contextCompression,
}
notifyAIConfigChanged()
}