Files
assistant/composables/useConversation.uts

260 lines
13 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* useConversation — Active conversation management
*
* The core business logic composable:
* - Manages the currently active conversation
* - Handles sending messages and receiving mock AI responses
* - Auto-generates workspace items for substantial responses
* - Coordinates between useConversations, useWorkflow, and useWorkspace
*/
import type { Message, WorkflowId, Conversation, WorkspaceItem } from '@/types/index'
import { STORAGE_KEYS, generateId, MOCK_AI_DELAY_MS } from '@/constants/index'
import { useStorage } from './useStorage'
// Mock AI response templates per workflow
const AI_RESPONSES: Record<WorkflowId, string[]> = {
general: [
'这是一个很好的问题!让我来为你详细解答。\n\n首先我们需要理解核心概念。人工智能正在快速发展已经渗透到我们生活的方方面面。\n\n**关键要点:**\n- 保持好奇心,不断学习新知识\n- 实践是检验真理的唯一标准\n- 多角度思考问题能带来更好的解决方案\n\n希望这些对你有所帮助还有其他问题吗',
'感谢你的提问!关于这个问题,我有以下几点建议:\n\n1. **明确目标**:先确定你想要达到的具体结果\n2. **制定计划**:把大目标拆分成小步骤\n3. **持续迭代**:在执行过程中不断优化调整\n\n如果你能提供更多具体信息我可以给出更具针对性的建议。',
'让我从几个角度来分析这个问题:\n\n从技术层面看这涉及到系统的整体架构设计。从用户体验角度来说简洁直观的交互至关重要。\n\n综合考虑我建议采用渐进式的方法先验证核心功能再逐步扩展。你觉得这个思路如何',
],
code: [
'好的,让我来帮你解决这个编程问题。\n\n```\nfunction solve(arr: number[]): number {\n const n = arr.length\n let result = 0\n for (let i = 0; i < n; i++) {\n result += arr[i]\n }\n return result\n}\n```\n\n这段代码的时间复杂度是 O(n),空间复杂度是 O(1)。\n\n**注意事项:**\n- 记得处理边界情况(空数组)\n- 可以考虑使用 reduce 方法简化代码\n- 如果需要处理大数,注意整数溢出问题\n\n还有什么需要我帮忙的吗',
'这是一个经典的设计模式问题。我推荐使用以下架构:\n\n```\nclass EventBus {\n private handlers: Map<string, Function[]>\n \n on(event: string, handler: Function) {\n // 注册事件处理器\n }\n \n emit(event: string, data: any) {\n // 触发事件\n }\n}\n```\n\n这种发布-订阅模式可以有效解耦模块间的依赖,让代码更具可维护性。',
'让我看看你的代码逻辑...\n\n问题在于异步操作的处理。你需要使用 `await` 来等待 Promise 完成:\n\n```\nasync function fetchData(url: string) {\n try {\n const response = await fetch(url)\n const data = await response.json()\n return data\n } catch (error) {\n console.error(\'Failed to fetch:\', error)\n throw error\n }\n}\n```\n\n另外建议添加错误处理和重试机制提高代码的健壮性。',
],
document: [
'好的,我来帮你撰写这篇文档。\n\n---\n\n**引言**\n\n在当今快速发展的数字化时代企业面临着前所未有的机遇与挑战。如何有效利用技术手段提升业务效率已成为每个管理者必须思考的问题。\n\n**核心观点**\n\n- 数字化转型不仅仅是技术升级,更是思维方式的转变\n- 数据驱动决策能够显著提升业务精准度\n- 用户体验应始终放在产品设计的首位\n\n**总结**\n\n通过系统化的方法推进数字化转型企业能够在竞争激烈的市场中保持领先地位。\n\n---\n\n以上是初稿你觉得需要调整什么方向吗',
'帮你润色了一下这段文字:\n\n**原文**:这个产品很好用,功能很多,界面也好看。\n\n**润色后**:这款产品表现优秀,不仅功能丰富全面,界面设计也简洁美观,为用户带来了出色的使用体验。\n\n**修改要点:**\n- 用词更加正式专业\n- 句式更加丰富多变\n- 增强了文字的感染力\n\n需要继续优化其他内容吗',
],
data: [
'根据你提供的数据,我进行了详细分析:\n\n📊 **数据概览**\n\n| 指标 | 当前值 | 环比变化 |\n|------|--------|----------|\n| 用户数 | 12,580 | +8.3% |\n| 活跃率 | 67.2% | +2.1% |\n| 转化率 | 3.8% | -0.5% |\n\n🔍 **关键洞察**\n\n1. 用户增长保持良好势头,增长率超过行业平均水平\n2. 活跃率稳步提升,说明产品粘性增强\n3. 转化率略有下降,需要关注转化漏斗的优化\n\n💡 **建议措施**\n\n- 针对转化率问题,优化注册流程\n- 分析高活跃用户的行为特征并复制推广\n- 关注用户留存曲线的变化趋势\n\n需要我深入分析某个具体指标吗',
'让我从数据角度来分析这个趋势:\n\n**相关性分析结果:**\n- 变量A与变量B的相关系数0.87(强正相关)\n- 变量C与结果的相关系数-0.42(中等负相关)\n\n这意味着当A增加时B也倾向于增加。而C的增加反而会抑制结果的提升。\n\n**统计显著性**p值 < 0.01,结果具有统计意义。\n\n建议重点关注A和B的关系这可能是一个关键的驱动因素。',
],
creative: [
'好的,让我来一场头脑风暴!🎯\n\n**方案一:社交裂变**\n利用用户的社交网络通过分享激励机制实现病毒式传播。核心是设计一个让用户"忍不住想分享"的体验。\n\n**方案二内容IP化**\n打造独特的品牌IP形象通过故事化内容与用户建立情感连接。IP可以出现在产品各个触点。\n\n**方案三:场景化营销**\n深入用户真实使用场景在不同场景下提供定制化的解决方案。让产品"恰好"出现在用户需要的时候。\n\n💡 我个人最看好方案二的长期价值,但方案一能在短期内带来快速增长。你觉得哪个方向更符合你的目标?',
'让我们跳出常规思维,重新构想这个问题的解决方案!\n\n想象一下如果完全没有技术和资源的限制理想的解决方案会是什么样\n\n从这个"理想态"往回推导:\n1. 理想体验的核心要素是什么?\n2. 哪些要素可以通过现有技术实现?\n3. 哪些需要创新的替代方案?\n\n这种"反向思维"往往能激发出意想不到的创意。比如Airbnb就是通过"让陌生人住进家里"这个看似疯狂的创意颠覆了酒店行业。\n\n你有什么初步的想法吗我们一起碰撞出更多灵感',
],
}
/**
* @param conversationsApi — functions from useConversations(): getConversation, saveConversation, createConversation
* @param getActiveWorkflowId — getter function that returns the current active workflow ID from useWorkflow()
* @param addWorkspaceItemFn — function from useWorkspace() to add workspace items.
* All are passed in as dependencies so the same state is shared with the page.
*/
export function useConversation(
conversationsApi: {
getConversation: (id: string) => Conversation | undefined
saveConversation: (conv: Conversation) => void
createConversation: (workflowId: WorkflowId, title?: string) => Conversation
},
getActiveWorkflowId: () => WorkflowId,
addWorkspaceItemFn?: (item: WorkspaceItem) => void
) {
const { loadValue, saveValue } = useStorage()
const { getConversation, saveConversation, createConversation } = conversationsApi
// Fallback no-op if not provided
const addWorkspaceItem = addWorkspaceItemFn ?? (() => {})
// Active conversation ID
const activeConversationId = ref<string | null>(
loadValue<string | null>(STORAGE_KEYS.ACTIVE_CONVERSATION_ID, null)
)
// Whether the AI is currently "typing"
const isLoading = ref(false)
/**
* Messages of the currently active conversation (computed).
*/
const messages = computed<Message[]>(() => {
if (!activeConversationId.value) return []
const conv = getConversation(activeConversationId.value)
return conv ? conv.messages : []
})
/**
* Set the active conversation ID and persist.
*/
function setActiveConversationId(id: string | null): void {
activeConversationId.value = id
if (id) {
saveValue(STORAGE_KEYS.ACTIVE_CONVERSATION_ID, id)
} else {
saveValue(STORAGE_KEYS.ACTIVE_CONVERSATION_ID, null)
}
}
/**
* Start a new conversation with the given workflow.
*/
function startNewConversation(workflowId: WorkflowId): void {
const conv = createConversation(workflowId)
setActiveConversationId(conv.id)
isLoading.value = false
}
/**
* Load an existing conversation by ID.
*/
function loadConversation(id: string): void {
const conv = getConversation(id)
if (conv) {
setActiveConversationId(id)
isLoading.value = false
}
}
/**
* Send a message from the user.
* Adds the user message, then triggers a mock AI response after a delay.
*/
function sendMessage(content: string): void {
if (!content.trim() || isLoading.value) return
// Ensure we have an active conversation
if (!activeConversationId.value) {
startNewConversation(getActiveWorkflowId())
}
const convId: string = activeConversationId.value ?? ''
if (!convId) return
const conv = getConversation(convId)
if (!conv) {
startNewConversation(getActiveWorkflowId())
// Retry after creating
sendMessage(content)
return
}
// Add user message
const userMessage: Message = {
id: generateId(),
role: 'user',
content: content.trim(),
timestamp: Date.now(),
status: 'sent',
}
conv.messages.push(userMessage)
conv.updatedAt = Date.now()
// Auto-title: use first user message as conversation title
if (conv.messages.filter((m) => m.role === 'user').length === 1) {
let title = content.trim()
if (title.length > 20) {
title = title.substring(0, 20) + '...'
}
conv.title = title
}
saveConversation(conv)
// Trigger mock AI response
isLoading.value = true
setTimeout(() => {
simulateAiResponse(conv)
}, MOCK_AI_DELAY_MS)
}
/**
* Retry sending a message (for error recovery).
*/
function retryMessage(messageId: string): void {
const conv = getConversation(activeConversationId.value ?? '')
if (!conv) return
const msg = conv.messages.find((m) => m.id === messageId)
if (msg) {
msg.status = 'sent'
saveConversation(conv)
}
}
/**
* Simulate an AI response.
* In production, replace this with a real API call.
*/
function simulateAiResponse(conv: Conversation): void {
const responses = AI_RESPONSES[conv.workflowId] ?? AI_RESPONSES['general']
const responseContent = responses[Math.floor(Math.random() * responses.length)]
const aiMessage: Message = {
id: generateId(),
role: 'assistant',
content: responseContent,
timestamp: Date.now(),
status: 'sent',
}
// Get fresh reference in case conversations array changed
const freshConv = getConversation(conv.id)
if (!freshConv) {
isLoading.value = false
return
}
freshConv.messages.push(aiMessage)
freshConv.updatedAt = Date.now()
saveConversation(freshConv)
// Auto-generate workspace item for substantial responses
if (conv.workflowId !== 'general') {
generateWorkspaceItem(freshConv, aiMessage)
}
isLoading.value = false
}
/**
* Generate a workspace item from an AI response.
*/
function generateWorkspaceItem(conv: Conversation, message: Message): void {
const typeMap: Record<string, string> = {
code: 'code_snippet',
document: 'document',
data: 'analysis_report',
creative: 'creative_output',
}
const titles: Record<string, string> = {
code: '代码片段',
document: '文档草稿',
data: '分析报告',
creative: '创意方案',
}
const itemType = (typeMap[conv.workflowId] ?? 'document') as import('@/types/index').WorkspaceItemType
const autoTitle = titles[conv.workflowId] ?? '工作结果'
// Only create if response is long enough to be "substantial"
if (message.content.length < 50) return
addWorkspaceItem({
id: generateId(),
conversationId: conv.id,
workflowId: conv.workflowId,
type: itemType,
title: `${autoTitle} - ${conv.title}`,
content: message.content,
summary: message.content.substring(0, 120) + '...',
createdAt: Date.now(),
})
}
return {
activeConversationId,
messages,
isLoading,
sendMessage,
retryMessage,
loadConversation,
startNewConversation,
setActiveConversationId,
}
}