mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-05-25 16:10:18 +08:00
feat: 逻辑优化
This commit is contained in:
@@ -42,9 +42,9 @@ watch(
|
||||
() => props.open,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
activeTab.value = 'ai-config'
|
||||
// 刷新 AI 配置
|
||||
aiConfigRef.value?.refresh()
|
||||
activeTab.value = 'settings' // 默认打开基础设置 Tab
|
||||
// 刷新缓存管理
|
||||
cacheManageRef.value?.refresh()
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@@ -4,10 +4,6 @@ import { storeToRefs } from 'pinia'
|
||||
import { ref, onMounted, nextTick } from 'vue'
|
||||
import { useRouter, useRoute } from 'vue-router'
|
||||
import type { AnalysisSession } from '@/types/chat'
|
||||
import pkg from '../../../package.json'
|
||||
|
||||
const { version } = pkg
|
||||
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import 'dayjs/locale/zh-cn'
|
||||
@@ -331,11 +327,6 @@ function isPrivateChat(session: AnalysisSession): boolean {
|
||||
<span v-if="!isCollapsed" class="truncate">设置</span>
|
||||
</UButton>
|
||||
</UTooltip>
|
||||
|
||||
<!-- 版本号 & 社交链接 -->
|
||||
<div v-if="!isCollapsed" class="flex items-center justify-center gap-2 py-1 text-xs text-gray-400">
|
||||
<span>v{{ version }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -105,9 +105,9 @@ function isActivePreset(presetId: string, chatType: 'group' | 'private'): boolea
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h4 class="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-violet-500" />
|
||||
群聊预设
|
||||
群聊系统提示词
|
||||
</h4>
|
||||
<UButton size="xs" variant="ghost" color="gray" @click="openAddModal('group')">
|
||||
<UButton variant="ghost" color="gray" @click="openAddModal('group')">
|
||||
<UIcon name="i-heroicons-plus" class="mr-1 h-3.5 w-3.5" />
|
||||
添加
|
||||
</UButton>
|
||||
@@ -141,17 +141,17 @@ function isActivePreset(presetId: string, chatType: 'group' | 'private'): boolea
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-medium text-gray-900 dark:text-white">{{ preset.name }}</span>
|
||||
<UBadge v-if="preset.isBuiltIn" color="gray" variant="soft" size="xs">内置</UBadge>
|
||||
<UBadge v-if="preset.isBuiltIn" color="gray" variant="soft">内置</UBadge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100" @click.stop>
|
||||
<UButton size="xs" color="gray" variant="ghost" @click="openEditModal(preset)">
|
||||
<UButton color="gray" variant="ghost" @click="openEditModal(preset)">
|
||||
{{ preset.isBuiltIn ? '查看' : '编辑' }}
|
||||
</UButton>
|
||||
<UButton size="xs" color="gray" variant="ghost" @click="duplicatePreset(preset.id)">复制</UButton>
|
||||
<UButton v-if="!preset.isBuiltIn" size="xs" color="error" variant="ghost" @click="deletePreset(preset.id)">
|
||||
<UButton color="gray" variant="ghost" @click="duplicatePreset(preset.id)">复制</UButton>
|
||||
<UButton v-if="!preset.isBuiltIn" color="error" variant="ghost" @click="deletePreset(preset.id)">
|
||||
删除
|
||||
</UButton>
|
||||
</div>
|
||||
@@ -162,14 +162,14 @@ function isActivePreset(presetId: string, chatType: 'group' | 'private'): boolea
|
||||
<!-- 分隔线 -->
|
||||
<div class="border-t border-gray-200 dark:border-gray-700"></div>
|
||||
|
||||
<!-- 私聊预设组 -->
|
||||
<!-- 私聊系统提示词 -->
|
||||
<div>
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h4 class="flex items-center gap-2 text-sm font-semibold text-gray-900 dark:text-white">
|
||||
<UIcon name="i-heroicons-user" class="h-4 w-4 text-blue-500" />
|
||||
私聊预设
|
||||
私聊系统提示词
|
||||
</h4>
|
||||
<UButton size="xs" variant="ghost" color="gray" @click="openAddModal('private')">
|
||||
<UButton variant="ghost" color="gray" @click="openAddModal('private')">
|
||||
<UIcon name="i-heroicons-plus" class="mr-1 h-3.5 w-3.5" />
|
||||
添加
|
||||
</UButton>
|
||||
@@ -203,33 +203,23 @@ function isActivePreset(presetId: string, chatType: 'group' | 'private'): boolea
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-medium text-gray-900 dark:text-white">{{ preset.name }}</span>
|
||||
<UBadge v-if="preset.isBuiltIn" color="gray" variant="soft" size="xs">内置</UBadge>
|
||||
<UBadge v-if="preset.isBuiltIn" color="gray" variant="soft">内置</UBadge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex items-center gap-1 opacity-0 transition-opacity group-hover:opacity-100" @click.stop>
|
||||
<UButton size="xs" color="gray" variant="ghost" @click="openEditModal(preset)">
|
||||
<UButton color="gray" variant="ghost" @click="openEditModal(preset)">
|
||||
{{ preset.isBuiltIn ? '查看' : '编辑' }}
|
||||
</UButton>
|
||||
<UButton size="xs" color="gray" variant="ghost" @click="duplicatePreset(preset.id)">复制</UButton>
|
||||
<UButton v-if="!preset.isBuiltIn" size="xs" color="error" variant="ghost" @click="deletePreset(preset.id)">
|
||||
<UButton color="gray" variant="ghost" @click="duplicatePreset(preset.id)">复制</UButton>
|
||||
<UButton v-if="!preset.isBuiltIn" color="error" variant="ghost" @click="deletePreset(preset.id)">
|
||||
删除
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<div class="rounded-lg border border-gray-200 bg-gray-50 p-3 dark:border-gray-700 dark:bg-gray-800/50">
|
||||
<div class="flex items-start gap-2">
|
||||
<UIcon name="i-heroicons-light-bulb" class="mt-0.5 h-4 w-4 shrink-0 text-amber-500" />
|
||||
<p class="text-xs text-gray-600 dark:text-gray-400">
|
||||
点击预设即可激活。时间信息和工具说明由系统自动注入,你只需编辑角色定义和回答要求。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 编辑/添加弹窗 -->
|
||||
|
||||
@@ -32,8 +32,8 @@ const formData = ref({
|
||||
const isBuiltIn = computed(() => props.preset?.isBuiltIn ?? false)
|
||||
const isEditMode = computed(() => props.mode === 'edit')
|
||||
const modalTitle = computed(() => {
|
||||
if (isBuiltIn.value) return '查看预设'
|
||||
return isEditMode.value ? '编辑预设' : '添加预设'
|
||||
if (isBuiltIn.value) return '查看系统提示词'
|
||||
return isEditMode.value ? '编辑系统提示词' : '添加系统提示词'
|
||||
})
|
||||
|
||||
const canSave = computed(() => {
|
||||
@@ -149,7 +149,7 @@ function getLockedPromptSection(chatType: string): string {
|
||||
- 当用户提到"对方"、"他/她"时,通过 get_group_members 获取另一方信息`
|
||||
: `成员查询策略:
|
||||
- 当用户提到特定群成员(如"张三说过什么"、"小明的发言"等)时,应先调用 get_group_members 获取成员列表
|
||||
- 群成员有三种名称:accountName(QQ原始昵称)、groupNickname(群昵称)、aliases(用户自定义别名)
|
||||
- 群成员有三种名称:accountName(原始昵称)、groupNickname(群昵称)、aliases(用户自定义别名)
|
||||
- 通过 get_group_members 的 search 参数可以模糊搜索这三种名称
|
||||
- 找到成员后,使用其 id 字段作为 search_messages 的 sender_id 参数来获取该成员的发言`
|
||||
|
||||
@@ -213,12 +213,15 @@ ${formData.value.responseRules}`
|
||||
<!-- 预设名称 -->
|
||||
<div>
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">预设名称</label>
|
||||
<UInput v-model="formData.name" placeholder="为预设起个名字" :disabled="isBuiltIn" />
|
||||
<UInput v-model="formData.name" placeholder="为预设起个名字" :disabled="isBuiltIn" class="w-60" />
|
||||
</div>
|
||||
|
||||
<!-- 适用类型(只读显示) -->
|
||||
<div class="flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400">
|
||||
<UIcon :name="formData.chatType === 'group' ? 'i-heroicons-chat-bubble-left-right' : 'i-heroicons-user'" class="h-4 w-4" />
|
||||
<UIcon
|
||||
:name="formData.chatType === 'group' ? 'i-heroicons-chat-bubble-left-right' : 'i-heroicons-user'"
|
||||
class="h-4 w-4"
|
||||
/>
|
||||
<span>适用于{{ formData.chatType === 'group' ? '群聊' : '私聊' }}</span>
|
||||
</div>
|
||||
|
||||
@@ -227,10 +230,10 @@ ${formData.value.responseRules}`
|
||||
<label class="mb-1.5 block text-sm font-medium text-gray-700 dark:text-gray-300">角色定义</label>
|
||||
<UTextarea
|
||||
v-model="formData.roleDefinition"
|
||||
:rows="5"
|
||||
:rows="8"
|
||||
placeholder="定义 AI 助手的角色和任务..."
|
||||
:disabled="isBuiltIn"
|
||||
class="font-mono text-sm"
|
||||
class="font-mono text-sm w-120"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -245,7 +248,7 @@ ${formData.value.responseRules}`
|
||||
:rows="5"
|
||||
placeholder="定义 AI 回答的格式和要求..."
|
||||
:disabled="isBuiltIn"
|
||||
class="font-mono text-sm"
|
||||
class="font-mono text-sm w-120"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -256,7 +259,7 @@ ${formData.value.responseRules}`
|
||||
完整提示词预览
|
||||
</label>
|
||||
<div class="rounded-lg border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-800/50">
|
||||
<pre class="max-h-48 overflow-y-auto whitespace-pre-wrap text-xs text-gray-700 dark:text-gray-300">{{ previewContent }}</pre>
|
||||
<pre class="whitespace-pre-wrap text-sm text-gray-700 dark:text-gray-300">{{ previewContent }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -272,4 +275,3 @@ ${formData.value.responseRules}`
|
||||
</template>
|
||||
</UModal>
|
||||
</template>
|
||||
|
||||
|
||||
@@ -243,6 +243,12 @@ export function useAIChat(
|
||||
maxMessagesLimit: aiGlobalSettings.value.maxMessagesPerRequest,
|
||||
}
|
||||
|
||||
console.log('[AI] 构建 context:', {
|
||||
sessionId,
|
||||
maxMessagesLimit: context.maxMessagesLimit,
|
||||
aiGlobalSettings: aiGlobalSettings.value,
|
||||
})
|
||||
|
||||
// 收集历史消息(排除当前用户消息和 AI 占位消息)
|
||||
const historyMessages = messages.value
|
||||
.slice(0, -2) // 排除刚添加的用户消息和 AI 占位消息
|
||||
@@ -270,7 +276,10 @@ export function useAIChat(
|
||||
return
|
||||
}
|
||||
|
||||
console.log('[AI] Agent chunk:', chunk.type, chunk.toolName || chunk.content?.slice(0, 50))
|
||||
// 只在工具调用时记录,减少日志噪音
|
||||
if (chunk.type === 'tool_start' || chunk.type === 'tool_result') {
|
||||
console.log('[AI] Agent chunk:', chunk.type, chunk.toolName)
|
||||
}
|
||||
|
||||
switch (chunk.type) {
|
||||
case 'content':
|
||||
|
||||
@@ -0,0 +1,280 @@
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
|
||||
// Props
|
||||
defineProps<{
|
||||
open: boolean
|
||||
}>()
|
||||
|
||||
// Emits
|
||||
const emit = defineEmits<{
|
||||
'update:open': [value: boolean]
|
||||
}>()
|
||||
|
||||
// 通用格式弹窗状态
|
||||
const showFormatModal = ref(false)
|
||||
|
||||
// 复制格式示例
|
||||
const formatExample = `{
|
||||
"chatlab": {
|
||||
"version": "1.0.0",
|
||||
"exportedAt": 1732924800,
|
||||
"generator": "Your Tool Name"
|
||||
},
|
||||
"meta": {
|
||||
"name": "群聊名称",
|
||||
"platform": "qq",
|
||||
"type": "group"
|
||||
},
|
||||
"members": [
|
||||
{
|
||||
"platformId": "123456789",
|
||||
"accountName": "用户昵称",
|
||||
"groupNickname": "群昵称(可选)"
|
||||
}
|
||||
],
|
||||
"messages": [
|
||||
{
|
||||
"sender": "123456789",
|
||||
"accountName": "发送时昵称",
|
||||
"timestamp": 1732924800,
|
||||
"type": 0,
|
||||
"content": "消息内容"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
function copyFormatExample() {
|
||||
navigator.clipboard.writeText(formatExample)
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
emit('update:open', false)
|
||||
}
|
||||
|
||||
function openExternalLink(url: string) {
|
||||
window.open(url, '_blank')
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 导入教程弹窗 -->
|
||||
<UModal :open="open" @update:open="emit('update:open', $event)" :ui="{ content: 'md:w-full max-w-2xl' }">
|
||||
<template #content>
|
||||
<div class="p-6">
|
||||
<!-- Header -->
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-xl bg-linear-to-br from-pink-100 to-rose-100 dark:from-pink-900/30 dark:to-rose-900/30"
|
||||
>
|
||||
<UIcon name="i-heroicons-book-open" class="h-5 w-5 text-pink-600 dark:text-pink-400" />
|
||||
</div>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">聊天记录导入教程</h2>
|
||||
</div>
|
||||
<UButton icon="i-heroicons-x-mark" variant="ghost" size="sm" @click="closeModal" />
|
||||
</div>
|
||||
|
||||
<!-- 教程内容 -->
|
||||
<div class="space-y-6">
|
||||
<!-- QQ 教程 -->
|
||||
<div class="rounded-xl border border-gray-200 p-4 dark:border-gray-700">
|
||||
<div class="mb-3 flex items-center gap-2">
|
||||
<UIcon name="i-heroicons-chat-bubble-left-right" class="h-5 w-5 text-pink-500" />
|
||||
<h3 class="font-semibold text-gray-900 dark:text-white">QQ</h3>
|
||||
</div>
|
||||
<ol class="space-y-2">
|
||||
<li class="flex items-start gap-3 text-sm text-gray-600 dark:text-gray-300">
|
||||
<span
|
||||
class="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-pink-100 text-xs font-medium text-pink-600 dark:bg-pink-900/30 dark:text-pink-400"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
<span>
|
||||
使用
|
||||
<a
|
||||
class="cursor-pointer text-pink-600 underline underline-offset-2 hover:text-pink-700 dark:text-pink-400 dark:hover:text-pink-300"
|
||||
@click="openExternalLink('https://github.com/shuakami/qq-chat-exporter')"
|
||||
>
|
||||
qq-chat-exporter
|
||||
<UIcon name="i-heroicons-arrow-top-right-on-square" class="inline h-3 w-3" />
|
||||
</a>
|
||||
导出聊天记录(目前仅支持Windows/Linux)
|
||||
</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-3 text-sm text-gray-600 dark:text-gray-300">
|
||||
<span
|
||||
class="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-pink-100 text-xs font-medium text-pink-600 dark:bg-pink-900/30 dark:text-pink-400"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
<span>导出完成后会得到 .json 文件</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-3 text-sm text-gray-600 dark:text-gray-300">
|
||||
<span
|
||||
class="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-pink-100 text-xs font-medium text-pink-600 dark:bg-pink-900/30 dark:text-pink-400"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
<span>将 .json 文件拖拽到上方导入区域</span>
|
||||
</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
<!-- 微信教程 -->
|
||||
<div class="rounded-xl border border-gray-200 p-4 dark:border-gray-700">
|
||||
<div class="mb-3 flex items-center gap-2">
|
||||
<UIcon name="i-heroicons-device-phone-mobile" class="h-5 w-5 text-blue-500" />
|
||||
<h3 class="font-semibold text-gray-900 dark:text-white">微信</h3>
|
||||
</div>
|
||||
<ol class="mb-4 space-y-2">
|
||||
<li class="flex items-start gap-3 text-sm text-gray-600 dark:text-gray-300">
|
||||
<span
|
||||
class="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600 dark:bg-blue-900/30 dark:text-blue-400"
|
||||
>
|
||||
1
|
||||
</span>
|
||||
<span>网络上工具较多,请自行找工具导出聊天记录</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-3 text-sm text-gray-600 dark:text-gray-300">
|
||||
<span
|
||||
class="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600 dark:bg-blue-900/30 dark:text-blue-400"
|
||||
>
|
||||
2
|
||||
</span>
|
||||
<span>使用脚本,将导出文件转换为 ChatLab 通用格式</span>
|
||||
</li>
|
||||
<li class="flex items-start gap-3 text-sm text-gray-600 dark:text-gray-300">
|
||||
<span
|
||||
class="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-blue-100 text-xs font-medium text-blue-600 dark:bg-blue-900/30 dark:text-blue-400"
|
||||
>
|
||||
3
|
||||
</span>
|
||||
<span>将转换后的 .json 文件拖拽到上方导入区域</span>
|
||||
</li>
|
||||
</ol>
|
||||
<UButton
|
||||
variant="soft"
|
||||
size="sm"
|
||||
:trailing-icon="'i-heroicons-document-text'"
|
||||
@click="showFormatModal = true"
|
||||
>
|
||||
查看通用格式说明
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部提示 -->
|
||||
<div class="mt-6 rounded-lg bg-gray-50 p-4 dark:bg-gray-800/50">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
💡 提示:ChatLab 支持多种聊天记录格式,包括 QQ、微信、Discord
|
||||
等平台。将导出的文件直接拖拽到导入区域即可开始分析。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
|
||||
<!-- 通用格式说明弹窗(层级高于教程弹窗) -->
|
||||
<UModal v-model:open="showFormatModal" :ui="{ content: 'md:w-full max-w-3xl z-[60]', overlay: 'z-[60]' }">
|
||||
<template #content>
|
||||
<div class="p-6">
|
||||
<!-- Header -->
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-xl bg-linear-to-br from-blue-100 to-indigo-100 dark:from-blue-900/30 dark:to-indigo-900/30"
|
||||
>
|
||||
<UIcon name="i-heroicons-document-text" class="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">ChatLab 通用格式说明</h2>
|
||||
</div>
|
||||
<UButton icon="i-heroicons-x-mark" variant="ghost" size="sm" @click="showFormatModal = false" />
|
||||
</div>
|
||||
|
||||
<!-- 格式说明 -->
|
||||
<div class="space-y-4">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-300">
|
||||
ChatLab 定义了一套聊天记录分析用标准 JSON 格式。只需在 JSON 文件中包含
|
||||
<code class="rounded bg-gray-100 px-1.5 py-0.5 text-pink-600 dark:bg-gray-800 dark:text-pink-400">
|
||||
chatlab
|
||||
</code>
|
||||
对象即可被识别。
|
||||
</p>
|
||||
|
||||
<!-- JSON 示例 -->
|
||||
<div class="rounded-xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-800/50">
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<span class="text-xs font-medium text-gray-500 dark:text-gray-400">示例格式</span>
|
||||
<UButton variant="ghost" size="xs" icon="i-heroicons-clipboard-document" @click="copyFormatExample">
|
||||
复制
|
||||
</UButton>
|
||||
</div>
|
||||
<pre class="overflow-x-auto text-xs leading-relaxed text-gray-700 dark:text-gray-300"><code>{
|
||||
"chatlab": {
|
||||
"version": "1.0.0",
|
||||
"exportedAt": 1732924800,
|
||||
"generator": "Your Tool Name"
|
||||
},
|
||||
"meta": {
|
||||
"name": "群聊名称",
|
||||
"platform": "qq", // qq | wechat | telegram | discord 等
|
||||
"type": "group" // group | private (群聊|私聊)
|
||||
},
|
||||
"members": [
|
||||
{
|
||||
"platformId": "123456789",
|
||||
"accountName": "用户昵称",
|
||||
"groupNickname": "群昵称(可选)"
|
||||
}
|
||||
],
|
||||
"messages": [
|
||||
{
|
||||
"sender": "123456789",
|
||||
"accountName": "发送时昵称",
|
||||
"timestamp": 1732924800, // 秒级时间戳
|
||||
"type": 0, // 0=文本 1=图片 2=语音 3=视频
|
||||
"content": "消息内容"
|
||||
}
|
||||
]
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<!-- 字段说明 -->
|
||||
<div class="rounded-xl border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-800">
|
||||
<h3 class="mb-3 text-sm font-semibold text-gray-900 dark:text-white">消息类型说明</h3>
|
||||
<div class="grid grid-cols-2 gap-2 text-xs sm:grid-cols-4">
|
||||
<div class="rounded-lg bg-gray-50 p-2 dark:bg-gray-700">
|
||||
<span class="font-mono text-pink-600 dark:text-pink-400">0</span>
|
||||
<span class="ml-2 text-gray-600 dark:text-gray-300">文本</span>
|
||||
</div>
|
||||
<div class="rounded-lg bg-gray-50 p-2 dark:bg-gray-700">
|
||||
<span class="font-mono text-pink-600 dark:text-pink-400">1</span>
|
||||
<span class="ml-2 text-gray-600 dark:text-gray-300">图片</span>
|
||||
</div>
|
||||
<div class="rounded-lg bg-gray-50 p-2 dark:bg-gray-700">
|
||||
<span class="font-mono text-pink-600 dark:text-pink-400">2</span>
|
||||
<span class="ml-2 text-gray-600 dark:text-gray-300">语音</span>
|
||||
</div>
|
||||
<div class="rounded-lg bg-gray-50 p-2 dark:bg-gray-700">
|
||||
<span class="font-mono text-pink-600 dark:text-pink-400">3</span>
|
||||
<span class="ml-2 text-gray-600 dark:text-gray-300">视频</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部提示 -->
|
||||
<div class="mt-6 rounded-lg bg-blue-50 p-4 dark:bg-blue-900/20">
|
||||
<p class="text-sm text-blue-600 dark:text-blue-400">
|
||||
💡 文件名只需以
|
||||
<code class="rounded bg-blue-100 px-1 dark:bg-blue-800">.json</code>
|
||||
结尾,JSON 中包含
|
||||
<code class="rounded bg-blue-100 px-1 dark:bg-blue-800">chatlab</code>
|
||||
对象即可被识别。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
</template>
|
||||
@@ -4,13 +4,13 @@ import { FileDropZone } from '@/components/UI'
|
||||
import { storeToRefs } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import ImportTutorialModal from './components/ImportTutorialModal.vue'
|
||||
|
||||
const chatStore = useChatStore()
|
||||
const { isImporting, importProgress } = storeToRefs(chatStore)
|
||||
|
||||
const importError = ref<string | null>(null)
|
||||
const showTutorialModal = ref(false)
|
||||
const showFormatModal = ref(false)
|
||||
|
||||
const features = [
|
||||
{
|
||||
@@ -85,71 +85,10 @@ async function handleFileDrop({ paths }: { files: File[]; paths: string[] }) {
|
||||
}
|
||||
}
|
||||
|
||||
// 教程 Accordion 数据
|
||||
const tutorialItems = [
|
||||
{
|
||||
value: 'qq',
|
||||
label: 'QQ',
|
||||
icon: 'i-heroicons-chat-bubble-left-right',
|
||||
steps: [
|
||||
'使用 qq-chat-exporter 导出聊天记录(推荐最新版)',
|
||||
'导出完成后会得到 .json 文件',
|
||||
'将 .json 文件拖拽到上方导入区域',
|
||||
],
|
||||
link: 'https://github.com/shuakami/qq-chat-exporter',
|
||||
hasExternalLink: true,
|
||||
},
|
||||
{
|
||||
value: 'other',
|
||||
label: '其他平台',
|
||||
icon: 'i-heroicons-device-phone-mobile',
|
||||
steps: ['使用任意工具导出聊天记录', '将导出文件转换为 ChatLab 通用格式', '将转换后的 .json 文件拖拽到上方导入区域'],
|
||||
hasExternalLink: false,
|
||||
showFormatButton: true,
|
||||
},
|
||||
]
|
||||
|
||||
// 默认展开所有项
|
||||
const tutorialDefaultValue = tutorialItems.map((item) => item.value)
|
||||
|
||||
function openTutorial() {
|
||||
showTutorialModal.value = true
|
||||
}
|
||||
|
||||
// 复制格式示例
|
||||
const formatExample = `{
|
||||
"chatlab": {
|
||||
"version": "1.0.0",
|
||||
"exportedAt": 1732924800,
|
||||
"generator": "Your Tool Name"
|
||||
},
|
||||
"meta": {
|
||||
"name": "群聊名称",
|
||||
"platform": "qq",
|
||||
"type": "group"
|
||||
},
|
||||
"members": [
|
||||
{
|
||||
"platformId": "123456789",
|
||||
"accountName": "用户昵称",
|
||||
"groupNickname": "群昵称(可选)"
|
||||
}
|
||||
],
|
||||
"messages": [
|
||||
{
|
||||
"sender": "123456789",
|
||||
"accountName": "发送时昵称",
|
||||
"timestamp": 1732924800,
|
||||
"type": 0,
|
||||
"content": "消息内容"
|
||||
}
|
||||
]
|
||||
}`
|
||||
|
||||
function copyFormatExample() {
|
||||
window.electron.copyToClipboard(formatExample)
|
||||
}
|
||||
|
||||
function getProgressText(): string {
|
||||
if (!importProgress.value) return ''
|
||||
switch (importProgress.value.stage) {
|
||||
@@ -324,176 +263,7 @@ function getProgressDetail(): string {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 通用格式说明弹窗(层级高于教程弹窗) -->
|
||||
<UModal v-model:open="showFormatModal" :ui="{ content: 'md:w-full max-w-3xl z-[60]', overlay: 'z-[60]' }">
|
||||
<template #content>
|
||||
<div class="p-6">
|
||||
<!-- Header -->
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-xl bg-linear-to-br from-blue-100 to-indigo-100 dark:from-blue-900/30 dark:to-indigo-900/30"
|
||||
>
|
||||
<UIcon name="i-heroicons-document-text" class="h-5 w-5 text-blue-600 dark:text-blue-400" />
|
||||
</div>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">ChatLab 通用格式说明</h2>
|
||||
</div>
|
||||
<UButton icon="i-heroicons-x-mark" variant="ghost" size="sm" @click="showFormatModal = false" />
|
||||
</div>
|
||||
|
||||
<!-- 格式说明 -->
|
||||
<div class="space-y-4">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-300">
|
||||
ChatLab 支持通用的 JSON 格式。只需在 JSON 文件中包含
|
||||
<code class="rounded bg-gray-100 px-1.5 py-0.5 text-pink-600 dark:bg-gray-800 dark:text-pink-400">
|
||||
chatlab
|
||||
</code>
|
||||
对象即可被识别。
|
||||
</p>
|
||||
|
||||
<!-- JSON 示例 -->
|
||||
<div class="rounded-xl border border-gray-200 bg-gray-50 p-4 dark:border-gray-700 dark:bg-gray-800/50">
|
||||
<div class="mb-2 flex items-center justify-between">
|
||||
<span class="text-xs font-medium text-gray-500 dark:text-gray-400">示例格式</span>
|
||||
<UButton variant="ghost" size="xs" icon="i-heroicons-clipboard-document" @click="copyFormatExample">
|
||||
复制
|
||||
</UButton>
|
||||
</div>
|
||||
<pre class="overflow-x-auto text-xs leading-relaxed text-gray-700 dark:text-gray-300"><code>{
|
||||
"chatlab": {
|
||||
"version": "1.0.0",
|
||||
"exportedAt": 1732924800,
|
||||
"generator": "Your Tool Name"
|
||||
},
|
||||
"meta": {
|
||||
"name": "群聊名称",
|
||||
"platform": "qq", // qq | wechat | telegram | discord 等
|
||||
"type": "group" // group | private (群聊|私聊)
|
||||
},
|
||||
"members": [
|
||||
{
|
||||
"platformId": "123456789",
|
||||
"accountName": "用户昵称",
|
||||
"groupNickname": "群昵称(可选)"
|
||||
}
|
||||
],
|
||||
"messages": [
|
||||
{
|
||||
"sender": "123456789",
|
||||
"accountName": "发送时昵称",
|
||||
"timestamp": 1732924800, // 秒级时间戳
|
||||
"type": 0, // 0=文本 1=图片 2=语音 3=视频
|
||||
"content": "消息内容"
|
||||
}
|
||||
]
|
||||
}</code></pre>
|
||||
</div>
|
||||
|
||||
<!-- 字段说明 -->
|
||||
<div class="rounded-xl border border-gray-200 bg-white p-4 dark:border-gray-700 dark:bg-gray-800">
|
||||
<h3 class="mb-3 text-sm font-semibold text-gray-900 dark:text-white">消息类型说明</h3>
|
||||
<div class="grid grid-cols-2 gap-2 text-xs sm:grid-cols-4">
|
||||
<div class="rounded-lg bg-gray-50 p-2 dark:bg-gray-700">
|
||||
<span class="font-mono text-pink-600 dark:text-pink-400">0</span>
|
||||
<span class="ml-2 text-gray-600 dark:text-gray-300">文本</span>
|
||||
</div>
|
||||
<div class="rounded-lg bg-gray-50 p-2 dark:bg-gray-700">
|
||||
<span class="font-mono text-pink-600 dark:text-pink-400">1</span>
|
||||
<span class="ml-2 text-gray-600 dark:text-gray-300">图片</span>
|
||||
</div>
|
||||
<div class="rounded-lg bg-gray-50 p-2 dark:bg-gray-700">
|
||||
<span class="font-mono text-pink-600 dark:text-pink-400">2</span>
|
||||
<span class="ml-2 text-gray-600 dark:text-gray-300">语音</span>
|
||||
</div>
|
||||
<div class="rounded-lg bg-gray-50 p-2 dark:bg-gray-700">
|
||||
<span class="font-mono text-pink-600 dark:text-pink-400">3</span>
|
||||
<span class="ml-2 text-gray-600 dark:text-gray-300">视频</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 底部提示 -->
|
||||
<div class="mt-6 rounded-lg bg-blue-50 p-4 dark:bg-blue-900/20">
|
||||
<p class="text-sm text-blue-600 dark:text-blue-400">
|
||||
💡 文件名只需以
|
||||
<code class="rounded bg-blue-100 px-1 dark:bg-blue-800">.json</code>
|
||||
结尾,JSON 中包含
|
||||
<code class="rounded bg-blue-100 px-1 dark:bg-blue-800">chatlab</code>
|
||||
对象即可被识别。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
|
||||
<!-- 导入教程弹窗 -->
|
||||
<UModal v-model:open="showTutorialModal" :ui="{ content: 'md:w-full max-w-2xl' }">
|
||||
<template #content>
|
||||
<div class="p-6">
|
||||
<!-- Header -->
|
||||
<div class="mb-6 flex items-center justify-between">
|
||||
<div class="flex items-center gap-3">
|
||||
<div
|
||||
class="flex h-10 w-10 items-center justify-center rounded-xl bg-linear-to-br from-pink-100 to-rose-100 dark:from-pink-900/30 dark:to-rose-900/30"
|
||||
>
|
||||
<UIcon name="i-heroicons-book-open" class="h-5 w-5 text-pink-600 dark:text-pink-400" />
|
||||
</div>
|
||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">聊天记录导入教程</h2>
|
||||
</div>
|
||||
<UButton icon="i-heroicons-x-mark" variant="ghost" size="sm" @click="showTutorialModal = false" />
|
||||
</div>
|
||||
|
||||
<!-- 教程内容 - 使用 Accordion -->
|
||||
<UAccordion type="multiple" :default-value="tutorialDefaultValue" :items="tutorialItems">
|
||||
<template #body="{ item }">
|
||||
<!-- 步骤列表 -->
|
||||
<ol class="mb-4 space-y-2">
|
||||
<li
|
||||
v-for="(step, index) in item.steps"
|
||||
:key="index"
|
||||
class="flex items-start gap-3 text-sm text-gray-600 dark:text-gray-300"
|
||||
>
|
||||
<span
|
||||
class="flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-pink-100 text-xs font-medium text-pink-600 dark:bg-pink-900/30 dark:text-pink-400"
|
||||
>
|
||||
{{ index + 1 }}
|
||||
</span>
|
||||
<span>{{ step }}</span>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<!-- 工具链接 / 格式说明按钮 -->
|
||||
<UButton
|
||||
v-if="item.hasExternalLink"
|
||||
variant="soft"
|
||||
size="sm"
|
||||
:trailing-icon="'i-heroicons-arrow-top-right-on-square'"
|
||||
@click="window.electron.openExternal(item.link)"
|
||||
>
|
||||
查看导出工具
|
||||
</UButton>
|
||||
<UButton
|
||||
v-if="item.showFormatButton"
|
||||
variant="soft"
|
||||
size="sm"
|
||||
:trailing-icon="'i-heroicons-document-text'"
|
||||
@click="showFormatModal = true"
|
||||
>
|
||||
查看通用格式说明
|
||||
</UButton>
|
||||
</template>
|
||||
</UAccordion>
|
||||
|
||||
<!-- 底部提示 -->
|
||||
<div class="mt-6 rounded-lg bg-gray-50 p-4 dark:bg-gray-800/50">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400">
|
||||
💡 提示:ChatLab 支持多种聊天记录格式,包括 QQ、微信、Discord
|
||||
等平台。将导出的文件直接拖拽到导入区域即可开始分析。
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</UModal>
|
||||
<ImportTutorialModal v-model:open="showTutorialModal" />
|
||||
</div>
|
||||
</template>
|
||||
+1
-1
@@ -5,7 +5,7 @@ export const router = createRouter({
|
||||
{
|
||||
path: '/',
|
||||
name: 'home',
|
||||
component: () => import('@/pages/index.vue'),
|
||||
component: () => import('@/pages/home/index.vue'),
|
||||
},
|
||||
{
|
||||
path: '/group-chat/:id',
|
||||
|
||||
Reference in New Issue
Block a user