reuse PlatformMudPointWalletEntry for account

This commit is contained in:
2026-07-16 20:21:00 +08:00
parent c5999b54d3
commit 9b8af1210e
5 changed files with 46 additions and 144 deletions
+1
View File
@@ -3377,6 +3377,7 @@ name = "quick-error"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quick-xml"
version = "0.39.4"
+36 -94
View File
@@ -37,6 +37,9 @@ import {
User,
Zap,
} from 'lucide-react';
import {
PlatformMudPointWalletEntry,
} from '../../../packages/shared/src/components/PlatformMudPointWalletEntry';
import type {
AuthEntryResponse,
@@ -74,7 +77,7 @@ import {
} from '../../../packages/shared/src/contracts/gameCreationApp';
import type {
ProfileDashboardSummary,
ProfileWalletLedgerEntry,
ProfileMudPointBalance,
} from '../../../packages/shared/src/contracts/runtime';
import {
API_RESPONSE_ENVELOPE_HEADER,
@@ -83,7 +86,7 @@ import {
} from '../../../packages/shared/src/http';
import {
getClientProfileDashboard,
getClientProfileWalletLedger,
getClientProfileRechargeCenter,
} from './services/clientApi';
import HomeView, {
type HomeAgentMode,
@@ -3799,34 +3802,6 @@ const homeAgentModeLabels: Record<HomeAgentMode, string> = {
doc: '做方案',
};
function formatMudPoints(value: number | null | undefined) {
if (typeof value !== 'number' || !Number.isFinite(value)) {
return '--';
}
return Math.max(0, Math.floor(value)).toLocaleString('zh-CN');
}
function formatLedgerAmount(entry: ProfileWalletLedgerEntry) {
const amount = Math.trunc(entry.amountDelta);
return `${amount >= 0 ? '+' : ''}${amount}`;
}
function formatLocalDateTime(value: string | number | null | undefined) {
if (value === null || value === undefined || value === '') {
return '未知时间';
}
const date = new Date(value);
if (Number.isNaN(date.getTime())) {
return '未知时间';
}
return date.toLocaleString('zh-CN', {
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
});
}
function buildHomeConversationContent(
mode: HomeAgentMode,
prompt: string,
@@ -5380,11 +5355,12 @@ export function WorkspaceLauncher({
useState<PendingNonEmptyProject | null>(null);
const [profileDashboard, setProfileDashboard] =
useState<ProfileDashboardSummary | null>(null);
const [walletPanelOpen, setWalletPanelOpen] = useState(false);
const [walletLedger, setWalletLedger] = useState<ProfileWalletLedgerEntry[]>(
[],
);
const [walletLedgerStatus, setWalletLedgerStatus] = useState('尚未读取明细');
const [mudPointBalance, setMudPointBalance] =
useState<ProfileMudPointBalance | null>(null);
const [mudPointBalanceStatus, setMudPointBalanceStatus] = useState<
'idle' | 'loading' | 'ready' | 'error'
>('idle');
const [mudPointBalanceError, setMudPointBalanceError] = useState('');
const [launcherNotice, setLauncherNotice] = useState<{
title: string;
message: string;
@@ -5842,7 +5818,7 @@ export function WorkspaceLauncher({
return;
}
setProfileDashboard(null);
setWalletLedgerStatus(
setMudPointBalanceError(
error instanceof Error ? error.message : '泥点读取失败',
);
});
@@ -5851,13 +5827,6 @@ export function WorkspaceLauncher({
};
}, []);
useEffect(() => {
if (!walletPanelOpen || walletLedgerStatus !== '尚未读取明细') {
return;
}
void loadWalletLedger();
}, [walletPanelOpen, walletLedgerStatus]);
useEffect(() => {
if (launcherView !== 'agent-chat') {
return;
@@ -6194,24 +6163,29 @@ export function WorkspaceLauncher({
);
}
async function loadWalletLedger() {
setWalletLedgerStatus('正在读取明细');
async function loadMudPointBalance() {
if (mudPointBalanceStatus === 'loading') {
return;
}
setMudPointBalanceStatus('loading');
setMudPointBalanceError('');
try {
const response = await getClientProfileWalletLedger();
setWalletLedger(response.entries.slice(0, 5));
setWalletLedgerStatus(
response.entries.length > 0 ? '已读取明细' : '暂无泥点明细',
const center = await getClientProfileRechargeCenter();
setMudPointBalance(center.mudPointBalance ?? null);
setProfileDashboard((current) =>
current ? { ...current, walletBalance: center.walletBalance } : current,
);
setMudPointBalanceStatus('ready');
} catch (error) {
setWalletLedger([]);
setWalletLedgerStatus(
error instanceof Error ? error.message : '明细读取失败',
setMudPointBalance(null);
setMudPointBalanceStatus('error');
setMudPointBalanceError(
error instanceof Error ? error.message : '泥点明细读取失败',
);
}
}
function showLauncherNotice(title: string) {
setWalletPanelOpen(false);
setLauncherNotice({
title,
message: `${title}正在接入中,当前版本会先保留入口。`,
@@ -8533,47 +8507,15 @@ export function WorkspaceLauncher({
) : null}
<div className="launcher-account-bar" aria-label="账户资产">
<div className="launcher-wallet-group">
<div className="launcher-wallet-anchor">
<button
type="button"
aria-label="泥点余额"
onClick={() => {
setWalletPanelOpen((open) => !open);
}}
>
<Zap size={12} aria-hidden="true" />
{formatMudPoints(profileDashboard?.walletBalance)}
</button>
{walletPanelOpen ? (
<div className="launcher-wallet-popover" role="dialog">
<strong></strong>
<p>{formatMudPoints(profileDashboard?.walletBalance)} </p>
<div className="launcher-wallet-ledger">
{walletLedger.length > 0 ? (
walletLedger.map((entry) => (
<div key={entry.id}>
<span>{formatLedgerAmount(entry)}</span>
<small>{formatLocalDateTime(entry.createdAt)}</small>
</div>
))
) : (
<small>{walletLedgerStatus}</small>
)}
</div>
<button
type="button"
onClick={() => showLauncherNotice('使用详情')}
>
使
</button>
</div>
) : null}
</div>
<button type="button" onClick={() => showLauncherNotice('升级')}>
</button>
</div>
<PlatformMudPointWalletEntry
balance={profileDashboard?.walletBalance ?? null}
breakdown={mudPointBalance}
isLoading={mudPointBalanceStatus === 'loading'}
error={mudPointBalanceError || null}
onRequestDetails={() => void loadMudPointBalance()}
onRecharge={() => showLauncherNotice('充值')}
onOpenLedger={() => showLauncherNotice('使用详情')}
/>
</div>
{launcherView === 'home' ? (
@@ -2,7 +2,7 @@ import {
API_RESPONSE_ENVELOPE_HEADER,
API_RESPONSE_ENVELOPE_VERSION,
ProfileDashboardSummary,
ProfileWalletLedgerResponse,
ProfileRechargeCenterResponse,
unwrapApiResponse,
} from '../../../../packages/shared/src';
@@ -134,9 +134,9 @@ export function getClientProfileDashboard() {
);
}
export function getClientProfileWalletLedger() {
return requestClientApi<ProfileWalletLedgerResponse>(
'/api/profile/wallet-ledger',
export function getClientProfileRechargeCenter() {
return requestClientApi<ProfileRechargeCenterResponse>(
'/api/profile/recharge-center',
{ method: 'GET' },
'读取泥点明细失败',
);
@@ -1,7 +1,4 @@
/** @vitest-environment jsdom */
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import {
act,
cleanup,
@@ -1476,17 +1473,17 @@ describe('AI 游戏创作 App 界面边界', () => {
).toBeNull();
expect(
within(screen.getByLabelText('账户资产')).getByRole('button', {
name: '泥点余额',
name: /^泥点 /,
}),
).not.toBeNull();
expect(
within(screen.getByLabelText('账户资产')).getByRole('button', {
name: '升级',
name: '充值',
}),
).not.toBeNull();
fireEvent.click(
fireEvent.mouseEnter(
within(screen.getByLabelText('账户资产')).getByRole('button', {
name: '泥点余额',
name: /^泥点 /,
}),
);
expect(screen.queryByText('已读取账户')).toBeNull();
@@ -19866,45 +19863,6 @@ describe('AI 游戏创作 App 界面边界', () => {
});
});
it('keeps multiline chat evidence readable', () => {
const styles = readFileSync(
resolve(process.cwd(), 'apps/ai-game-creator-shell/src/styles.css'),
'utf8',
);
expect(styles).toMatch(/\.message\s*\{[^}]*white-space:\s*pre-wrap/s);
expect(styles).toMatch(/\.message\s*\{[^}]*overflow-wrap:\s*anywhere/s);
expect(styles).toMatch(
/\.launcher-agent-chat-main\s*\{[^}]*grid-template-rows:[^;]*clamp\(260px,\s*40vh,\s*380px\)/s,
);
expect(styles).toMatch(
/\.launcher-agent-chat-messages\s*\{[^}]*grid-row:\s*5[^}]*height:\s*clamp\(260px,\s*40vh,\s*380px\)[^}]*overflow-y:\s*auto/s,
);
expect(styles).toMatch(
/\.launcher-agent-chat-composer\s*\{[^}]*grid-row:\s*6/s,
);
expect(styles).not.toMatch(
/\.launcher-agent-runtime-stack:empty\s*\{[^}]*display:\s*none/s,
);
});
it('keeps launcher page header actions styled after Tailwind preflight', () => {
const styles = readFileSync(
resolve(process.cwd(), 'apps/ai-game-creator-shell/src/styles.css'),
'utf8',
);
const headerActionRule = styles.match(
/\.launcher-page \.launcher-project-list-actions button,[^{]*\{([^}]*)\}/s,
);
expect(headerActionRule?.[1]).toContain('padding: 0 12px;');
expect(headerActionRule?.[1]).toContain('border: 1px solid #d8dde5;');
expect(headerActionRule?.[1]).toContain('background: #fff;');
expect(styles).toMatch(
/\.launcher-page \.launcher-project-list-actions button:disabled\s*\{[^}]*cursor:\s*not-allowed;[^}]*opacity:\s*0\.55;/s,
);
});
it('shows developer panels only in dev mode', () => {
renderAppAt('/?dev');
@@ -526,6 +526,7 @@ game-project/
- 聊天输入 `/publish` 只使用主窗口当前已加载的 manifest、最近 run trace、预览状态、资产来源和最近命令摘要,在聊天里生成发布准备清单,列出原型通过状态、预览、任务、资产、音频、包装说明和试玩包导出状态,并提供 `/run``/trace``/agent-resume ``/export` 草稿;该命令不调用 Tauri 读写、不启动或打开预览、不读取文件、不新增普通用户面板,真正导出仍由用户发送 `/export` 并走确认流。
- 开发窗口可从 Agent 状态列表进入单个专业 Agent 对话并管理其 Session;正式用户项目开发页只读展示专业 Agent 协作状态,不提供单 Agent 对话入口、Session 控件或工具台。
- v1 普通用户登录后直接进入单窗口客户端首页;同一窗口中切换首页、项目组、指南 / 反馈和项目开发页。项目组页管理最近项目、打开项目、新建项目和显示目录;打开项目只切换到项目开发页,不调用 `open_game_creator_workspace_window` 打开第二窗口。旧 Tauri 窗口 command 只保留兼容,不进入用户主流程。
- 2026-07-16 补充:普通用户窗口顶部账户资产统一复用 `PlatformMudPointWalletEntry`;首屏余额来自 `/api/profile/dashboard`,展开时按需读取 `/api/profile/recharge-center` 中的泥点拆分。AI 游戏创作壳的 Tailwind 入口必须显式扫描 `packages/shared/src/components`,避免共享组件的 utility 样式在构建时被遗漏。
- 主窗口可通过系统文件管理器显示当前项目目录,也可在聊天输入 `/open-project` 走同一只读打开动作;该操作只打开本地目录,不初始化项目、不写项目文件、不切换工作区。主窗口头部显示最近 `.agent/run.latest.json` 的 run 状态摘要和当前项目预览状态,并通过“刷新状态”重新读取同一 trace,不新增状态数据库。
- 首页、项目组页和项目开发页共用单窗口壳的全局运行时配置弹窗,读写 Tauri 应用配置目录中的 `game-creator.config.json`;正式 Supervisor 项目页缺配置时只显示错误,不自动打开该弹窗。API Key 仍不进入本地项目、trace、manifest 或聊天记录。
- 首页发送和项目组新建都通过 Tauri 原生目录选择器选择项目路径;用户取消目录选择时不覆盖已有输入或草稿。