mirror of
https://github.com/hellodigua/ChatLab.git
synced 2026-10-26 02:04:37 +08:00
feat(ai): add fork conversation UI button on AI replies
This commit is contained in:
1 parent
6224ca996e
commit
e78c8f9368
8 files changed
+62
-5
No files matched your search
@@ -347,10 +347,10 @@ describe('AIConversationManager forkConversation', () => {
|
||||
assert.equal(forked.sessionId, 's1')
|
||||
|
||||
const forkedMessages = manager.getMessages(forked.id)
|
||||
assert.equal(forkedMessages.length, 2)
|
||||
assert.equal(forkedMessages.length, 3)
|
||||
assert.deepEqual(
|
||||
forkedMessages.map((m) => m.content),
|
||||
['q1', 'a1']
|
||||
['q1', 'a1', 'q2']
|
||||
)
|
||||
|
||||
// Original conversation should be untouched
|
||||
@@ -374,8 +374,11 @@ describe('AIConversationManager forkConversation', () => {
|
||||
|
||||
assert.equal(forked.title, 'MyChat (fork)')
|
||||
const forkedMessages = manager.getMessages(forked.id)
|
||||
assert.equal(forkedMessages.length, 1)
|
||||
assert.equal(forkedMessages[0]?.content, 'q1')
|
||||
assert.equal(forkedMessages.length, 2)
|
||||
assert.deepEqual(
|
||||
forkedMessages.map((m) => m.content),
|
||||
['q1', 'a1']
|
||||
)
|
||||
manager.close()
|
||||
} finally {
|
||||
cleanup(dir)
|
||||
|
||||
@@ -609,7 +609,7 @@ export class AIConversationManager {
|
||||
throw new Error('Message not on active path')
|
||||
}
|
||||
|
||||
const messagesToCopy = activePath.slice(0, cutIndex)
|
||||
const messagesToCopy = activePath.slice(0, cutIndex + 1)
|
||||
const now = Math.floor(Date.now() / 1000)
|
||||
const newConvId = this.generateId('conv')
|
||||
const forkTitle = title || `${source.title || 'Untitled'} (fork)`
|
||||
|
||||
@@ -259,6 +259,19 @@ async function handleEditMessage(payload: { messageId: string; content: string;
|
||||
conversationListRef.value?.refresh()
|
||||
}
|
||||
|
||||
async function handleForkConversation(messageId: string) {
|
||||
if (!currentConversationId.value) return
|
||||
try {
|
||||
const forked = await useAIService().forkConversation(currentConversationId.value, messageId)
|
||||
await loadConversation(forked.id)
|
||||
conversationListRef.value?.refresh()
|
||||
scrollToBottom(true)
|
||||
toast.success(t('ai.chat.fork.success'))
|
||||
} catch (error) {
|
||||
toast.fail(t('ai.chat.fork.failed'), { description: String(error) })
|
||||
}
|
||||
}
|
||||
|
||||
// 切换数据源面板
|
||||
function toggleSourcePanel() {
|
||||
isSourcePanelCollapsed.value = !isSourcePanelCollapsed.value
|
||||
@@ -444,11 +457,13 @@ watch(
|
||||
(pair.assistant.contentBlocks && pair.assistant.contentBlocks.length > 0))
|
||||
"
|
||||
:role="pair.assistant.role"
|
||||
:message-id="pair.assistant.id"
|
||||
:content="pair.assistant.content"
|
||||
:timestamp="pair.assistant.timestamp"
|
||||
:is-streaming="pair.assistant.isStreaming"
|
||||
:content-blocks="pair.assistant.contentBlocks"
|
||||
:show-capture-button="!pair.assistant.isStreaming"
|
||||
@fork="handleForkConversation"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -27,6 +27,7 @@ const props = defineProps<{
|
||||
|
||||
const emit = defineEmits<{
|
||||
edit: [payload: { messageId: string; content: string; overwriteSubsequent?: boolean }]
|
||||
fork: [messageId: string]
|
||||
}>()
|
||||
|
||||
// 格式化时间
|
||||
@@ -41,6 +42,7 @@ const isEditing = ref(false)
|
||||
const editContent = ref(props.content)
|
||||
const editTextareaRef = ref<HTMLTextAreaElement | null>(null)
|
||||
const canEdit = computed(() => isUser.value && props.editable && !props.isStreaming && !!props.messageId)
|
||||
const canFork = computed(() => !isUser.value && !isSummary.value && !props.isStreaming && !!props.messageId)
|
||||
const overwriteSubsequent = ref(false)
|
||||
|
||||
// 创建 markdown-it 实例
|
||||
@@ -574,6 +576,15 @@ async function handleCopyMarkdown() {
|
||||
<UTooltip v-if="canEdit" :text="t('ai.chat.message.edit.tooltip')" class="no-capture">
|
||||
<UButton icon="i-heroicons-pencil-square" variant="ghost" color="primary" size="xs" @click="startEditing" />
|
||||
</UTooltip>
|
||||
<UTooltip v-if="canFork" :text="t('ai.chat.message.fork.tooltip')" class="no-capture">
|
||||
<UButton
|
||||
icon="i-heroicons-arrow-top-right-on-square"
|
||||
variant="ghost"
|
||||
color="primary"
|
||||
size="xs"
|
||||
@click="emit('fork', messageId!)"
|
||||
/>
|
||||
</UTooltip>
|
||||
<!-- 截屏按钮(仅 AI 回复显示) -->
|
||||
<CaptureButton
|
||||
v-if="showCaptureButton && !isUser && !isStreaming"
|
||||
|
||||
@@ -38,6 +38,10 @@
|
||||
"needMoreThanSkill": "Selecting a skill alone is not enough. Enter a specific question.",
|
||||
"needQuestion": "Enter a question before sending"
|
||||
},
|
||||
"fork": {
|
||||
"success": "Conversation forked",
|
||||
"failed": "Failed to fork conversation"
|
||||
},
|
||||
"backgroundTask": {
|
||||
"runningShort": "Running",
|
||||
"runningTitle": "AI is still thinking",
|
||||
@@ -58,6 +62,9 @@
|
||||
"submit": "Send",
|
||||
"overwriteSubsequent": "Overwrite all subsequent messages"
|
||||
},
|
||||
"fork": {
|
||||
"tooltip": "Fork conversation from here"
|
||||
},
|
||||
"tools": {
|
||||
"get_chat_overview": "Get Chat Overview",
|
||||
"search_messages": "Search Messages",
|
||||
|
||||
@@ -38,6 +38,10 @@
|
||||
"needMoreThanSkill": "スキルを選んだだけでは送信できません。具体的な質問を入力してください。",
|
||||
"needQuestion": "質問を入力してから送信してください"
|
||||
},
|
||||
"fork": {
|
||||
"success": "会話をフォークしました",
|
||||
"failed": "会話のフォークに失敗しました"
|
||||
},
|
||||
"backgroundTask": {
|
||||
"runningShort": "進行中",
|
||||
"runningTitle": "AI がバックグラウンドで推論中です",
|
||||
@@ -58,6 +62,9 @@
|
||||
"submit": "送信",
|
||||
"overwriteSubsequent": "以降のすべての会話を上書き"
|
||||
},
|
||||
"fork": {
|
||||
"tooltip": "ここから会話をフォーク"
|
||||
},
|
||||
"tools": {
|
||||
"get_chat_overview": "チャット概要を取得",
|
||||
"search_messages": "チャット履歴を検索",
|
||||
|
||||
@@ -38,6 +38,10 @@
|
||||
"needMoreThanSkill": "仅选择技能还不能发送,请继续输入具体问题",
|
||||
"needQuestion": "请输入问题后再发送"
|
||||
},
|
||||
"fork": {
|
||||
"success": "已复刻对话",
|
||||
"failed": "复刻对话失败"
|
||||
},
|
||||
"backgroundTask": {
|
||||
"runningShort": "进行中",
|
||||
"runningTitle": "AI 正在后台推理",
|
||||
@@ -58,6 +62,9 @@
|
||||
"submit": "发送",
|
||||
"overwriteSubsequent": "覆盖后续所有对话"
|
||||
},
|
||||
"fork": {
|
||||
"tooltip": "从此处复刻对话"
|
||||
},
|
||||
"tools": {
|
||||
"get_chat_overview": "获取聊天概览",
|
||||
"search_messages": "搜索聊天记录",
|
||||
|
||||
@@ -38,6 +38,10 @@
|
||||
"needMoreThanSkill": "只選擇技能還不能送出,請繼續輸入具體問題",
|
||||
"needQuestion": "請輸入問題後再送出"
|
||||
},
|
||||
"fork": {
|
||||
"success": "已複製對話",
|
||||
"failed": "複製對話失敗"
|
||||
},
|
||||
"backgroundTask": {
|
||||
"runningShort": "進行中",
|
||||
"runningTitle": "AI 正在背景推理",
|
||||
@@ -58,6 +62,9 @@
|
||||
"submit": "送出",
|
||||
"overwriteSubsequent": "覆蓋後續所有對話"
|
||||
},
|
||||
"fork": {
|
||||
"tooltip": "從此處複製對話"
|
||||
},
|
||||
"tools": {
|
||||
"get_chat_overview": "取得聊天概覽",
|
||||
"search_messages": "搜尋聊天紀錄",
|
||||
|
||||
Reference in new issue
Block a user