mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-26 02:04:37 +08:00
feat(chat-record): polish viewer controls and highlights
This commit is contained in:
1 parent
851166188d
commit
e5954d8fb1
7 files changed
+63
-56
No files matched your search
@@ -70,10 +70,8 @@ const currentColor = computed(() => colorPalette[colorIndex.value])
|
||||
const avatarColor = computed(() => currentColor.value.avatar)
|
||||
const nameColor = computed(() => currentColor.value.name)
|
||||
|
||||
// 气泡颜色(Owner 使用微粉色,其他人使用微灰色)
|
||||
const bubbleColor = computed(() =>
|
||||
isOwner.value ? 'bg-pink-50/80 dark:bg-pink-950/20' : 'bg-gray-100/80 dark:bg-gray-800/80'
|
||||
)
|
||||
// 气泡颜色
|
||||
const bubbleColor = 'bg-gray-100/80 dark:bg-gray-800/80'
|
||||
|
||||
// 显示名称(包含别名)
|
||||
const displayName = computed(() => {
|
||||
@@ -116,23 +114,23 @@ const avatarLetter = computed(() => {
|
||||
return '?'
|
||||
})
|
||||
|
||||
const HIGHLIGHT_MARK_CLASS =
|
||||
'bg-transparent text-inherit underline decoration-primary-500/80 underline-offset-4 dark:decoration-primary-400/80'
|
||||
|
||||
// 高亮关键词
|
||||
function highlightContent(content: string): string {
|
||||
if (!props.highlightKeywords?.length || !content) return content
|
||||
|
||||
const pattern = props.highlightKeywords.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('|')
|
||||
const regex = new RegExp(`(${pattern})`, 'gi')
|
||||
return content.replace(
|
||||
regex,
|
||||
'<mark class="bg-transparent border-b-2 border-yellow-400 dark:border-yellow-500">$1</mark>'
|
||||
)
|
||||
return content.replace(regex, `<mark class="${HIGHLIGHT_MARK_CLASS}">$1</mark>`)
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="group px-4 py-2.5 transition-all duration-300"
|
||||
:class="[isTarget ? 'bg-amber-500/5 dark:bg-amber-500/10' : 'hover:bg-gray-50/40 dark:hover:bg-gray-900/10']"
|
||||
:class="[isTarget ? 'bg-primary-500/5 dark:bg-primary-500/10' : 'hover:bg-gray-50/40 dark:hover:bg-gray-900/10']"
|
||||
>
|
||||
<!-- Owner 消息显示在右侧 -->
|
||||
<div class="flex gap-3" :class="isOwner ? 'flex-row-reverse' : ''">
|
||||
@@ -164,7 +162,7 @@ function highlightContent(content: string): string {
|
||||
<div class="flex items-start gap-1 max-w-[calc(100%-68px)]" :class="isOwner ? 'flex-row-reverse' : ''">
|
||||
<div
|
||||
class="relative inline-block rounded-2xl px-3.5 py-2.5 transition-all duration-300 border border-gray-100 dark:border-gray-800/40"
|
||||
:class="[bubbleColor, isTarget ? 'ring-2 ring-amber-400 dark:ring-amber-500' : '']"
|
||||
:class="bubbleColor"
|
||||
>
|
||||
<!-- 回复引用样式 -->
|
||||
<div
|
||||
|
||||
@@ -148,7 +148,7 @@ function getSessionAvatarClass(sessionId: number): string {
|
||||
|
||||
// 估算项目高度
|
||||
const ESTIMATED_DATE_HEIGHT = 28 // 日期头高度
|
||||
const ESTIMATED_SESSION_HEIGHT = 52 // 会话项高度(含两行摘要)
|
||||
const ESTIMATED_SESSION_HEIGHT = 52 // 会话项高度(摘要/生成按钮单独一行)
|
||||
|
||||
// 虚拟化器
|
||||
const virtualizer = useVirtualizer(
|
||||
@@ -227,10 +227,10 @@ function scrollToBottom() {
|
||||
}
|
||||
|
||||
// 滚动到指定会话
|
||||
function scrollToSession(sessionId: number) {
|
||||
function scrollToSession(sessionId: number, behavior: 'auto' | 'smooth' = 'auto') {
|
||||
const index = flatList.value.findIndex((item) => item.type === 'session' && item.session.id === sessionId)
|
||||
if (index !== -1) {
|
||||
virtualizer.value.scrollToIndex(index, { align: 'center' })
|
||||
virtualizer.value.scrollToIndex(index, { align: 'center', behavior })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -296,7 +296,7 @@ watch(
|
||||
() => props.activeSessionId,
|
||||
(newId) => {
|
||||
if (newId) {
|
||||
scrollToSession(newId)
|
||||
scrollToSession(newId, 'smooth')
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -333,7 +333,9 @@ watch(
|
||||
<span class="text-xs font-medium text-gray-600 dark:text-gray-300">{{ t('records.timeline.timeline') }}</span>
|
||||
<div class="flex items-center gap-0.5">
|
||||
<UTooltip :text="t('records.batchSummary.title')">
|
||||
<UButton icon="i-heroicons-sparkles" variant="ghost" size="xs" @click="showBatchSummaryModal = true" />
|
||||
<UButton icon="i-heroicons-sparkles" variant="ghost" size="xs" @click="showBatchSummaryModal = true">
|
||||
{{ t('records.batchSummary.trigger') }}
|
||||
</UButton>
|
||||
</UTooltip>
|
||||
<UButton icon="i-heroicons-chevron-left" variant="ghost" size="xs" @click="isCollapsed = true" />
|
||||
</div>
|
||||
@@ -375,7 +377,7 @@ watch(
|
||||
<template v-else-if="flatList[virtualItem.index]?.type === 'session'">
|
||||
<div class="px-1.5 py-0.5">
|
||||
<button
|
||||
class="group relative flex w-full gap-2.5 items-center rounded-xl p-1.5 pl-2.5 text-left transition-all duration-200"
|
||||
class="group relative flex h-12 w-full items-center gap-2.5 rounded-xl p-1.5 pl-2.5 text-left transition-all duration-200"
|
||||
:class="[
|
||||
activeSessionId === (flatList[virtualItem.index] as { session: ChatSessionItem }).session.id
|
||||
? 'bg-pink-500/5 dark:bg-pink-500/10'
|
||||
@@ -395,38 +397,35 @@ watch(
|
||||
|
||||
<!-- 莫兰迪配色哈希头像 -->
|
||||
<div
|
||||
class="w-7 h-7 rounded-lg flex items-center justify-center shrink-0 transition-colors"
|
||||
class="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg px-1 font-mono text-[10px] font-semibold transition-colors"
|
||||
:class="
|
||||
getSessionAvatarClass((flatList[virtualItem.index] as { session: ChatSessionItem }).session.id)
|
||||
"
|
||||
>
|
||||
<UIcon name="i-heroicons-chat-bubble-oval-left" class="w-3.5 h-3.5" />
|
||||
<span class="truncate">
|
||||
{{ (flatList[virtualItem.index] as { session: ChatSessionItem }).session.messageCount }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 会话内容信息 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<!-- 时间和消息数 -->
|
||||
<div class="flex items-center justify-between mb-0.5">
|
||||
<span class="text-xs font-semibold text-gray-700 dark:text-gray-200">
|
||||
{{ formatTime((flatList[virtualItem.index] as { session: ChatSessionItem }).session.startTs) }}
|
||||
</span>
|
||||
<span
|
||||
class="font-mono text-[10px] px-1.5 py-0.5 rounded bg-gray-200/50 dark:bg-gray-700/50 text-gray-500 dark:text-gray-400 font-medium"
|
||||
>
|
||||
{{ (flatList[virtualItem.index] as { session: ChatSessionItem }).session.messageCount }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 摘要或生成按钮 -->
|
||||
<div class="flex w-full items-center">
|
||||
<!-- 有摘要:显示摘要(两行) -->
|
||||
<div class="min-w-0 flex-1">
|
||||
<div class="min-w-0">
|
||||
<!-- 有摘要:时间和摘要作为连续文本排版,换行后从整行起点继续显示 -->
|
||||
<UTooltip
|
||||
v-if="(flatList[virtualItem.index] as { session: ChatSessionItem }).session.summary"
|
||||
:popper="{ placement: 'right' }"
|
||||
:content="{ side: 'right', align: 'start' }"
|
||||
:ui="{ content: 'z-[10001] h-auto max-h-80 overflow-y-auto' }"
|
||||
>
|
||||
<span class="line-clamp-2 text-xs leading-normal text-gray-400 dark:text-gray-500 font-normal">
|
||||
{{ (flatList[virtualItem.index] as { session: ChatSessionItem }).session.summary }}
|
||||
<span
|
||||
class="line-clamp-2 min-w-0 text-xs font-normal leading-snug text-gray-500 dark:text-gray-400"
|
||||
>
|
||||
<span class="font-medium">
|
||||
{{
|
||||
formatTime((flatList[virtualItem.index] as { session: ChatSessionItem }).session.startTs)
|
||||
}}
|
||||
</span>
|
||||
<span class="ml-1">
|
||||
{{ (flatList[virtualItem.index] as { session: ChatSessionItem }).session.summary }}
|
||||
</span>
|
||||
</span>
|
||||
<template #content>
|
||||
<div class="max-w-sm whitespace-pre-wrap text-sm leading-relaxed font-normal">
|
||||
@@ -434,13 +433,19 @@ watch(
|
||||
</div>
|
||||
</template>
|
||||
</UTooltip>
|
||||
<span v-else class="text-xs font-medium text-gray-500 dark:text-gray-400">
|
||||
{{ formatTime((flatList[virtualItem.index] as { session: ChatSessionItem }).session.startTs) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="!(flatList[virtualItem.index] as { session: ChatSessionItem }).session.summary"
|
||||
class="mt-0.5 flex min-w-0 items-center"
|
||||
>
|
||||
<!-- 无摘要且消息数>=3:显示生成按钮 -->
|
||||
<span
|
||||
v-else-if="
|
||||
(flatList[virtualItem.index] as { session: ChatSessionItem }).session.messageCount >= 3
|
||||
"
|
||||
class="flex items-center gap-1 text-xs text-gray-400 hover:text-pink-500 dark:text-gray-500 dark:hover:text-pink-400 cursor-pointer"
|
||||
v-if="(flatList[virtualItem.index] as { session: ChatSessionItem }).session.messageCount >= 3"
|
||||
class="flex min-w-0 cursor-pointer items-center gap-1 text-xs text-gray-400 hover:text-pink-500 dark:text-gray-500 dark:hover:text-pink-400"
|
||||
@click="
|
||||
generateSummary((flatList[virtualItem.index] as { session: ChatSessionItem }).session, $event)
|
||||
"
|
||||
@@ -448,14 +453,14 @@ watch(
|
||||
<UIcon
|
||||
v-if="isGenerating((flatList[virtualItem.index] as { session: ChatSessionItem }).session.id)"
|
||||
name="i-heroicons-arrow-path"
|
||||
class="h-3 w-3 animate-spin"
|
||||
class="h-3 w-3 shrink-0 animate-spin"
|
||||
/>
|
||||
<UIcon v-else name="i-heroicons-sparkles" class="h-3 w-3" />
|
||||
<span>{{ t('records.timeline.generateSummary') }}</span>
|
||||
<UIcon v-else name="i-heroicons-sparkles" class="h-3 w-3 shrink-0" />
|
||||
<span class="truncate">{{ t('records.timeline.generateSummary') }}</span>
|
||||
</span>
|
||||
|
||||
<!-- 消息数<3:显示提示 -->
|
||||
<span v-else class="text-xs italic text-gray-300 dark:text-gray-600">
|
||||
<span v-else class="min-w-0 flex-1 truncate text-xs italic text-gray-300 dark:text-gray-600">
|
||||
{{ t('records.timeline.tooFewMessages') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -44,6 +44,16 @@ const timeSelectVisible = computed(() => !['ai-chat', 'memory', 'lab', 'debug'].
|
||||
<PageHeader :title="title" :avatar="avatar" size="compact" :icon="icon" :icon-class="iconClass">
|
||||
<template #actions>
|
||||
<template v-if="layoutStore.toolsPanelPosition === 'header'">
|
||||
<UTooltip :text="t('analysis.tooltip.viewChatRecord')">
|
||||
<UButton
|
||||
icon="i-heroicons-chat-bubble-bottom-center-text"
|
||||
variant="ghost"
|
||||
color="gray"
|
||||
size="sm"
|
||||
class="hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||
@click="emit('openChatRecord')"
|
||||
/>
|
||||
</UTooltip>
|
||||
<UTooltip :text="t('analysis.tooltip.incrementalImport')">
|
||||
<UButton
|
||||
icon="i-heroicons-plus-circle"
|
||||
@@ -64,16 +74,6 @@ const timeSelectVisible = computed(() => !['ai-chat', 'memory', 'lab', 'debug'].
|
||||
@click="emit('openMemberManagement')"
|
||||
/>
|
||||
</UTooltip>
|
||||
<UTooltip :text="t('analysis.tooltip.viewChatRecord')">
|
||||
<UButton
|
||||
icon="i-heroicons-chat-bubble-bottom-center-text"
|
||||
variant="ghost"
|
||||
color="gray"
|
||||
size="sm"
|
||||
class="hover:bg-gray-100 dark:hover:bg-gray-800"
|
||||
@click="emit('openChatRecord')"
|
||||
/>
|
||||
</UTooltip>
|
||||
<CaptureButton color="gray" />
|
||||
<UTooltip :text="t('analysis.tooltip.more')">
|
||||
<UButton
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"summaryUnknownError": "Unknown error",
|
||||
"batchSummary": {
|
||||
"title": "Batch Generate Summaries",
|
||||
"trigger": "Batch",
|
||||
"byRange": "By Range",
|
||||
"byTime": "By Time",
|
||||
"selectRange": "Select Range",
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"summaryUnknownError": "不明なエラー",
|
||||
"batchSummary": {
|
||||
"title": "要約をまとめて作成",
|
||||
"trigger": "一括生成",
|
||||
"byRange": "範囲で選択",
|
||||
"byTime": "時間で選択",
|
||||
"selectRange": "範囲を選択",
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"summaryUnknownError": "未知错误",
|
||||
"batchSummary": {
|
||||
"title": "批量生成摘要",
|
||||
"trigger": "批量生成",
|
||||
"byRange": "按范围",
|
||||
"byTime": "按时间",
|
||||
"selectRange": "选择范围",
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
"summaryUnknownError": "未知錯誤",
|
||||
"batchSummary": {
|
||||
"title": "批次產生摘要",
|
||||
"trigger": "批次產生",
|
||||
"byRange": "依範圍",
|
||||
"byTime": "依時間",
|
||||
"selectRange": "選擇範圍",
|
||||
|
||||
Reference in new issue
Block a user