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:
+134
-11
@@ -27,6 +27,7 @@ import type {
|
||||
MindSpacePage,
|
||||
MindSpacePageDeletePreview,
|
||||
MindSpacePageDeleteResult,
|
||||
MindSpaceQuota,
|
||||
MindSpaceSaveCategory,
|
||||
MindSpacePublication,
|
||||
MindSpacePublicationStats,
|
||||
@@ -40,6 +41,7 @@ import type {
|
||||
SessionEvent,
|
||||
SessionListResponse,
|
||||
UsageRecord,
|
||||
BalanceUpdate,
|
||||
} from '../types';
|
||||
|
||||
const API = '/api';
|
||||
@@ -223,6 +225,9 @@ export type WechatAuthConfig = {
|
||||
enabled: boolean;
|
||||
inWechat?: boolean;
|
||||
scanEnabled?: boolean;
|
||||
openScanEnabled?: boolean;
|
||||
openAppId?: string;
|
||||
oauthCallbackUrl?: string;
|
||||
};
|
||||
|
||||
export type WechatPendingProfile = {
|
||||
@@ -286,6 +291,12 @@ export async function getMyUsage(): Promise<UsageRecord[]> {
|
||||
return result.records ?? [];
|
||||
}
|
||||
|
||||
export async function getMyBillingLedger(limit = 30): Promise<LedgerEntry[]> {
|
||||
const query = limit ? `?limit=${encodeURIComponent(String(limit))}` : '';
|
||||
const result = await portalFetch<{ entries: LedgerEntry[] }>(`/auth/billing/ledger${query}`);
|
||||
return result.entries ?? [];
|
||||
}
|
||||
|
||||
export async function getBillingConfig(): Promise<BillingConfig> {
|
||||
return portalFetch('/auth/billing/config');
|
||||
}
|
||||
@@ -326,9 +337,10 @@ export async function listMindSpaceCleanupItems(): Promise<{
|
||||
export async function runMindSpaceCleanup(itemIds: string[]): Promise<{
|
||||
removedCount: number;
|
||||
freedBytes: number;
|
||||
quota?: MindSpaceQuota;
|
||||
}> {
|
||||
const result = await apiFetch<{
|
||||
data: { removedCount: number; freedBytes: number };
|
||||
data: { removedCount: number; freedBytes: number; quota?: MindSpaceQuota };
|
||||
}>('/mindspace/v1/space/cleanup', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ item_ids: itemIds }),
|
||||
@@ -420,6 +432,74 @@ export async function getMindSpacePage(pageId: string): Promise<MindSpacePage> {
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export async function bindMindSpacePageLiveEdit(
|
||||
pageId: string,
|
||||
sessionId: string,
|
||||
options?: { parentSessionId?: string },
|
||||
): Promise<{ sessionId: string; pageId: string; parentSessionId?: string }> {
|
||||
const result = await apiFetch<{ data: { sessionId: string; pageId: string; parentSessionId?: string } }>(
|
||||
`/mindspace/v1/pages/${encodeURIComponent(pageId)}/live-edit/bind`,
|
||||
{
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
session_id: sessionId,
|
||||
...(options?.parentSessionId ? { parent_session_id: options.parentSessionId } : {}),
|
||||
}),
|
||||
},
|
||||
);
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export async function forkMindSpacePageEditSession(
|
||||
pageId: string,
|
||||
parentSessionId: string,
|
||||
h5ApiBase?: string | null,
|
||||
): Promise<{ sessionId: string; pageId: string; parentSessionId: string }> {
|
||||
const result = await apiFetch<{
|
||||
data: { sessionId: string; pageId: string; parentSessionId: string };
|
||||
}>(`/mindspace/v1/pages/${encodeURIComponent(pageId)}/live-edit/fork-session`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
parent_session_id: parentSessionId,
|
||||
...(h5ApiBase ? { h5_api_base: h5ApiBase } : {}),
|
||||
}),
|
||||
});
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export async function closeMindSpacePageEditSession(
|
||||
pageId: string,
|
||||
input: {
|
||||
sessionId: string;
|
||||
parentSessionId?: string | null;
|
||||
summary?: string;
|
||||
},
|
||||
): Promise<{ sessionId: string; pageId: string; merged: boolean }> {
|
||||
const result = await apiFetch<{
|
||||
data: { sessionId: string; pageId: string; merged: boolean };
|
||||
}>(`/mindspace/v1/pages/${encodeURIComponent(pageId)}/live-edit/close-session`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
session_id: input.sessionId,
|
||||
parent_session_id: input.parentSessionId ?? undefined,
|
||||
summary: input.summary ?? '',
|
||||
}),
|
||||
});
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export async function getMindSpacePageLiveRevision(pageId: string): Promise<{
|
||||
pageId: string;
|
||||
versionNo: number;
|
||||
updatedAt: number;
|
||||
liveRevision: number;
|
||||
}> {
|
||||
const result = await apiFetch<{
|
||||
data: { pageId: string; versionNo: number; updatedAt: number; liveRevision: number };
|
||||
}>(`/mindspace/v1/pages/${encodeURIComponent(pageId)}/live-edit/revision`);
|
||||
return result.data;
|
||||
}
|
||||
|
||||
export async function analyzeChatSave(input: {
|
||||
sessionId: string;
|
||||
messageId: string;
|
||||
@@ -1224,11 +1304,22 @@ export async function getWechatBindingStatus(): Promise<WechatBindingStatus> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function startWechatScanLogin(returnTo?: string): Promise<{
|
||||
state: string;
|
||||
qrUrl: string;
|
||||
expiresInMs: number;
|
||||
}> {
|
||||
export type WechatScanSession =
|
||||
| {
|
||||
mode: 'open';
|
||||
state: string;
|
||||
openAppId: string;
|
||||
redirectUri: string;
|
||||
expiresInMs: number;
|
||||
}
|
||||
| {
|
||||
mode: 'mp';
|
||||
state: string;
|
||||
qrUrl: string;
|
||||
expiresInMs: number;
|
||||
};
|
||||
|
||||
export async function startWechatScanLogin(returnTo?: string): Promise<WechatScanSession> {
|
||||
const params = new URLSearchParams();
|
||||
if (returnTo) params.set('return_to', returnTo);
|
||||
const query = params.toString();
|
||||
@@ -1242,14 +1333,33 @@ export async function startWechatScanLogin(returnTo?: string): Promise<{
|
||||
}
|
||||
const body = (await response.json().catch(() => null)) as {
|
||||
message?: string;
|
||||
mode?: 'open' | 'mp';
|
||||
state?: string;
|
||||
openAppId?: string;
|
||||
redirectUri?: string;
|
||||
qrUrl?: string;
|
||||
expiresInMs?: number;
|
||||
};
|
||||
if (!response.ok || !body?.state || !body?.qrUrl) {
|
||||
if (!response.ok || !body?.state || !body?.mode) {
|
||||
throw new ApiError(response.status, body?.message ?? '无法启动扫码登录');
|
||||
}
|
||||
if (body.mode === 'open') {
|
||||
if (!body.openAppId || !body.redirectUri) {
|
||||
throw new ApiError(response.status, body?.message ?? '无法启动扫码登录');
|
||||
}
|
||||
return {
|
||||
mode: 'open',
|
||||
state: body.state,
|
||||
openAppId: body.openAppId,
|
||||
redirectUri: body.redirectUri,
|
||||
expiresInMs: body.expiresInMs ?? 600000,
|
||||
};
|
||||
}
|
||||
if (!body.qrUrl) {
|
||||
throw new ApiError(response.status, body?.message ?? '无法启动扫码登录');
|
||||
}
|
||||
return {
|
||||
mode: 'mp',
|
||||
state: body.state,
|
||||
qrUrl: body.qrUrl,
|
||||
expiresInMs: body.expiresInMs ?? 600000,
|
||||
@@ -1486,12 +1596,16 @@ export async function logout(): Promise<void> {
|
||||
await fetch('/auth/logout', { method: 'POST' });
|
||||
}
|
||||
|
||||
export async function resumeSession(sessionId: string): Promise<Session> {
|
||||
export async function resumeSession(
|
||||
sessionId: string,
|
||||
options?: { skipReconcile?: boolean },
|
||||
): Promise<Session> {
|
||||
const result = await apiFetch<{ session: Session }>('/agent/resume', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
session_id: sessionId,
|
||||
load_model_and_extensions: true,
|
||||
...(options?.skipReconcile ? { skip_reconcile: true } : {}),
|
||||
}),
|
||||
});
|
||||
return result.session;
|
||||
@@ -1541,6 +1655,10 @@ export async function getSession(sessionId: string): Promise<Session> {
|
||||
return apiFetch<Session>(`/sessions/${sessionId}`);
|
||||
}
|
||||
|
||||
export async function deleteChatSession(sessionId: string): Promise<void> {
|
||||
await apiFetch(`/sessions/${sessionId}`, { method: 'DELETE' });
|
||||
}
|
||||
|
||||
export async function loadSessionDetail(sessionId: string): Promise<{
|
||||
session: Session;
|
||||
messages: Message[];
|
||||
@@ -1589,7 +1707,7 @@ export async function confirmTool(
|
||||
|
||||
type SubscribeOptions = {
|
||||
pauseWhenHidden?: boolean;
|
||||
onBalance?: (balanceCents: number) => void;
|
||||
onBalance?: (update: BalanceUpdate) => void;
|
||||
};
|
||||
|
||||
export function subscribeSessionEvents(
|
||||
@@ -1649,9 +1767,14 @@ export function subscribeSessionEvents(
|
||||
if (!data) continue;
|
||||
try {
|
||||
if (eventName === 'balance') {
|
||||
const payload = JSON.parse(data) as { balanceCents?: number };
|
||||
const payload = JSON.parse(data) as BalanceUpdate & { balanceCents?: number };
|
||||
if (typeof payload.balanceCents === 'number') {
|
||||
onBalance?.(payload.balanceCents);
|
||||
onBalance?.({
|
||||
balanceCents: payload.balanceCents,
|
||||
tokensUsed:
|
||||
typeof payload.tokensUsed === 'number' ? payload.tokensUsed : undefined,
|
||||
lastUsage: payload.lastUsage,
|
||||
});
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user