commit 3fc394921e2650910c43984ad1390c99f62073ad Author: 2910410219 <2910410219@qq.com> Date: Fri Jun 12 17:23:00 2026 +0800 Initial commit: AI chat assistant with workflow chat, workspace, and profile tabs Co-Authored-By: Claude diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2370852 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +unpackage/ +.hbuilderx/ +.DS_Store diff --git a/App.uvue b/App.uvue new file mode 100644 index 0000000..da20992 --- /dev/null +++ b/App.uvue @@ -0,0 +1,70 @@ + + + diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..7dde7ea --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,67 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a **uni-app x** project — DCloud's cross-platform app framework that compiles to native Android, iOS, HarmonyOS, H5, and various mini-programs. The `assistant` directory is the project root. + +- **Language**: UTS (uni-app TypeScript) — a TypeScript superset that compiles to platform-native code +- **UI Framework**: Vue 3 with Composition API (` + + diff --git a/components/BottomTabBar.uvue b/components/BottomTabBar.uvue new file mode 100644 index 0000000..1333994 --- /dev/null +++ b/components/BottomTabBar.uvue @@ -0,0 +1,93 @@ + + + + + diff --git a/components/DrawerHeader.uvue b/components/DrawerHeader.uvue new file mode 100644 index 0000000..ad5c1e3 --- /dev/null +++ b/components/DrawerHeader.uvue @@ -0,0 +1,57 @@ + + + + + diff --git a/components/HistoryItem.uvue b/components/HistoryItem.uvue new file mode 100644 index 0000000..8859cee --- /dev/null +++ b/components/HistoryItem.uvue @@ -0,0 +1,149 @@ + + + + + diff --git a/components/HistoryList.uvue b/components/HistoryList.uvue new file mode 100644 index 0000000..6b028ba --- /dev/null +++ b/components/HistoryList.uvue @@ -0,0 +1,85 @@ + + + + + diff --git a/components/InputArea.uvue b/components/InputArea.uvue new file mode 100644 index 0000000..01f6680 --- /dev/null +++ b/components/InputArea.uvue @@ -0,0 +1,126 @@ + + + + + diff --git a/components/MessageBubble.uvue b/components/MessageBubble.uvue new file mode 100644 index 0000000..4c376d7 --- /dev/null +++ b/components/MessageBubble.uvue @@ -0,0 +1,288 @@ + + + + + diff --git a/components/MessageList.uvue b/components/MessageList.uvue new file mode 100644 index 0000000..da23095 --- /dev/null +++ b/components/MessageList.uvue @@ -0,0 +1,175 @@ + + + + + diff --git a/components/ProfilePanel.uvue b/components/ProfilePanel.uvue new file mode 100644 index 0000000..59f1638 --- /dev/null +++ b/components/ProfilePanel.uvue @@ -0,0 +1,251 @@ + + + + + diff --git a/components/TopBar.uvue b/components/TopBar.uvue new file mode 100644 index 0000000..c3a31a6 --- /dev/null +++ b/components/TopBar.uvue @@ -0,0 +1,129 @@ + + + + + diff --git a/components/TypingIndicator.uvue b/components/TypingIndicator.uvue new file mode 100644 index 0000000..d95ce52 --- /dev/null +++ b/components/TypingIndicator.uvue @@ -0,0 +1,53 @@ + + + + + diff --git a/components/WorkflowChip.uvue b/components/WorkflowChip.uvue new file mode 100644 index 0000000..8b00877 --- /dev/null +++ b/components/WorkflowChip.uvue @@ -0,0 +1,65 @@ + + + + + diff --git a/components/WorkflowSelector.uvue b/components/WorkflowSelector.uvue new file mode 100644 index 0000000..9259d9e --- /dev/null +++ b/components/WorkflowSelector.uvue @@ -0,0 +1,67 @@ + + + + + diff --git a/components/WorkspaceItem.uvue b/components/WorkspaceItem.uvue new file mode 100644 index 0000000..8bf472c --- /dev/null +++ b/components/WorkspaceItem.uvue @@ -0,0 +1,132 @@ + + + + + diff --git a/components/WorkspaceView.uvue b/components/WorkspaceView.uvue new file mode 100644 index 0000000..a5bb3a9 --- /dev/null +++ b/components/WorkspaceView.uvue @@ -0,0 +1,81 @@ + + + + + diff --git a/composables/useConversation.uts b/composables/useConversation.uts new file mode 100644 index 0000000..5ff4fa9 --- /dev/null +++ b/composables/useConversation.uts @@ -0,0 +1,259 @@ +/** + * 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 = { + 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\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( + loadValue(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(() => { + 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 = { + code: 'code_snippet', + document: 'document', + data: 'analysis_report', + creative: 'creative_output', + } + + const titles: Record = { + 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, + } +} diff --git a/composables/useConversations.uts b/composables/useConversations.uts new file mode 100644 index 0000000..86907b3 --- /dev/null +++ b/composables/useConversations.uts @@ -0,0 +1,93 @@ +/** + * useConversations — Conversation CRUD and history list management + * + * Manages the full array of all conversations. Persisted to storage. + * Provides create, delete, update, and query operations. + */ + +import type { Conversation, WorkflowId } from '@/types/index' +import { STORAGE_KEYS, generateId } from '@/constants/index' +import { useStorage } from './useStorage' + +export function useConversations() { + const { loadValue, saveValue } = useStorage() + + // All conversations loaded from persistence + const conversations = ref( + loadValue(STORAGE_KEYS.CONVERSATIONS, []) + ) + + /** + * Persist the current conversations array to storage. + */ + function persist(): void { + saveValue(STORAGE_KEYS.CONVERSATIONS, conversations.value) + } + + /** + * Create a new empty conversation with the given workflow. + */ + function createConversation(workflowId: WorkflowId, title?: string): Conversation { + const now = Date.now() + const conversation: Conversation = { + id: generateId(), + title: title ?? '新对话', + workflowId: workflowId, + messages: [], + createdAt: now, + updatedAt: now, + } + conversations.value.unshift(conversation) + persist() + return conversation + } + + /** + * Delete a conversation by ID. + */ + function deleteConversation(id: string): void { + conversations.value = conversations.value.filter((c) => c.id !== id) + persist() + } + + /** + * Update a conversation's title. + */ + function updateConversationTitle(id: string, title: string): void { + const conv = conversations.value.find((c) => c.id === id) + if (conv) { + conv.title = title + conv.updatedAt = Date.now() + persist() + } + } + + /** + * Get a conversation by ID. + */ + function getConversation(id: string): Conversation | undefined { + return conversations.value.find((c) => c.id === id) + } + + /** + * Update a conversation's messages and timestamp. + * Used after adding/removing messages. + */ + function saveConversation(conversation: Conversation): void { + const index = conversations.value.findIndex((c) => c.id === conversation.id) + if (index !== -1) { + conversations.value[index] = conversation + persist() + } + } + + return { + conversations, + createConversation, + deleteConversation, + updateConversationTitle, + getConversation, + saveConversation, + persist, + } +} diff --git a/composables/useDrawer.uts b/composables/useDrawer.uts new file mode 100644 index 0000000..40bdb3c --- /dev/null +++ b/composables/useDrawer.uts @@ -0,0 +1,29 @@ +/** + * useDrawer — Drawer open/close state management + * + * Controls the left-slide drawer that contains history and workspace views. + * Simple boolean toggle — no persistence needed. + */ + +export function useDrawer() { + const isDrawerOpen = ref(false) + + function openDrawer(): void { + isDrawerOpen.value = true + } + + function closeDrawer(): void { + isDrawerOpen.value = false + } + + function toggleDrawer(): void { + isDrawerOpen.value = !isDrawerOpen.value + } + + return { + isDrawerOpen, + openDrawer, + closeDrawer, + toggleDrawer, + } +} diff --git a/composables/useStorage.uts b/composables/useStorage.uts new file mode 100644 index 0000000..7fe96dc --- /dev/null +++ b/composables/useStorage.uts @@ -0,0 +1,53 @@ +/** + * useStorage — Typed persistence wrapper around uni.getStorageSync/setStorageSync + */ + +export function useStorage() { + + /** + * Load a value from storage with a fallback default. + * Returns fallback if key doesn't exist or JSON parsing fails. + */ + function loadValue(key: string, fallback: T): T { + try { + const raw = uni.getStorageSync(key) + if (raw === '' || raw === undefined || raw === null) { + return fallback + } + const parsed = JSON.parse(raw) as T + return parsed !== null ? parsed : fallback + } catch (_e) { + console.warn(`[useStorage] Failed to load key "${key}", using fallback`) + return fallback + } + } + + /** + * Save a value to storage as JSON. + */ + function saveValue(key: string, value: T): void { + try { + const raw = JSON.stringify(value) + uni.setStorageSync(key, raw) + } catch (e) { + console.error(`[useStorage] Failed to save key "${key}":`, e) + } + } + + /** + * Remove a key from storage. + */ + function removeValue(key: string): void { + try { + uni.removeStorageSync(key) + } catch (e) { + console.error(`[useStorage] Failed to remove key "${key}":`, e) + } + } + + return { + loadValue, + saveValue, + removeValue, + } +} diff --git a/composables/useWorkflow.uts b/composables/useWorkflow.uts new file mode 100644 index 0000000..4e0e7c8 --- /dev/null +++ b/composables/useWorkflow.uts @@ -0,0 +1,43 @@ +/** + * useWorkflow — Current workflow selection state + * + * Manages which workflow is active (general, code, document, data, creative). + * The selection is persisted so it survives app restarts. + */ + +import type { WorkflowId, WorkflowDef } from '@/types/index' +import { WORKFLOWS, STORAGE_KEYS } from '@/constants/index' +import { useStorage } from './useStorage' + +export function useWorkflow() { + const { loadValue, saveValue } = useStorage() + + // Initialize from storage, fallback to 'general' + const activeWorkflowId = ref( + loadValue(STORAGE_KEYS.ACTIVE_WORKFLOW_ID, 'general') + ) + + // All available workflows (static) + const workflows: WorkflowDef[] = WORKFLOWS + + // Computed: the currently active workflow definition + const activeWorkflow = computed(() => { + const found = workflows.find((w) => w.id === activeWorkflowId.value) + return found ?? workflows[0] + }) + + /** + * Select a workflow and persist the choice. + */ + function selectWorkflow(id: WorkflowId): void { + activeWorkflowId.value = id + saveValue(STORAGE_KEYS.ACTIVE_WORKFLOW_ID, id) + } + + return { + activeWorkflowId, + workflows, + activeWorkflow, + selectWorkflow, + } +} diff --git a/composables/useWorkspace.uts b/composables/useWorkspace.uts new file mode 100644 index 0000000..643b3ff --- /dev/null +++ b/composables/useWorkspace.uts @@ -0,0 +1,69 @@ +/** + * useWorkspace — Workspace items management + * + * Manages workspace items generated from AI workflow responses. + * Items are persisted and filterable by workflow type. + */ + +import type { WorkspaceItem, WorkflowId } from '@/types/index' +import { STORAGE_KEYS } from '@/constants/index' +import { useStorage } from './useStorage' + +export function useWorkspace() { + const { loadValue, saveValue } = useStorage() + + const workspaceItems = ref( + loadValue(STORAGE_KEYS.WORKSPACE, []) + ) + + function persist(): void { + saveValue(STORAGE_KEYS.WORKSPACE, workspaceItems.value) + } + + /** + * Add a new workspace item. + */ + function addWorkspaceItem(item: WorkspaceItem): void { + workspaceItems.value.unshift(item) + persist() + } + + /** + * Remove a workspace item by ID. + */ + function removeWorkspaceItem(id: string): void { + workspaceItems.value = workspaceItems.value.filter((item) => item.id !== id) + persist() + } + + /** + * Get workspace items filtered by workflow type. + */ + function getItemsByWorkflow(workflowId: WorkflowId): WorkspaceItem[] { + return workspaceItems.value.filter((item) => item.workflowId === workflowId) + } + + /** + * Get workspace item by ID. + */ + function getItemById(id: string): WorkspaceItem | undefined { + return workspaceItems.value.find((item) => item.id === id) + } + + /** + * Clear all workspace items. + */ + function clearAll(): void { + workspaceItems.value = [] + persist() + } + + return { + workspaceItems, + addWorkspaceItem, + removeWorkspaceItem, + getItemsByWorkflow, + getItemById, + clearAll, + } +} diff --git a/constants/index.uts b/constants/index.uts new file mode 100644 index 0000000..2ed67db --- /dev/null +++ b/constants/index.uts @@ -0,0 +1,65 @@ +/** + * AI Chat Assistant — Constants + */ + +import type { WorkflowDef } from '@/types/index' + +// Workflow definitions +export const WORKFLOWS: WorkflowDef[] = [ + { + id: 'general', + name: '通用对话', + icon: '💬', + description: '自由提问,获取AI智能回答', + color: '#6c5ce7', + }, + { + id: 'code', + name: '代码助手', + icon: '💻', + description: '编程问题解答、代码生成与调试', + color: '#00b894', + }, + { + id: 'document', + name: '文档写作', + icon: '📝', + description: '文章撰写、润色与总结', + color: '#fdcb6e', + }, + { + id: 'data', + name: '数据分析', + icon: '📊', + description: '数据处理、图表解读与洞察分析', + color: '#e17055', + }, + { + id: 'creative', + name: '创意生成', + icon: '✨', + description: '头脑风暴、创意点子与方案策划', + color: '#a29bfe', + }, +] + +// Storage keys +export const STORAGE_KEYS = { + CONVERSATIONS: 'asst_conversations', + WORKSPACE: 'asst_workspace', + ACTIVE_CONVERSATION_ID: 'asst_active_conv_id', + ACTIVE_WORKFLOW_ID: 'asst_workflow_id', +} as const + +// App settings +export const MOCK_AI_DELAY_MS = 1500 +export const MAX_INPUT_LENGTH = 2000 +export const APP_NAME = 'AI助手' +export const APP_SUBTITLE = '智能工作助手' + +// Generate a simple unique ID +export function generateId(): string { + const timestamp = Date.now().toString(36) + const random = Math.floor(Math.random() * 100000).toString(36) + return `${timestamp}${random}` +} diff --git a/index.html b/index.html new file mode 100644 index 0000000..8793fef --- /dev/null +++ b/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/main.uts b/main.uts new file mode 100644 index 0000000..8bdcc86 --- /dev/null +++ b/main.uts @@ -0,0 +1,9 @@ +import App from './App.uvue' + +import { createSSRApp } from 'vue' +export function createApp() { + const app = createSSRApp(App) + return { + app + } +} \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..4f6d427 --- /dev/null +++ b/manifest.json @@ -0,0 +1,43 @@ +{ + "name" : "AI助手", + "appid" : "", + "description" : "智能AI聊天助手 - 多工作流对话、工作空间、历史记录", + "versionName" : "1.0.0", + "versionCode" : "100", + "uni-app-x" : {}, + /* 快应用特有相关 */ + "quickapp" : {}, + /* 小程序特有相关 */ + "mp-weixin" : { + "appid" : "", + "setting" : { + "urlCheck" : false + }, + "usingComponents" : true + }, + "mp-alipay" : { + "usingComponents" : true + }, + "mp-baidu" : { + "usingComponents" : true + }, + "mp-toutiao" : { + "usingComponents" : true + }, + "uniStatistics" : { + "enable" : false + }, + "vueVersion" : "3", + "app" : { + "distribute" : { + "icons" : { + "android" : { + "hdpi" : "", + "xhdpi" : "", + "xxhdpi" : "", + "xxxhdpi" : "" + } + } + } + } +} diff --git a/pages.json b/pages.json new file mode 100644 index 0000000..17e0f31 --- /dev/null +++ b/pages.json @@ -0,0 +1,19 @@ +{ + "pages": [ + { + "path": "pages/index/index", + "style": { + "navigationStyle": "custom", + "navigationBarTitleText": "AI助手", + "softinputMode": "adjustResize" + } + } + ], + "globalStyle": { + "navigationBarTextStyle": "white", + "navigationBarTitleText": "AI助手", + "navigationBarBackgroundColor": "#0f0f1a", + "backgroundColor": "#0f0f1a" + }, + "uniIdRouter": {} +} diff --git a/pages/index/index.uvue b/pages/index/index.uvue new file mode 100644 index 0000000..1d2e9ea --- /dev/null +++ b/pages/index/index.uvue @@ -0,0 +1,425 @@ + + + + + diff --git a/platformConfig.json b/platformConfig.json new file mode 100644 index 0000000..896f669 --- /dev/null +++ b/platformConfig.json @@ -0,0 +1,7 @@ + +// 参考链接 https://doc.dcloud.net.cn/uni-app-x/tutorial/ls-plugin.html#setting +{ + "targets": [ + "APP-ANDROID" + ] +} \ No newline at end of file diff --git a/static/logo.png b/static/logo.png new file mode 100644 index 0000000..b5771e2 Binary files /dev/null and b/static/logo.png differ diff --git a/types/index.uts b/types/index.uts new file mode 100644 index 0000000..4af0130 --- /dev/null +++ b/types/index.uts @@ -0,0 +1,59 @@ +/** + * AI Chat Assistant — Type Definitions + */ + +// Workflow types +export type WorkflowId = 'general' | 'code' | 'document' | 'data' | 'creative' + +export interface WorkflowDef { + id: WorkflowId + name: string + icon: string + description: string + color: string +} + +// Message types +export type MessageRole = 'user' | 'assistant' +export type MessageStatus = 'sending' | 'sent' | 'error' + +export interface Message { + id: string + role: MessageRole + content: string + timestamp: number + status: MessageStatus +} + +// Conversation types +export interface Conversation { + id: string + title: string + workflowId: WorkflowId + messages: Message[] + createdAt: number + updatedAt: number +} + +// Workspace types +export type WorkspaceItemType = 'code_snippet' | 'document' | 'analysis_report' | 'creative_output' + +export interface WorkspaceItem { + id: string + conversationId: string + workflowId: WorkflowId + type: WorkspaceItemType + title: string + content: string + summary: string + createdAt: number +} + +// Parsed markdown segment +export interface ParsedSegment { + type: 'text' | 'code' | 'bold' + value: string +} + +// Bottom tab keys +export type TabKey = 'workspace' | 'chat' | 'profile' diff --git a/uni.scss b/uni.scss new file mode 100644 index 0000000..d2fcd76 --- /dev/null +++ b/uni.scss @@ -0,0 +1,156 @@ +/** + * 这里是uni-app内置的常用样式变量 + * + * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量 + * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App + */ + +/** + * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能 + * + * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件 + */ + +/* 颜色变量 */ + +/* 行为相关颜色 */ +$uni-color-primary: #007aff; +$uni-color-success: #4cd964; +$uni-color-warning: #f0ad4e; +$uni-color-error: #dd524d; + +/* 文字基本颜色 */ +$uni-text-color:#333;//基本色 +$uni-text-color-inverse:#fff;//反色 +$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息 +$uni-text-color-placeholder: #808080; +$uni-text-color-disable:#c0c0c0; + +/* 背景颜色 */ +$uni-bg-color:#ffffff; +$uni-bg-color-grey:#f8f8f8; +$uni-bg-color-hover:#f1f1f1;//点击状态颜色 +$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色 + +/* 边框颜色 */ +$uni-border-color:#c8c7cc; + +/* 尺寸变量 */ + +/* 文字尺寸 */ +$uni-font-size-sm:12px; +$uni-font-size-base:14px; +$uni-font-size-lg:16px; + +/* 图片尺寸 */ +$uni-img-size-sm:20px; +$uni-img-size-base:26px; +$uni-img-size-lg:40px; + +/* Border Radius */ +$uni-border-radius-sm: 2px; +$uni-border-radius-base: 3px; +$uni-border-radius-lg: 6px; +$uni-border-radius-circle: 50%; + +/* 水平间距 */ +$uni-spacing-row-sm: 5px; +$uni-spacing-row-base: 10px; +$uni-spacing-row-lg: 15px; + +/* 垂直间距 */ +$uni-spacing-col-sm: 4px; +$uni-spacing-col-base: 8px; +$uni-spacing-col-lg: 12px; + +/* 透明度 */ +$uni-opacity-disabled: 0.3; // 组件禁用态的透明度 + +/* 文章场景相关 */ +$uni-color-title: #2C405A; // 文章标题颜色 +$uni-font-size-title:20px; +$uni-color-subtitle: #555555; // 二级标题颜色 +$uni-font-size-subtitle:26px; +$uni-color-paragraph: #3F536E; // 文章段落颜色 +$uni-font-size-paragraph:15px; + +// ========================================== +// Dark AI Theme Design Tokens +// ========================================== + +/* 暗色背景 */ +$dark-bg-primary: #0f0f1a; +$dark-bg-secondary: #1a1a2e; +$dark-bg-card: #222240; +$dark-bg-input: rgba(255, 255, 255, 0.06); +$dark-bg-hover: rgba(255, 255, 255, 0.08); + +/* 暗色强调色 */ +$dark-accent-primary: #6c5ce7; +$dark-accent-secondary: #a29bfe; +$dark-accent-gradient: linear-gradient(135deg, #6c5ce7 0%, #a29bfe 100%); +$dark-accent-glow: 0 0 20px rgba(108, 92, 231, 0.3); + +/* 暗色文字 */ +$dark-text-primary: rgba(255, 255, 255, 0.92); +$dark-text-secondary: rgba(255, 255, 255, 0.6); +$dark-text-tertiary: rgba(255, 255, 255, 0.38); + +/* 暗色边框 */ +$dark-border: rgba(255, 255, 255, 0.08); +$dark-border-light: rgba(255, 255, 255, 0.12); + +/* 暗色遮罩 */ +$dark-mask: rgba(0, 0, 0, 0.55); + +/* 气泡尺寸 */ +$chat-bubble-radius: 24rpx; +$chat-bubble-padding: 20rpx 24rpx; +$chat-bubble-max-width: 80%; + +/* 芯片尺寸 */ +$chip-height: 64rpx; +$chip-padding: 0 24rpx; +$chip-radius: 32rpx; + +/* 输入区域 */ +$input-height: 72rpx; +$input-radius: 36rpx; +$input-padding: 16rpx 24rpx; + +/* 顶栏 */ +$topbar-height: 88rpx; + +/* 抽屉 */ +$drawer-width: 600rpx; + +/* 动画 */ +$transition-fast: 0.2s ease; +$transition-normal: 0.3s cubic-bezier(0.4, 0, 0.2, 1); +$transition-slow: 0.4s cubic-bezier(0.4, 0, 0.2, 1); + +/* 字体大小 */ +$font-size-xs: 20rpx; +$font-size-sm: 24rpx; +$font-size-base: 28rpx; +$font-size-md: 32rpx; +$font-size-lg: 36rpx; + +/* 间距 */ +$spacing-xs: 8rpx; +$spacing-sm: 16rpx; +$spacing-md: 24rpx; +$spacing-lg: 32rpx; +$spacing-xl: 48rpx; + +/* 玻璃效果 */ +@mixin glass { + background: rgba(15, 15, 26, 0.85); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); +} + +@mixin glass-light { + background: rgba(255, 255, 255, 0.06); + border: 1px solid $dark-border; +}