Add MindSpace page live edit, chat skills, and H5 deploy tooling.

Introduce page edit sessions with draft preview and patch API, chat skill picker, user memory profile, h5ApiBase resolution, voice WAV transport, and scripts for 105/g2 deployment and Plaza local dev.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
John
2026-06-15 22:09:38 -07:00
parent 3cd322ccfe
commit 6ee6fd64dd
94 changed files with 7015 additions and 2136 deletions
+91 -22
View File
@@ -1,14 +1,17 @@
import { useEffect, useState } from 'react';
import { useChat } from '../context/ChatProvider';
import { INSUFFICIENT_BALANCE_NOTICE } from '../hooks/useTKMindChat';
import type { CapabilityMap, PortalUser } from '../types';
import { useNetworkStatus } from '../hooks/useNetworkStatus';
import { getSessionDisplayName } from '../utils/sessions';
import { openAvatarPicker } from '../utils/userAvatar';
import { BalanceRing } from './BalanceRing';
import { HistorySidebar } from './HistorySidebar';
import { TKMindAvatar } from './TKMindAvatar';
import { ChatPanel } from './ChatPanel';
import { WechatBindPrompt } from './WechatBindPrompt';
import { WechatAccountButton } from './WechatAccountButton';
import { ChatHeaderMoreMenu } from './ChatHeaderMoreMenu';
import type { MindSpaceSaveCategory } from '../types';
export function ChatView({
@@ -46,12 +49,14 @@ export function ChatView({
rememberCurrentContext,
refreshProjectMemory,
switchSession,
deleteSession,
retryConnect,
dismissNotice,
onSidebarOpen,
workingDir,
balanceCents,
totalCreditCents,
tokensUsed,
openRecharge,
} = useChat();
const online = useNetworkStatus();
@@ -63,7 +68,7 @@ export function ChatView({
}
}, [sidebarOpen, onSidebarOpen]);
const busy = chatState === 'streaming' || chatState === 'loading';
const busy = chatState === 'streaming' || chatState === 'loading' || chatState === 'connecting';
const handleSelectSession = (sessionId: string) => {
void switchSession(sessionId);
@@ -75,7 +80,37 @@ export function ChatView({
setSidebarOpen(false);
};
const sessionTitle = session ? getSessionDisplayName(session) : '连接中…';
const sessionTitle = session
? getSessionDisplayName(session)
: chatState === 'connecting'
? '新对话'
: '连接中…';
const moreMenuItems = [
{
id: 'new-session',
label: '新会话',
onClick: () => void handleNewSession(),
},
{
id: 'remember',
label: '记住本轮',
onClick: () => void rememberCurrentContext(),
disabled: busy || memoryLoading || messages.length === 0,
},
{
id: 'refresh-memory',
label: memoryLoading ? '同步中…' : '刷新记忆',
onClick: () => void refreshProjectMemory(),
disabled: busy || memoryLoading,
},
...(onOpenAdmin
? [{ id: 'admin', label: '管理', onClick: () => onOpenAdmin() }]
: []),
...(onLogout
? [{ id: 'logout', label: '登出', onClick: () => onLogout(), danger: true }]
: []),
];
return (
<div className="app-shell">
@@ -87,6 +122,7 @@ export function ChatView({
onClose={() => setSidebarOpen(false)}
onSelect={handleSelectSession}
onNew={handleNewSession}
onDelete={(sessionId) => void deleteSession(sessionId)}
/>
<div className="app">
@@ -106,11 +142,12 @@ export function ChatView({
<div className="header-sub">{sessionTitle}</div>
</div>
</div>
<div className="header-actions">
<div className="header-actions header-actions-desktop">
{typeof balanceCents === 'number' && (
<BalanceRing
balanceCents={balanceCents}
totalCreditCents={totalCreditCents}
tokensUsed={tokensUsed}
onRecharge={openRecharge}
/>
)}
@@ -152,6 +189,41 @@ export function ChatView({
</button>
)}
</div>
<div className="header-actions header-actions-mobile">
{typeof balanceCents === 'number' && (
<BalanceRing
balanceCents={balanceCents}
totalCreditCents={totalCreditCents}
tokensUsed={tokensUsed}
onRecharge={openRecharge}
/>
)}
{onOpenSpace && (
<button
type="button"
className="header-icon-btn"
aria-label="我的空间"
title="我的空间"
onClick={() => onOpenSpace?.()}
>
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path
d="M4 8.5V19a1 1 0 001 1h14a1 1 0 001-1V8.5M4 8.5l8-4.5 8 4.5M4 8.5h16M9 21v-6h6v6"
stroke="currentColor"
strokeWidth="1.75"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</button>
)}
<ChatHeaderMoreMenu
items={moreMenuItems}
returnTo={window.location.pathname}
showWechat={Boolean(user)}
/>
</div>
</header>
{!online && (
@@ -163,7 +235,7 @@ export function ChatView({
{typeof balanceCents === 'number' && balanceCents <= 0 && (
<div className="banner banner-error">
<span>使</span>
<button type="button" className="banner-action" onClick={() => openRecharge(true)}>
<button type="button" className="banner-action" onClick={() => openRecharge(false)}>
</button>
</div>
@@ -177,26 +249,23 @@ export function ChatView({
</div>
)}
{user?.publishUrl && (
<div className="banner banner-info publish-banner">
<span>
<a href={user.publishUrl} target="_blank" rel="noreferrer">
{user.publishUrl}
</a>
{Boolean(capabilities?.static_publish) || (grantedSkills?.includes(user.publishSkillName ?? 'static-page-publish') ?? false)
? '(已开通静态页面技能,生成后可直接访问)'
: '(需管理员在「技能配置」中勾选 static-page-publish'}
</span>
</div>
)}
{notice && (
<div className="banner banner-info">
<div className={`banner${notice === INSUFFICIENT_BALANCE_NOTICE ? ' banner-warning' : ' banner-info'}`}>
<span>{notice}</span>
<button type="button" className="banner-dismiss" onClick={dismissNotice}>
</button>
{notice === INSUFFICIENT_BALANCE_NOTICE ? (
<>
<button type="button" className="banner-action" onClick={() => openRecharge(false)}>
</button>
<button type="button" className="banner-dismiss" onClick={dismissNotice}>
</button>
</>
) : (
<button type="button" className="banner-dismiss" onClick={dismissNotice}>
</button>
)}
</div>
)}