diff --git a/miniapp/.gitignore b/miniapp/.gitignore new file mode 100644 index 0000000..8818d42 --- /dev/null +++ b/miniapp/.gitignore @@ -0,0 +1,4 @@ +node_modules/ +miniprogram_npm/ +project.private.config.json +*.local diff --git a/miniapp/README.md b/miniapp/README.md new file mode 100644 index 0000000..fa299e1 --- /dev/null +++ b/miniapp/README.md @@ -0,0 +1,22 @@ +# TKMind WeChat Mini Program + +This directory contains the native WeChat Mini Program client. It is intentionally isolated from the H5/backend code so development can proceed without changing existing services. + +## Current MVP + +- Native WeChat login entry via `wx.login`. +- H5 account/password login fallback through `/auth/login`. +- Chat submission through `/api/agent/runs`. +- Agent run polling through `/api/agent/runs/:runId`. +- Session detail refresh through `/api/sessions/:sessionId`. +- Session list through `/api/sessions`. +- MindSpace page list through `/api/mindspace/v1/pages`. +- Page preview through Mini Program `web-view`. + +## Backend Assumptions + +The H5 APIs are reused as-is. Native Mini Program login posts the `wx.login` code to `MINIAPP_LOGIN_PATH` in `utils/config.js`; the current default is `/auth/wechat-miniapp/login`. If that backend endpoint is not available yet, use the account/password login fallback during local development. + +## Isolation Rule + +Mini Program work should stay inside `miniapp/`. Do not modify backend, H5, Memory, MindSpace, or production release files for this MVP unless explicitly approved. diff --git a/miniapp/app.js b/miniapp/app.js new file mode 100644 index 0000000..1159c30 --- /dev/null +++ b/miniapp/app.js @@ -0,0 +1,16 @@ +const { getStoredSession, setApiBaseUrl } = require('./utils/api'); +const { API_BASE_URL } = require('./utils/config'); + +App({ + globalData: { + user: null, + }, + + onLaunch() { + setApiBaseUrl(API_BASE_URL); + const session = getStoredSession(); + if (session?.user) { + this.globalData.user = session.user; + } + }, +}); diff --git a/miniapp/app.json b/miniapp/app.json new file mode 100644 index 0000000..fe32a86 --- /dev/null +++ b/miniapp/app.json @@ -0,0 +1,36 @@ +{ + "pages": [ + "pages/chat/index", + "pages/login/index", + "pages/sessions/index", + "pages/space/index", + "pages/webview/index" + ], + "window": { + "navigationBarTitleText": "TKMind", + "navigationBarBackgroundColor": "#101820", + "navigationBarTextStyle": "white", + "backgroundColor": "#f5f7fb" + }, + "tabBar": { + "color": "#64748b", + "selectedColor": "#0f766e", + "backgroundColor": "#ffffff", + "borderStyle": "black", + "list": [ + { + "pagePath": "pages/chat/index", + "text": "聊天" + }, + { + "pagePath": "pages/sessions/index", + "text": "会话" + }, + { + "pagePath": "pages/space/index", + "text": "页面" + } + ] + }, + "sitemapLocation": "sitemap.json" +} diff --git a/miniapp/app.wxss b/miniapp/app.wxss new file mode 100644 index 0000000..33a1e0d --- /dev/null +++ b/miniapp/app.wxss @@ -0,0 +1,45 @@ +page { + min-height: 100%; + background: #f5f7fb; + color: #132028; + font-family: -apple-system, BlinkMacSystemFont, "PingFang SC", "Helvetica Neue", sans-serif; +} + +.page { + min-height: 100vh; + box-sizing: border-box; + padding: 28rpx; +} + +.panel { + background: #ffffff; + border: 1rpx solid #dbe3eb; + border-radius: 16rpx; + box-shadow: 0 10rpx 30rpx rgba(15, 23, 42, 0.06); +} + +.muted { + color: #64748b; +} + +.primary-button { + height: 88rpx; + border-radius: 12rpx; + background: #0f766e; + color: #ffffff; + font-weight: 700; + line-height: 88rpx; +} + +.secondary-button { + height: 78rpx; + border-radius: 12rpx; + background: #e7f3f1; + color: #0f766e; + font-weight: 700; + line-height: 78rpx; +} + +.danger-text { + color: #b42318; +} diff --git a/miniapp/pages/chat/index.js b/miniapp/pages/chat/index.js new file mode 100644 index 0000000..825d821 --- /dev/null +++ b/miniapp/pages/chat/index.js @@ -0,0 +1,97 @@ +const { + checkAuth, + createAgentRun, + getAgentRun, + loadSession +} = require('../../utils/api'); +const { AGENT_RUN_MAX_POLLS, AGENT_RUN_POLL_MS } = require('../../utils/config'); +const { createUserMessage, normalizeMessages } = require('../../utils/messages'); + +function delay(ms) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +Page({ + data: { + sessionId: '', + messages: [], + draft: '', + loading: false, + scrollIntoView: '' + }, + + async onShow() { + try { + const status = await checkAuth(); + if (!status?.authenticated) { + wx.redirectTo({ url: '/pages/login/index' }); + } + } catch { + wx.redirectTo({ url: '/pages/login/index' }); + } + }, + + handleInput(event) { + this.setData({ draft: event.detail.value }); + }, + + scrollToBottom() { + const index = Math.max(0, this.data.messages.length - 1); + this.setData({ scrollIntoView: `msg-${index}` }); + }, + + async sendMessage() { + const text = this.data.draft.trim(); + if (!text || this.data.loading) return; + + const userMessage = createUserMessage(text); + const nextMessages = [ + ...this.data.messages, + { id: userMessage.id, role: 'user', text } + ]; + this.setData({ messages: nextMessages, draft: '', loading: true }); + this.scrollToBottom(); + + try { + const payload = { + session_id: this.data.sessionId || null, + user_message: userMessage, + source: 'wechat-miniapp' + }; + const run = await createAgentRun(payload); + await this.waitForRun(run?.id || run?.runId); + } catch (error) { + wx.showToast({ title: error?.message || '发送失败', icon: 'none' }); + } finally { + this.setData({ loading: false }); + this.scrollToBottom(); + } + }, + + async waitForRun(runId) { + if (!runId) throw new Error('后台任务未返回 runId'); + let sessionId = this.data.sessionId; + for (let i = 0; i < AGENT_RUN_MAX_POLLS; i += 1) { + const run = await getAgentRun(runId); + if (run?.sessionId && run.sessionId !== sessionId) { + sessionId = run.sessionId; + this.setData({ sessionId }); + } + if (run?.status === 'succeeded') { + if (sessionId) await this.refreshSession(sessionId); + return; + } + if (run?.status === 'failed') { + throw new Error(run.error || '后台任务失败'); + } + await delay(AGENT_RUN_POLL_MS); + } + throw new Error('任务耗时较长,请稍后在会话中查看'); + }, + + async refreshSession(sessionId) { + const detail = await loadSession(sessionId); + const messages = normalizeMessages(detail?.messages || detail?.data?.messages || []); + this.setData({ messages }); + } +}); diff --git a/miniapp/pages/chat/index.json b/miniapp/pages/chat/index.json new file mode 100644 index 0000000..29fc0ed --- /dev/null +++ b/miniapp/pages/chat/index.json @@ -0,0 +1,3 @@ +{ + "navigationBarTitleText": "TKMind 聊天" +} diff --git a/miniapp/pages/chat/index.wxml b/miniapp/pages/chat/index.wxml new file mode 100644 index 0000000..db88ba3 --- /dev/null +++ b/miniapp/pages/chat/index.wxml @@ -0,0 +1,22 @@ + + + + {{item.text}} + + + 正在思考... + + + + +