048f25f580
Show privacy popup on launch, gate login on legal consent, and link user agreement and privacy policy pages in the mini program. Co-authored-by: Cursor <cursoragent@cursor.com>
257 lines
7.2 KiB
JavaScript
257 lines
7.2 KiB
JavaScript
const {
|
|
checkAuth,
|
|
createAgentRun,
|
|
getAgentRun,
|
|
getMe,
|
|
loadSession,
|
|
logout
|
|
} = require('../../utils/api');
|
|
const {
|
|
AGENT_RUN_MAX_POLLS,
|
|
AGENT_RUN_POLL_MS,
|
|
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));
|
|
}
|
|
|
|
Page({
|
|
data: {
|
|
sessionId: '',
|
|
messages: [],
|
|
draft: '',
|
|
loading: false,
|
|
error: '',
|
|
scrollIntoView: ''
|
|
},
|
|
|
|
async onShow() {
|
|
if (this._authCheckPromise) {
|
|
await this._authCheckPromise;
|
|
return;
|
|
}
|
|
this._authCheckPromise = this.verifyAccess();
|
|
try {
|
|
await this._authCheckPromise;
|
|
} finally {
|
|
this._authCheckPromise = null;
|
|
}
|
|
},
|
|
|
|
async verifyAccess() {
|
|
if (this._redirectingToLogin) return;
|
|
try {
|
|
const status = await checkAuth();
|
|
if (!status?.authenticated) {
|
|
this._redirectingToLogin = true;
|
|
wx.redirectTo({
|
|
url: '/pages/login/index',
|
|
complete: () => {
|
|
this._redirectingToLogin = false;
|
|
},
|
|
});
|
|
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 {
|
|
if (this._redirectingToLogin) return;
|
|
this._redirectingToLogin = true;
|
|
wx.redirectTo({
|
|
url: '/pages/login/index',
|
|
complete: () => {
|
|
this._redirectingToLogin = false;
|
|
},
|
|
});
|
|
}
|
|
},
|
|
|
|
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 });
|
|
},
|
|
|
|
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 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 || undefined,
|
|
user_message: userMessage
|
|
};
|
|
console.info('[TKMind miniapp] createAgentRun start', {
|
|
sessionId: payload.session_id || null
|
|
});
|
|
const run = await createAgentRun(payload);
|
|
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 {
|
|
this.setData({ loading: false });
|
|
this.scrollToBottom();
|
|
}
|
|
},
|
|
|
|
async waitForRun(run) {
|
|
const runId = run?.id;
|
|
if (!runId) throw new Error('后台任务未返回 runId');
|
|
let sessionId = run?.sessionId || this.data.sessionId;
|
|
for (let i = 0; i < AGENT_RUN_MAX_POLLS; i += 1) {
|
|
const latest = i === 0 ? run : await getAgentRun(runId);
|
|
if (latest?.sessionId) {
|
|
sessionId = latest.sessionId;
|
|
if (sessionId !== this.data.sessionId) {
|
|
this.setData({ sessionId });
|
|
}
|
|
}
|
|
if (latest?.status === 'succeeded') {
|
|
if (sessionId) {
|
|
await this.refreshSession(sessionId);
|
|
} else {
|
|
throw new Error('任务已完成,但未返回会话 ID');
|
|
}
|
|
return;
|
|
}
|
|
if (latest?.status === 'failed') {
|
|
throw new Error(latest.error || '后台任务失败');
|
|
}
|
|
await delay(AGENT_RUN_POLL_MS);
|
|
}
|
|
throw new Error('任务耗时较长,请稍后在会话中查看');
|
|
},
|
|
|
|
async refreshSession(sessionId) {
|
|
const detail = await loadSession(sessionId);
|
|
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: '' });
|
|
},
|
|
|
|
async openSession(sessionId) {
|
|
this.setData({ loading: true, error: '' });
|
|
try {
|
|
await this.refreshSession(sessionId);
|
|
} catch (error) {
|
|
this.setData({ error: error?.message || '会话加载失败' });
|
|
} finally {
|
|
this.setData({ loading: false });
|
|
this.scrollToBottom();
|
|
}
|
|
},
|
|
|
|
startNewChat() {
|
|
wx.removeStorageSync(SELECTED_SESSION_KEY);
|
|
this.setData({ sessionId: '', messages: [], draft: '', error: '' });
|
|
},
|
|
|
|
openLegalCenter() {
|
|
wx.navigateTo({ url: '/pages/legal/index/index' });
|
|
},
|
|
|
|
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 {
|
|
await logout();
|
|
wx.redirectTo({ url: '/pages/login/index' });
|
|
} catch (error) {
|
|
this.setData({ error: error?.message || '退出失败' });
|
|
} finally {
|
|
this.setData({ loading: false });
|
|
}
|
|
}
|
|
});
|