feat: integrate automatic game resource delivery

This commit is contained in:
2026-09-06 09:50:49 +08:00
parent f721966f3c
commit 4df4bc9624
20 changed files with 1667 additions and 191 deletions

View File

@@ -12,6 +12,100 @@ vi.mock('@/lib/coding-attachments', () => ({
describe('CodingConversationTimeline', () => {
afterEach(() => vi.unstubAllGlobals());
it('shows one Game Resource progress card from generation through automatic project save', async () => {
const { codingConversationStore } = await import('@/stores/coding-conversations');
const { CodingConversationTimeline } = await import(
'@/pages/Chat/CodingConversationTimeline'
);
const base = createProductSnapshot('conversation-game-resource-progress', 1);
const details = (phase: 'saving' | 'saved') => ({
schema: 'makelore-capability.v1' as const,
plugin_id: 'makelore.game-resource',
plugin_version: '1.0.0',
capability_id: 'game-resource.generate',
operation: 'generate',
request_id: 'pi:game-run:game-tool',
success: true,
status: phase === 'saved' ? 200 : 202,
code: null,
error: null,
retryable: false,
billing: phase === 'saved' ? {
mode: 'platform_metered' as const,
status: 'settled' as const,
reserved_points: '2.00',
actual_points: '2.00',
usage_amount: 1,
unit: 'generation',
} : {
mode: 'platform_metered' as const,
status: 'dispatched' as const,
reserved_points: '2.00',
usage_amount: 1,
unit: 'generation',
},
payload_schema: 'game-resource.v1',
data: {
phase,
executionId: '11111111-1111-4111-8111-111111111111',
providerStatus: 'succeeded',
deliveryStatus: phase,
outputCount: 2,
files: phase === 'saved' ? [
{ path: 'assets/generated/game-resource/execution/output-1.png', bytes: 128 },
{ path: 'assets/generated/game-resource/execution/output-2.png', bytes: 256 },
] : [],
},
});
const snapshot = (seq: number, phase: 'saving' | 'saved') => {
const toolDetails = details(phase);
return {
...base,
cursor: { ...base.cursor, seq },
nodes: [{
kind: 'tool' as const,
id: 'tool-game-resource-progress',
toolCallId: 'game-tool',
toolName: 'game_resource_generate',
title: '生成游戏资源',
inputText: '',
status: phase === 'saved' ? 'complete' as const : 'running' as const,
output: [{
kind: 'text' as const,
id: `tool-output-game-resource-${phase}`,
text: JSON.stringify(toolDetails),
status: phase === 'saved' ? 'complete' as const : 'streaming' as const,
}],
details: toolDetails,
}],
};
};
codingConversationStore.getState().applySnapshotEvent({
type: 'snapshot',
conversationId: 'conversation-game-resource-progress',
workerGeneration: 1,
seq: base.cursor.seq,
snapshot: snapshot(base.cursor.seq, 'saving'),
});
render(<CodingConversationTimeline conversationId="conversation-game-resource-progress" />);
expect(screen.getByTestId('tool-progress-preview')).toHaveTextContent(
'游戏资源 · 正在保存到项目',
);
const nextSeq = base.cursor.seq + 1;
act(() => codingConversationStore.getState().applySnapshotEvent({
type: 'snapshot',
conversationId: 'conversation-game-resource-progress',
workerGeneration: 1,
seq: nextSeq,
snapshot: snapshot(nextSeq, 'saved'),
}));
await waitFor(() => expect(screen.getByTestId('tool-progress-preview'))
.toHaveTextContent('游戏资源 · 已保存 2 个文件'));
});
it('resolves attachment refs into temporary object URLs without base64 state', async () => {
const createObjectURL = vi.fn(() => 'blob:authenticated-preview');
const revokeObjectURL = vi.fn();