260 lines
13 KiB
Plaintext
260 lines
13 KiB
Plaintext
/**
|
||
* 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,
|
||
}
|
||
}
|