diff --git a/miniapp/.gitignore b/miniapp/.gitignore index 8818d42..fc32101 100644 --- a/miniapp/.gitignore +++ b/miniapp/.gitignore @@ -1,4 +1,5 @@ node_modules/ miniprogram_npm/ project.private.config.json +utils/config.local.js *.local diff --git a/miniapp/README.md b/miniapp/README.md index fa299e1..d348b10 100644 --- a/miniapp/README.md +++ b/miniapp/README.md @@ -17,6 +17,45 @@ This directory contains the native WeChat Mini Program client. It is intentional 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. +## Production / 提审前(当前默认) + +- `utils/config.js` 默认 `LOCAL_DEV = false`,API 指向 **`https://m.tkmind.cn`**。 +- `project.config.json` 中 `urlCheck: true`;DevTools 需配置真实小程序 AppID(非 tourist)。 +- 微信公众平台需配置 request / web-view 合法域名:`m.tkmind.cn`。 +- 此配置**不会**触发 103 发版;仅小程序客户端连线上 Portal。 +- **微信一键登录**还需 Portal 侧配置 `H5_WECHAT_MINIAPP_APP_ID` / `H5_WECHAT_MINIAPP_APP_SECRET` 并实现 `/auth/wechat-miniapp/login`;未部署前可先用账号密码登录。 + +### 开发者工具仍显示「游客模式」时 + +1. 关闭项目,重新「导入项目」并选择 AppID `wx3e79ecd530c88da4`(不要选测试号/游客模式) +2. 或:详情 → 基本信息 → AppID 改为你自己的小程序 +3. 确认 `project.config.json` 与 `project.private.config.json` 中 `appid` 均为 `wx3e79ecd530c88da4` +4. 重新编译后再点「微信一键登录」 + +## Local Debug (optional) + +1. Start local Portal in the repo root: + +```bash +pnpm dev +# or ensure http://127.0.0.1:8081/auth/status responds +``` + +2. Switch miniapp back to local Portal **without** touching production: + + - In `utils/config.js`, set `LOCAL_DEV = true`, **or** + - Copy `utils/config.local.example.js` → `utils/config.local.js` and set `API_BASE_URL`. + + `project.private.config.json` only affects DevTools compiler settings; it is **not** available to runtime `require()`. + +3. WeChat DevTools → 详情 → 本地设置 (local only): + - enable **Do not verify合法域名 / TLS** + - keep **tourist AppID** only if you use account/password login; real AppID is required for `wx.login` + +4. Restart Portal after pulling local server changes. Local Portal only bypasses CSRF for WeChat `servicewechat.com` referrers; production `m.tkmind.cn` is unchanged. + +5. Use an account that exists in your local `.env` database auth. Wrong credentials return `401`, not `403`. + ## 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 index 1159c30..5d1d322 100644 --- a/miniapp/app.js +++ b/miniapp/app.js @@ -1,4 +1,4 @@ -const { getStoredSession, setApiBaseUrl } = require('./utils/api'); +const { getStoredSession, setApiBaseUrl, getMiniProgramAppId, isTouristMode, getApiBaseUrl } = require('./utils/api'); const { API_BASE_URL } = require('./utils/config'); App({ @@ -8,6 +8,12 @@ App({ onLaunch() { setApiBaseUrl(API_BASE_URL); + const appId = getMiniProgramAppId(); + console.info('[TKMind miniapp] API_BASE_URL =', getApiBaseUrl() || API_BASE_URL); + console.info('[TKMind miniapp] AppID =', appId || '(none)'); + if (isTouristMode()) { + console.warn('[TKMind miniapp] 游客模式:wx.login 仅返回模拟 code,微信一键登录不可用'); + } const session = getStoredSession(); if (session?.user) { this.globalData.user = session.user; diff --git a/miniapp/pages/chat/index.js b/miniapp/pages/chat/index.js index c6c0fed..379036f 100644 --- a/miniapp/pages/chat/index.js +++ b/miniapp/pages/chat/index.js @@ -2,15 +2,18 @@ const { checkAuth, createAgentRun, getAgentRun, + getMe, loadSession, logout } = require('../../utils/api'); const { AGENT_RUN_MAX_POLLS, AGENT_RUN_POLL_MS, - SELECTED_SESSION_KEY + SELECTED_SESSION_KEY, + API_BASE_URL } = require('../../utils/config'); const { createUserMessage, normalizeMessages } = require('../../utils/messages'); +const { buildMessageView, resolveWorkspacePublicUrl } = require('../../utils/message-display'); function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); @@ -33,18 +36,49 @@ Page({ wx.redirectTo({ url: '/pages/login/index' }); return; } + await this.ensureCurrentUser(); const selectedSessionId = wx.getStorageSync(SELECTED_SESSION_KEY); if (selectedSessionId) { wx.removeStorageSync(SELECTED_SESSION_KEY); if (selectedSessionId !== this.data.sessionId) { await this.openSession(selectedSessionId); } + } else if (this.data.sessionId && this.shouldRefreshRenderedMessages()) { + await this.refreshSession(this.data.sessionId); } } catch { wx.redirectTo({ url: '/pages/login/index' }); } }, + getMessageRenderOptions() { + const app = getApp(); + const user = app?.globalData?.user || {}; + return { + publishKey: user.id || user.publishSlug || '', + apiBaseUrl: API_BASE_URL, + }; + }, + + shouldRefreshRenderedMessages() { + return this.data.messages.some( + (item) => + !Array.isArray(item.parts) || + (/\bpublic\/[A-Za-z0-9._-]+\.html\b/i.test(item.text || '') && + (!item.pageLinks || item.pageLinks.length === 0)) + ); + }, + + async ensureCurrentUser() { + const app = getApp(); + if (app?.globalData?.user?.id) return app.globalData.user; + const result = await getMe(); + if (result?.user) { + app.globalData.user = result.user; + } + return result?.user || null; + }, + handleInput(event) { this.setData({ draft: event.detail.value }); }, @@ -59,22 +93,24 @@ Page({ if (!text || this.data.loading) return; const userMessage = createUserMessage(text); - const nextMessages = [ - ...this.data.messages, - { id: userMessage.id, role: 'user', text } - ]; + const userView = buildMessageView(userMessage, this.getMessageRenderOptions()); + const nextMessages = [...this.data.messages, userView]; this.setData({ messages: nextMessages, draft: '', loading: true, error: '' }); this.scrollToBottom(); try { const payload = { - session_id: this.data.sessionId || null, - user_message: userMessage, - source: 'wechat-miniapp' + session_id: this.data.sessionId || undefined, + user_message: userMessage }; + console.info('[TKMind miniapp] createAgentRun start', { + sessionId: payload.session_id || null + }); const run = await createAgentRun(payload); - await this.waitForRun(run?.id || run?.runId); + console.info('[TKMind miniapp] agent run created', run?.id, run?.status); + await this.waitForRun(run); } catch (error) { + console.error('[TKMind miniapp] send failed', error); this.setData({ error: error?.message || '发送失败' }); wx.showToast({ title: error?.message || '发送失败', icon: 'none' }); } finally { @@ -83,21 +119,28 @@ Page({ } }, - async waitForRun(runId) { + async waitForRun(run) { + const runId = run?.id; if (!runId) throw new Error('后台任务未返回 runId'); - let sessionId = this.data.sessionId; + let sessionId = run?.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 }); + const latest = i === 0 ? run : await getAgentRun(runId); + if (latest?.sessionId) { + sessionId = latest.sessionId; + if (sessionId !== this.data.sessionId) { + this.setData({ sessionId }); + } } - if (run?.status === 'succeeded') { - if (sessionId) await this.refreshSession(sessionId); + if (latest?.status === 'succeeded') { + if (sessionId) { + await this.refreshSession(sessionId); + } else { + throw new Error('任务已完成,但未返回会话 ID'); + } return; } - if (run?.status === 'failed') { - throw new Error(run.error || '后台任务失败'); + if (latest?.status === 'failed') { + throw new Error(latest.error || '后台任务失败'); } await delay(AGENT_RUN_POLL_MS); } @@ -106,7 +149,16 @@ Page({ async refreshSession(sessionId) { const detail = await loadSession(sessionId); - const messages = normalizeMessages(detail?.messages || detail?.data?.messages || []); + const conversation = + detail?.conversation || + detail?.messages || + detail?.data?.conversation || + detail?.data?.messages || + []; + const visible = conversation.filter( + (message) => message?.metadata?.userVisible !== false + ); + const messages = normalizeMessages(visible, this.getMessageRenderOptions()); this.setData({ sessionId, messages, error: '' }); }, @@ -127,6 +179,38 @@ Page({ this.setData({ sessionId: '', messages: [], draft: '', error: '' }); }, + openLink(event) { + let url = String(event.currentTarget.dataset.url || '').trim(); + if (!url) { + wx.showToast({ title: '链接无效', icon: 'none' }); + return; + } + const { publishKey, apiBaseUrl } = this.getMessageRenderOptions(); + if (/^public\//i.test(url)) { + const resolved = resolveWorkspacePublicUrl(url, publishKey, apiBaseUrl); + if (resolved) url = resolved; + } else if (url.startsWith('/')) { + url = `${String(apiBaseUrl).replace(/\/$/, '')}${url}`; + } + wx.navigateTo({ + url: `/pages/webview/index?url=${encodeURIComponent(url)}` + }); + }, + + copyMessage(event) { + const text = String(event.currentTarget.dataset.text || '').trim(); + if (!text) return; + wx.setClipboardData({ + data: text, + success() { + wx.showToast({ title: '已复制', icon: 'success' }); + }, + fail() { + wx.showToast({ title: '复制失败', icon: 'none' }); + } + }); + }, + async handleLogout() { this.setData({ loading: true, error: '' }); try { diff --git a/miniapp/pages/chat/index.wxml b/miniapp/pages/chat/index.wxml index f40fd7b..aaa2ffc 100644 --- a/miniapp/pages/chat/index.wxml +++ b/miniapp/pages/chat/index.wxml @@ -14,23 +14,60 @@ 发一条消息开始使用 TKMind。 - - {{item.text}} + + + + + {{part.text}} + {{part.text}} + + + + + {{page.title}} + + + + + + - - 正在思考... + + + 正在思考... + -