mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-25 17:54:34 +08:00
refactor(ai): remove unused data source panel
This commit is contained in:
1 parent
245a99b8ff
commit
23f77f9a89
9 files changed
+1
-252
No files matched your search
@@ -4,7 +4,6 @@ import { useI18n } from 'vue-i18n'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
import ConversationList from './chat/ConversationList.vue'
|
||||
import DataSourcePanel from './chat/DataSourcePanel.vue'
|
||||
import ChatMessage from './chat/ChatMessage.vue'
|
||||
import AIChatInput from './input/AIChatInput.vue'
|
||||
import AIThinkingIndicator from './chat/AIThinkingIndicator.vue'
|
||||
@@ -53,9 +52,6 @@ const initialAIChatId = typeof route.query.aiChatId === 'string' ? route.query.a
|
||||
const {
|
||||
initialization,
|
||||
messages,
|
||||
sourceMessages,
|
||||
currentKeywords,
|
||||
isLoadingSource,
|
||||
isAIThinking,
|
||||
currentAIChatId,
|
||||
currentToolStatus,
|
||||
@@ -190,7 +186,6 @@ const currentPresetQuestions = computed(() => {
|
||||
const currentChatType = computed(() => props.chatType ?? 'group')
|
||||
|
||||
// UI 状态
|
||||
const isSourcePanelCollapsed = ref(false)
|
||||
const hasLLMConfig = ref(false)
|
||||
const isCheckingConfig = ref(true)
|
||||
const configModalScrollToSection = ref<string | undefined>(undefined)
|
||||
@@ -419,11 +414,6 @@ async function handleForkAIChat(messageId: string) {
|
||||
}
|
||||
}
|
||||
|
||||
// 切换数据源面板
|
||||
function toggleSourcePanel() {
|
||||
isSourcePanelCollapsed.value = !isSourcePanelCollapsed.value
|
||||
}
|
||||
|
||||
// 选择对话
|
||||
async function handleSelectAIChat(convId: string) {
|
||||
await loadAIChat(convId)
|
||||
@@ -686,23 +676,6 @@ watch(
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧:数据源面板 -->
|
||||
<Transition name="slide-fade">
|
||||
<div
|
||||
v-if="sourceMessages.length > 0 && !isSourcePanelCollapsed"
|
||||
class="w-80 shrink-0 border-l border-gray-200 bg-gray-50/50 p-4 dark:border-gray-800 dark:bg-page-dark/50"
|
||||
>
|
||||
<DataSourcePanel
|
||||
:messages="sourceMessages"
|
||||
:keywords="currentKeywords"
|
||||
:is-loading="isLoadingSource"
|
||||
:is-collapsed="isSourcePanelCollapsed"
|
||||
class="h-full"
|
||||
@toggle="toggleSourcePanel"
|
||||
/>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
|
||||
<!-- 助手配置弹窗 -->
|
||||
@@ -754,18 +727,6 @@ watch(
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Transition styles for slide-fade */
|
||||
.slide-fade-enter-active,
|
||||
.slide-fade-leave-active {
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
.slide-fade-enter-from,
|
||||
.slide-fade-leave-to {
|
||||
transform: translateX(20px);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
/* Transition styles for slide-up (status bar) */
|
||||
.slide-up-enter-active,
|
||||
.slide-up-leave-active {
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import dayjs from 'dayjs'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
// Props
|
||||
const props = defineProps<{
|
||||
messages: Array<{
|
||||
id: number
|
||||
senderName: string
|
||||
content: string
|
||||
timestamp: number
|
||||
}>
|
||||
keywords: string[]
|
||||
isLoading: boolean
|
||||
isCollapsed: boolean
|
||||
}>()
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
toggle: []
|
||||
}>()
|
||||
|
||||
// 格式化时间
|
||||
function formatTime(timestamp: number): string {
|
||||
return dayjs(timestamp).format('MM-DD HH:mm')
|
||||
}
|
||||
|
||||
// 高亮关键词
|
||||
function highlightKeywords(text: string): string {
|
||||
if (!props.keywords.length) return text
|
||||
const pattern = props.keywords.join('|')
|
||||
const regex = new RegExp(`(${pattern})`, 'gi')
|
||||
return text.replace(regex, '<mark class="bg-yellow-200 dark:bg-yellow-800/50 px-0.5 rounded">$1</mark>')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="relative flex flex-col rounded-xl bg-white transition-all duration-300 dark:bg-page-dark"
|
||||
:class="[isCollapsed ? 'w-12' : 'w-full']"
|
||||
>
|
||||
<!-- 折叠状态 -->
|
||||
<template v-if="isCollapsed">
|
||||
<button
|
||||
class="flex h-full flex-col items-center justify-center gap-2 text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200"
|
||||
@click="emit('toggle')"
|
||||
>
|
||||
<UIcon name="i-heroicons-chevron-left" class="h-5 w-5" />
|
||||
<span class="writing-vertical text-xs">{{ t('ai.chat.dataSource.title') }}</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<!-- 展开状态 -->
|
||||
<template v-else>
|
||||
<!-- 头部 -->
|
||||
<div class="flex items-center justify-between border-b border-gray-200 px-4 py-3 dark:border-gray-800">
|
||||
<div class="flex items-center gap-2">
|
||||
<UIcon name="i-heroicons-document-magnifying-glass" class="h-5 w-5 text-gray-500" />
|
||||
<span class="font-medium text-gray-900 dark:text-white">{{ t('ai.chat.dataSource.title') }}</span>
|
||||
</div>
|
||||
<UButton icon="i-heroicons-chevron-right" color="gray" variant="ghost" size="xs" @click="emit('toggle')" />
|
||||
</div>
|
||||
|
||||
<!-- 当前关键词 -->
|
||||
<div v-if="keywords.length > 0" class="border-b border-gray-200 px-4 py-2 dark:border-gray-800">
|
||||
<div class="flex flex-wrap items-center gap-2">
|
||||
<span class="text-xs text-gray-500">{{ t('ai.chat.dataSource.keywords') }}</span>
|
||||
<span
|
||||
v-for="kw in keywords"
|
||||
:key="kw"
|
||||
class="rounded-full bg-violet-100 px-2 py-0.5 text-xs text-violet-700 dark:bg-violet-900/30 dark:text-violet-400"
|
||||
>
|
||||
{{ kw }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<div class="flex-1 overflow-y-auto">
|
||||
<!-- 加载中 -->
|
||||
<div v-if="isLoading" class="flex items-center justify-center py-8">
|
||||
<UIcon name="i-heroicons-arrow-path" class="h-6 w-6 animate-spin text-gray-400" />
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="messages.length === 0" class="flex flex-col items-center justify-center py-8 text-center">
|
||||
<UIcon name="i-heroicons-inbox" class="h-10 w-10 text-gray-300 dark:text-gray-600" />
|
||||
<p class="mt-2 text-sm text-gray-500">{{ t('ai.chat.dataSource.empty') }}</p>
|
||||
<p class="text-xs text-gray-400">{{ t('ai.chat.dataSource.emptyHint') }}</p>
|
||||
</div>
|
||||
|
||||
<!-- 消息列表 -->
|
||||
<div v-else class="divide-y divide-gray-100 dark:divide-gray-800">
|
||||
<div
|
||||
v-for="msg in messages"
|
||||
:key="msg.id"
|
||||
class="px-4 py-3 transition-colors hover:bg-gray-50 dark:hover:bg-gray-800/50"
|
||||
>
|
||||
<div class="mb-1 flex items-center justify-between">
|
||||
<span class="text-sm font-medium text-gray-900 dark:text-white">
|
||||
{{ msg.senderName }}
|
||||
</span>
|
||||
<span class="text-xs text-gray-400">{{ formatTime(msg.timestamp) }}</span>
|
||||
</div>
|
||||
<p class="line-clamp-3 text-sm text-gray-600 dark:text-gray-400" v-html="highlightKeywords(msg.content)" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部统计 -->
|
||||
<div v-if="messages.length > 0" class="border-t border-gray-200 px-4 py-2 dark:border-gray-800">
|
||||
<span class="text-xs text-gray-500">
|
||||
{{ t('ai.chat.dataSource.totalRecords', { count: messages.length }) }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 半透明数据流背景效果 -->
|
||||
<div v-if="!isCollapsed" class="pointer-events-none absolute inset-0 overflow-hidden rounded-xl opacity-[0.03]">
|
||||
<div class="data-flow-bg absolute inset-0" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.writing-vertical {
|
||||
writing-mode: vertical-rl;
|
||||
text-orientation: mixed;
|
||||
}
|
||||
|
||||
/* 数据流背景动画 */
|
||||
.data-flow-bg {
|
||||
background: repeating-linear-gradient(0deg, transparent, transparent 20px, currentColor 20px, currentColor 21px);
|
||||
animation: dataFlow 20s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes dataFlow {
|
||||
0% {
|
||||
transform: translateY(0);
|
||||
}
|
||||
100% {
|
||||
transform: translateY(-100px);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -2,7 +2,7 @@
|
||||
* Composables 统一导出
|
||||
*/
|
||||
export { usePageAnchors, type AnchorItem } from './usePageAnchors'
|
||||
export { useAIChat, type ChatMessage, type SourceMessage } from './useAIChat'
|
||||
export { useAIChat, type ChatMessage } from './useAIChat'
|
||||
export { useToast } from './useToast'
|
||||
export { useScreenCapture, type ScreenCaptureOptions } from './useScreenCapture'
|
||||
export { useTimeSelect } from './useTimeSelect'
|
||||
|
||||
@@ -7,7 +7,6 @@ import { toRef } from 'vue'
|
||||
import { useAIChatStore } from '@/stores/aiChat'
|
||||
import type {
|
||||
ChatMessage,
|
||||
SourceMessage,
|
||||
ToolStatus,
|
||||
ToolCallRecord,
|
||||
ToolBlockContent,
|
||||
@@ -22,7 +21,6 @@ import type { TokenUsage, AgentRuntimeStatus } from '@electron/shared/types'
|
||||
export type { TokenUsage, AgentRuntimeStatus }
|
||||
export type {
|
||||
ChatMessage,
|
||||
SourceMessage,
|
||||
ToolStatus,
|
||||
ToolCallRecord,
|
||||
ToolBlockContent,
|
||||
@@ -58,9 +56,6 @@ export function useAIChat(
|
||||
return {
|
||||
initialization,
|
||||
messages: toRef(state, 'messages'),
|
||||
sourceMessages: toRef(state, 'sourceMessages'),
|
||||
currentKeywords: toRef(state, 'currentKeywords'),
|
||||
isLoadingSource: toRef(state, 'isLoadingSource'),
|
||||
isAIThinking: toRef(state, 'isAIThinking'),
|
||||
currentAIChatId: toRef(state, 'currentAIChatId'),
|
||||
currentToolStatus: toRef(state, 'currentToolStatus'),
|
||||
|
||||
@@ -223,13 +223,6 @@
|
||||
"copied": "Copied to clipboard",
|
||||
"unknown": "Unknown error"
|
||||
},
|
||||
"dataSource": {
|
||||
"title": "Data Source",
|
||||
"keywords": "Keywords:",
|
||||
"empty": "No data",
|
||||
"emptyHint": "Related records will appear here after asking questions",
|
||||
"totalRecords": "{count} records"
|
||||
},
|
||||
"evidence": {
|
||||
"title": "Evidence",
|
||||
"query": "Question",
|
||||
|
||||
@@ -223,13 +223,6 @@
|
||||
"copied": "クリップボードにコピーしました",
|
||||
"unknown": "不明なエラー"
|
||||
},
|
||||
"dataSource": {
|
||||
"title": "データソース",
|
||||
"keywords": "キーワード:",
|
||||
"empty": "データがありません",
|
||||
"emptyHint": "質問を送信すると、関連する記録がここに表示されます",
|
||||
"totalRecords": "全 {count} 件の記録"
|
||||
},
|
||||
"evidence": {
|
||||
"title": "根拠",
|
||||
"query": "質問",
|
||||
|
||||
@@ -223,13 +223,6 @@
|
||||
"copied": "已复制到剪贴板",
|
||||
"unknown": "未知错误"
|
||||
},
|
||||
"dataSource": {
|
||||
"title": "数据源",
|
||||
"keywords": "关键词:",
|
||||
"empty": "暂无数据",
|
||||
"emptyHint": "发送问题后,相关记录会显示在这里",
|
||||
"totalRecords": "共 {count} 条记录"
|
||||
},
|
||||
"evidence": {
|
||||
"title": "依据证据",
|
||||
"query": "问题",
|
||||
|
||||
@@ -223,13 +223,6 @@
|
||||
"copied": "已複製到剪貼簿",
|
||||
"unknown": "未知錯誤"
|
||||
},
|
||||
"dataSource": {
|
||||
"title": "資料來源",
|
||||
"keywords": "關鍵字:",
|
||||
"empty": "暫無資料",
|
||||
"emptyHint": "傳送問題後,相關紀錄會顯示在這裡",
|
||||
"totalRecords": "共 {count} 筆紀錄"
|
||||
},
|
||||
"evidence": {
|
||||
"title": "依據證據",
|
||||
"query": "問題",
|
||||
|
||||
@@ -132,16 +132,6 @@ export interface ChatMessage {
|
||||
entityRefs?: AIEntityRef[]
|
||||
}
|
||||
|
||||
// 搜索结果消息类型(保留用于数据源面板)
|
||||
export interface SourceMessage {
|
||||
id: number
|
||||
senderName: string
|
||||
senderPlatformId: string
|
||||
content: string
|
||||
timestamp: number
|
||||
type: number
|
||||
}
|
||||
|
||||
interface OwnerInfo {
|
||||
platformId: string
|
||||
displayName: string
|
||||
@@ -149,8 +139,6 @@ interface OwnerInfo {
|
||||
|
||||
interface AIChatBuffer {
|
||||
messages: ChatMessage[]
|
||||
sourceMessages: SourceMessage[]
|
||||
currentKeywords: string[]
|
||||
assistantId: string | null
|
||||
loaded: boolean
|
||||
sessionTokenUsage?: TokenUsage
|
||||
@@ -165,9 +153,6 @@ export interface AIChatSessionState {
|
||||
timeFilter?: { startTs: number; endTs: number }
|
||||
selectedAssistantId: string | null
|
||||
messages: ChatMessage[]
|
||||
sourceMessages: SourceMessage[]
|
||||
currentKeywords: string[]
|
||||
isLoadingSource: boolean
|
||||
isAIThinking: boolean
|
||||
currentAIChatId: string | null
|
||||
currentToolStatus: ToolStatus | null
|
||||
@@ -287,8 +272,6 @@ function toRuntimeMessage(msg: PersistedAIMessage): ChatMessage {
|
||||
function createAIChatBuffer(assistantId: string | null = null): AIChatBuffer {
|
||||
return {
|
||||
messages: [],
|
||||
sourceMessages: [],
|
||||
currentKeywords: [],
|
||||
assistantId,
|
||||
loaded: false,
|
||||
}
|
||||
@@ -305,9 +288,6 @@ function createSessionState(params: EnsureAIChatSessionParams): AIChatSessionSta
|
||||
timeFilter: params.timeFilter,
|
||||
selectedAssistantId: null,
|
||||
messages: draftBuffer.messages,
|
||||
sourceMessages: draftBuffer.sourceMessages,
|
||||
currentKeywords: draftBuffer.currentKeywords,
|
||||
isLoadingSource: false,
|
||||
isAIThinking: false,
|
||||
currentAIChatId: null,
|
||||
currentToolStatus: null,
|
||||
@@ -424,8 +404,6 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
const buffer = getOrCreateBuffer(state, bufferKey)
|
||||
state.currentAIChatId = bufferKey === DRAFT_AI_CHAT_KEY ? null : bufferKey
|
||||
state.messages = buffer.messages
|
||||
state.sourceMessages = buffer.sourceMessages
|
||||
state.currentKeywords = buffer.currentKeywords
|
||||
state.selectedAssistantId = buffer.assistantId
|
||||
state.sessionTokenUsage = buffer.sessionTokenUsage ? { ...buffer.sessionTokenUsage } : createEmptyTokenUsage()
|
||||
state.agentStatus = null
|
||||
@@ -592,8 +570,6 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
if (!isLatestAIChatSelection(chatKey, selectionGeneration)) return false
|
||||
|
||||
buffer.messages.splice(0, buffer.messages.length, ...history.map((msg) => toRuntimeMessage(msg)))
|
||||
buffer.sourceMessages.splice(0, buffer.sourceMessages.length)
|
||||
buffer.currentKeywords.splice(0, buffer.currentKeywords.length)
|
||||
buffer.sessionTokenUsage = toTokenUsage(tokenUsage)
|
||||
buffer.loaded = true
|
||||
}
|
||||
@@ -693,7 +669,6 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
bindDisplayedBuffer(state, DRAFT_AI_CHAT_KEY)
|
||||
state.currentToolStatus = null
|
||||
state.toolsUsedInCurrentRound = []
|
||||
state.isLoadingSource = false
|
||||
state.sessionTokenUsage = createEmptyTokenUsage()
|
||||
state.agentStatus = null
|
||||
state.isAborted = false
|
||||
@@ -1058,7 +1033,6 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
const currentEntityRefs = (options?.entityRefs ?? []).map((ref) => ({ ...ref }))
|
||||
|
||||
state.isAIThinking = true
|
||||
state.isLoadingSource = true
|
||||
state.currentToolStatus = null
|
||||
state.toolsUsedInCurrentRound = []
|
||||
state.agentStatus = null
|
||||
@@ -1332,7 +1306,6 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
status: toolFailed ? 'error' : 'done',
|
||||
})
|
||||
}
|
||||
state.isLoadingSource = false
|
||||
break
|
||||
|
||||
case 'plan_delta':
|
||||
@@ -1556,7 +1529,6 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
return { success: false, reason: 'error' }
|
||||
} finally {
|
||||
state.isAIThinking = false
|
||||
state.isLoadingSource = false
|
||||
state.currentToolStatus = null
|
||||
state.isAborted = false
|
||||
state.currentRequestId = ''
|
||||
@@ -1752,7 +1724,6 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
void ensureOwnerInfo(chatKey)
|
||||
|
||||
state.isAIThinking = true
|
||||
state.isLoadingSource = true
|
||||
state.currentToolStatus = null
|
||||
state.toolsUsedInCurrentRound = []
|
||||
state.agentStatus = null
|
||||
@@ -1951,7 +1922,6 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
status: toolFailed ? 'error' : 'done',
|
||||
})
|
||||
}
|
||||
state.isLoadingSource = false
|
||||
break
|
||||
case 'plan_delta':
|
||||
if (chunk.planDelta) appendPlanDraftToBlocks(chunk.planDelta)
|
||||
@@ -2089,7 +2059,6 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
return { success: false, reason: 'error' }
|
||||
} finally {
|
||||
state.isAIThinking = false
|
||||
state.isLoadingSource = false
|
||||
state.currentToolStatus = null
|
||||
state.isAborted = false
|
||||
state.currentRequestId = ''
|
||||
@@ -2104,7 +2073,6 @@ export const useAIChatStore = defineStore('aiChatRuntime', () => {
|
||||
|
||||
state.isAborted = true
|
||||
state.isAIThinking = false
|
||||
state.isLoadingSource = false
|
||||
state.currentToolStatus = null
|
||||
setAgentPhase(state, 'aborted')
|
||||
|
||||
|
||||
Reference in new issue
Block a user