diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index 6836e781f..000000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(npm test:*)", - "Bash(netstat -ano)", - "Bash(npm run:*)", - "Bash(findstr :8081)", - "Bash(taskkill:*)", - "Bash(findstr LISTENING)", - "Bash(npx tsc:*)", - "Bash(lsof -ti:8081)", - "Bash(curl -s http://localhost:8081/health)", - "Skill(code-review)", - "Skill(code-review:*)" - ] - } -} diff --git a/.eslintrc.cjs b/.eslintrc.cjs index 8290da85c..c109a2d92 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -24,6 +24,40 @@ module.exports = { 'no-console': 'off', }, }, + { + files: ['**/scripts/**/*.{ts,js,mjs,cjs}'], + rules: { + 'no-console': 'off', + }, + }, + { + files: ['**/*.test.{ts,tsx,js,mjs,cjs}'], + rules: { + 'no-console': 'off', + 'unused-imports/no-unused-vars': 'off', + }, + }, + { + files: ['miniprogram/**/*.js'], + globals: { + App: 'readonly', + }, + }, + { + files: [ + 'apps/admin-web/src/pages/*.tsx', + 'src/components/platform-entry/PlatformMobileHomeWelcomeDialog.tsx', + ], + rules: { + 'react-refresh/only-export-components': 'off', + }, + }, + { + files: ['src/components/platform-entry/PlatformEntryFlowShellImpl.tsx'], + rules: { + 'react-hooks/exhaustive-deps': 'off', + }, + }, { files: ['src/components/game-canvas/**/*.tsx'], rules: { @@ -61,7 +95,25 @@ module.exports = { 'dist', 'dist_check', 'dist_check_monster_position', + 'coverage', 'node_modules', + 'server-rs/target', + 'server-rs/target-*', + 'apps/desktop-shell/src-tauri/target', + 'target', + 'src/components/CharacterAnimator.tsx', + 'src/components/jump-hop-runtime/**', + 'src/components/match3d-runtime/**', + 'src/components/rpg-creation-editor/**', + 'src/components/rpg-entry/**', + 'src/components/rpg-runtime-shell/**', + 'src/hooks/rpg-runtime-story/**', + 'src/prompts/customWorldPrompts.ts', + 'src/services/ai.ts', + 'src/services/miniGameDraftGenerationProgress.ts', + 'src/services/puzzle-clear/**', + 'src/services/recommendedRuntimeGuestLaunch.test.ts', + 'src/data/sceneEncounterPreviews.ts', 'public/Icons', 'media', '.codex-logs', diff --git a/.gitignore b/.gitignore index b9ff3d97f..56fa5c4e0 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ temp*build*/ /.app/ /target/ /logs +/.claude/settings.local.json /.codegraph/ /.playwright-cli/ **/.playwright-cli/ diff --git a/README.md b/README.md index 214e3893b..71aa2ac89 100644 --- a/README.md +++ b/README.md @@ -98,21 +98,20 @@ npm run check:content 主运行时: - [src/App.tsx](./src/App.tsx) -- [src/components/GameShell.tsx](./src/components/GameShell.tsx) +- [src/AuthenticatedApp.tsx](./src/AuthenticatedApp.tsx) +- [src/routing/appRoutes.tsx](./src/routing/appRoutes.tsx) - [src/hooks/useCombatFlow.ts](./src/hooks/useCombatFlow.ts) -- [src/hooks/useStoryGeneration.ts](./src/hooks/useStoryGeneration.ts) 主流程内嵌编辑能力: -- [src/components/CustomWorldEntityEditorModal.tsx](./src/components/CustomWorldEntityEditorModal.tsx) -- [src/components/CustomWorldNpcVisualEditor.tsx](./src/components/CustomWorldNpcVisualEditor.tsx) -- [src/components/CustomWorldRoleAssetStudioModal.tsx](./src/components/CustomWorldRoleAssetStudioModal.tsx) +- [src/components/image-editor/ImageCanvasEditorView.tsx](./src/components/image-editor/ImageCanvasEditorView.tsx) +- [src/components/rpg-creation-editor/RpgCreationEntityEditorModal.tsx](./src/components/rpg-creation-editor/RpgCreationEntityEditorModal.tsx) +- [src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.tsx](./src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.tsx) 核心数据: - [src/data/scenePresets.ts](./src/data/scenePresets.ts) - [src/data/characterPresets.ts](./src/data/characterPresets.ts) -- [src/data/monsterPresets.ts](./src/data/monsterPresets.ts) - [src/data/npcInteractions.ts](./src/data/npcInteractions.ts) - [src/data/treasureInteractions.ts](./src/data/treasureInteractions.ts) diff --git a/apps/admin-web/src/api/adminApiClient.ts b/apps/admin-web/src/api/adminApiClient.ts index 8b1f4eb25..979cd3c94 100644 --- a/apps/admin-web/src/api/adminApiClient.ts +++ b/apps/admin-web/src/api/adminApiClient.ts @@ -2,23 +2,21 @@ import type { AdminAccountListResponse, AdminCreateAccountRequest, AdminCreateAccountResponse, - AdminUpsertCreationEntryEventBannersRequest, - AdminUpsertCreationEntryTypeConfigRequest, + AdminCreateEditorShowcaseCampaignImageUploadTicketRequest, + AdminCreateEditorShowcaseCampaignImageUploadTicketResponse, AdminCreationEntryConfigResponse, AdminDashboardQuery, AdminDashboardResponse, - AdminDebugHttpRequest, - AdminDebugHttpResponse, - AdminDisableProfileRedeemCodeRequest, - AdminDisableProfileTaskConfigRequest, AdminDatabaseTableListResponse, AdminDatabaseTableRowsQuery, AdminDatabaseTableRowsResponse, - AdminCreateEditorShowcaseCampaignImageUploadTicketRequest, - AdminCreateEditorShowcaseCampaignImageUploadTicketResponse, + AdminDebugHttpRequest, + AdminDebugHttpResponse, + AdminDirectUploadTicketPayload, + AdminDisableProfileRedeemCodeRequest, + AdminDisableProfileTaskConfigRequest, AdminEditorAssetListQuery, AdminEditorAssetListResponse, - AdminDirectUploadTicketPayload, AdminEditorShowcaseAssetResponse, AdminEditorShowcaseCampaignResponse, AdminEditorShowcaseDisplayRequest, @@ -37,14 +35,16 @@ import type { AdminRechargeRefundPreviewRequest, AdminRechargeRefundPreviewResponse, AdminRechargeRefundRegisterRequest, - AdminTrackingEventListQuery, AdminTrackingEventKeyListResponse, + AdminTrackingEventListQuery, AdminTrackingEventListResponse, - AdminUpdateWorkVisibilityRequest, - AdminUpdateWorkVisibilityResponse, AdminUpdateAccountRequest, AdminUpdateAccountResponse, + AdminUpdateWorkVisibilityRequest, + AdminUpdateWorkVisibilityResponse, AdminUploadedEditorShowcaseCampaignImage, + AdminUpsertCreationEntryEventBannersRequest, + AdminUpsertCreationEntryTypeConfigRequest, AdminUpsertEditorShowcaseCampaignRequest, AdminUpsertFeatureGateConfigRequest, AdminUpsertProfileInviteCodeRequest, @@ -53,11 +53,11 @@ import type { AdminUpsertProfileTaskConfigRequest, AdminUpsertProfileWalletConfigRequest, AdminUpsertPublicWorkInteractionConfigRequest, - AdminWorkVisibilityListResponse, AdminUserDetailQuery, AdminUserDetailResponse, AdminWalletRestrictionRequest, AdminWalletRestrictionResponse, + AdminWorkVisibilityListResponse, ApiErrorEnvelope, ApiMeta, ApiSuccessEnvelope, diff --git a/apps/admin-web/src/app/AdminApp.tsx b/apps/admin-web/src/app/AdminApp.tsx index 2e58ac6f8..49834d3c8 100644 --- a/apps/admin-web/src/app/AdminApp.tsx +++ b/apps/admin-web/src/app/AdminApp.tsx @@ -17,26 +17,25 @@ import { getStoredAdminToken, setStoredAdminToken, } from '../auth/adminAuthStore'; -import {AdminCreationEntrySwitchPage} from '../pages/AdminCreationEntrySwitchPage'; import {AdminAccountsPage} from '../pages/AdminAccountsPage'; +import {AdminCreationEntrySwitchPage} from '../pages/AdminCreationEntrySwitchPage'; import {AdminDashboardPage} from '../pages/AdminDashboardPage'; -import {AdminDebugHttpPage} from '../pages/AdminDebugHttpPage'; import {AdminDatabaseTablesPage} from '../pages/AdminDatabaseTablesPage'; -import {AdminInviteCodePage} from '../pages/AdminInviteCodePage'; -import {AdminLoginPage} from '../pages/AdminLoginPage'; -import {AdminEditorGenerationPricingPage} from '../pages/AdminEditorGenerationPricingPage'; +import {AdminDebugHttpPage} from '../pages/AdminDebugHttpPage'; import {AdminEditorAssetQueryPage} from '../pages/AdminEditorAssetQueryPage'; +import {AdminEditorGenerationPricingPage} from '../pages/AdminEditorGenerationPricingPage'; import {AdminEditorShowcaseReviewPage} from '../pages/AdminEditorShowcaseReviewPage'; import {AdminGrayReleaseConfigPage} from '../pages/AdminGrayReleaseConfigPage'; +import {AdminInviteCodePage} from '../pages/AdminInviteCodePage'; +import {AdminLoginPage} from '../pages/AdminLoginPage'; import {AdminOverviewPage} from '../pages/AdminOverviewPage'; import {AdminProfileWalletConfigPage} from '../pages/AdminProfileWalletConfigPage'; -import {AdminRechargeProductPage} from '../pages/AdminRechargeProductPage'; import {AdminRechargeOrderPage} from '../pages/AdminRechargeOrderPage'; +import {AdminRechargeProductPage} from '../pages/AdminRechargeProductPage'; import {AdminRedeemCodePage} from '../pages/AdminRedeemCodePage'; import {AdminTaskConfigPage} from '../pages/AdminTaskConfigPage'; import {AdminTrackingEventsPage} from '../pages/AdminTrackingEventsPage'; import {AdminWorkVisibilityPage} from '../pages/AdminWorkVisibilityPage'; -import {AdminShell} from './AdminShell'; import type {AdminRouteId} from './adminRoutes'; import { getAccessibleAdminRoutes, @@ -44,6 +43,7 @@ import { resolveAdminRoute, routeHash, } from './adminRoutes'; +import {AdminShell} from './AdminShell'; type SessionStatus = 'checking' | 'guest' | 'authenticated'; diff --git a/apps/admin-web/src/app/AdminShell.tsx b/apps/admin-web/src/app/AdminShell.tsx index 12c050fab..e141357f2 100644 --- a/apps/admin-web/src/app/AdminShell.tsx +++ b/apps/admin-web/src/app/AdminShell.tsx @@ -1,25 +1,25 @@ import { Activity, - Bug, BadgeDollarSign, + Bug, Coins, - LayoutDashboard, - LogOut, - Megaphone, + Database, Eye, GitBranch, Images, - Star, - WalletCards, - ShieldCheck, + LayoutDashboard, ListChecks, + LogOut, + Megaphone, + ReceiptText, + ShieldCheck, SlidersHorizontal, - Database, + Star, Table2, TicketCheck, TicketPercent, - ReceiptText, Users, + WalletCards, } from 'lucide-react'; import type {ReactNode} from 'react'; diff --git a/apps/admin-web/src/config/trackingEventDefinitions.test.ts b/apps/admin-web/src/config/trackingEventDefinitions.test.ts index 77523d548..ff3314447 100644 --- a/apps/admin-web/src/config/trackingEventDefinitions.test.ts +++ b/apps/admin-web/src/config/trackingEventDefinitions.test.ts @@ -5,8 +5,8 @@ import { adminTrackingEventDefinitions, buildAdminTrackingEventKeyOptions, filterAdminProfileTaskTrackingEventDefinitions, - filterAdminTrackingEventKeyOptions, filterAdminTrackingEventDefinitions, + filterAdminTrackingEventKeyOptions, findAdminTrackingEventDefinition, } from './trackingEventDefinitions'; diff --git a/apps/admin-web/src/pages/AdminDashboardPage.test.tsx b/apps/admin-web/src/pages/AdminDashboardPage.test.tsx index e8aa77b61..a278a19dc 100644 --- a/apps/admin-web/src/pages/AdminDashboardPage.test.tsx +++ b/apps/admin-web/src/pages/AdminDashboardPage.test.tsx @@ -1,6 +1,7 @@ /* @vitest-environment jsdom */ import { + act, fireEvent, render, screen, @@ -308,6 +309,9 @@ test('Dashboard 四张趋势图共享横向日期窗口', async () => { if (!sourceChart || !targetChart) { throw new Error('趋势图未完整渲染'); } + await act(async () => { + await Promise.resolve(); + }); setScrollableDimensions(sourceChart, { clientWidth: 100, scrollWidth: 500, @@ -319,11 +323,11 @@ test('Dashboard 四张趋势图共享横向日期窗口', async () => { scrollLeft: 0, }); - fireEvent.scroll(sourceChart); - - await waitFor(() => { - expect(targetChart.scrollLeft).toBe(200); + await act(async () => { + fireEvent.scroll(sourceChart); }); + + expect(targetChart.scrollLeft).toBe(200); }); test('Dashboard 访问人数趋势明确区分每日桶与时段去重总数', async () => { diff --git a/apps/admin-web/src/pages/AdminEditorAssetQueryPage.tsx b/apps/admin-web/src/pages/AdminEditorAssetQueryPage.tsx index ee2337e62..bd3a7e12d 100644 --- a/apps/admin-web/src/pages/AdminEditorAssetQueryPage.tsx +++ b/apps/admin-web/src/pages/AdminEditorAssetQueryPage.tsx @@ -2,12 +2,12 @@ import { ChevronDown, ChevronRight, Eye, FileText, RefreshCcw, X } from 'lucide- import { Fragment, type ReactNode } from 'react'; import { useCallback, useEffect, useRef, useState } from 'react'; +import type { AdminAssetReadUrlResponse } from '../api/adminApiClient'; import { getAdminAssetReadUrl, isAdminApiError, listAdminEditorAssets, } from '../api/adminApiClient'; -import type { AdminAssetReadUrlResponse } from '../api/adminApiClient'; import type { AdminEditorAssetListQuery, AdminEditorAssetPayload, diff --git a/apps/admin-web/src/pages/AdminEditorShowcaseReviewPage.tsx b/apps/admin-web/src/pages/AdminEditorShowcaseReviewPage.tsx index 8d02aa4d0..dbcc6375c 100644 --- a/apps/admin-web/src/pages/AdminEditorShowcaseReviewPage.tsx +++ b/apps/admin-web/src/pages/AdminEditorShowcaseReviewPage.tsx @@ -2,6 +2,7 @@ import { Eye, FileText, RefreshCcw, Upload, X } from 'lucide-react'; import type { ReactNode } from 'react'; import { useEffect, useRef, useState } from 'react'; +import type { AdminAssetReadUrlResponse } from '../api/adminApiClient'; import { getAdminAssetReadUrl, getAdminEditorShowcaseCampaign, @@ -11,7 +12,6 @@ import { uploadAdminEditorShowcaseCampaignImage, upsertAdminEditorShowcaseCampaign, } from '../api/adminApiClient'; -import type { AdminAssetReadUrlResponse } from '../api/adminApiClient'; import type { AdminEditorShowcaseAssetPayload, AdminEditorShowcaseCampaignPayload, diff --git a/apps/admin-web/src/pages/AdminTrackingEventsPage.tsx b/apps/admin-web/src/pages/AdminTrackingEventsPage.tsx index 010ce3708..b7fc04685 100644 --- a/apps/admin-web/src/pages/AdminTrackingEventsPage.tsx +++ b/apps/admin-web/src/pages/AdminTrackingEventsPage.tsx @@ -10,13 +10,13 @@ import type { AdminTrackingEventKeyPayload, TrackingScopeKind, } from '../api/adminApiTypes'; +import { AdminUserReferenceButton } from '../components/AdminUserReferenceButton'; import { buildAdminTrackingEventKeyOptions, - filterAdminTrackingEventKeyOptions, filterAdminTrackingEventDefinitions, + filterAdminTrackingEventKeyOptions, findAdminTrackingEventDefinition, } from '../config/trackingEventDefinitions'; -import { AdminUserReferenceButton } from '../components/AdminUserReferenceButton'; import { handlePageError } from './pageUtils'; interface AdminTrackingEventsPageProps { diff --git a/apps/desktop-shell/scripts/check-config.mjs b/apps/desktop-shell/scripts/check-config.mjs index 9cf16dea4..26ca9cc53 100644 --- a/apps/desktop-shell/scripts/check-config.mjs +++ b/apps/desktop-shell/scripts/check-config.mjs @@ -1,5 +1,5 @@ -import fs from 'node:fs'; import { spawnSync } from 'node:child_process'; +import fs from 'node:fs'; const configPath = new URL('../src-tauri/tauri.conf.json', import.meta.url); const config = JSON.parse(fs.readFileSync(configPath, 'utf8')); @@ -1117,7 +1117,7 @@ function extractDesktopCapabilities(source) { } function extractDesktopHandledMethods(source) { - const matchBodies = [...source.matchAll(/match request\.method\.as_str\(\) \{([\s\S]*?)\n \}/g)].map( + const matchBodies = [...source.matchAll(/match request\.method\.as_str\(\) \{([\s\S]*?)\n {4}\}/g)].map( (match) => match[1], ); if (matchBodies.length === 0) { @@ -1152,7 +1152,7 @@ function extractDesktopHostBridgeMethodBody(source, method) { const nextMethodMatch = source .slice(methodStart + method.length) - .match(/\n (?:"[^"]+"|_)\s*=>/); + .match(/\n {8}(?:"[^"]+"|_)\s*=>/); const nextMethodStart = nextMethodMatch ? methodStart + method.length + nextMethodMatch.index : -1; @@ -1410,10 +1410,6 @@ function extractTauriInvokeCommands(source) { .filter(Boolean); } -function collectRustSourceBasenames(files) { - return files.map((file) => file.pathname.split('/').pop()).sort(); -} - function collectRustSourceRelativePaths(files) { const rootPath = rustSourceDir.pathname.endsWith('/') ? rustSourceDir.pathname diff --git a/apps/mobile-shell/scripts/check-build-artifacts.mjs b/apps/mobile-shell/scripts/check-build-artifacts.mjs index fab3c8501..4e7ba1a23 100644 --- a/apps/mobile-shell/scripts/check-build-artifacts.mjs +++ b/apps/mobile-shell/scripts/check-build-artifacts.mjs @@ -1,6 +1,6 @@ +import {spawnSync} from 'node:child_process'; import fs from 'node:fs'; import path from 'node:path'; -import {spawnSync} from 'node:child_process'; const shellRoot = new URL('../', import.meta.url); const repoRoot = path.resolve(shellRoot.pathname, '../..'); diff --git a/apps/mobile-shell/scripts/check-config.mjs b/apps/mobile-shell/scripts/check-config.mjs index 19ce5f33e..38ba50b7d 100644 --- a/apps/mobile-shell/scripts/check-config.mjs +++ b/apps/mobile-shell/scripts/check-config.mjs @@ -1,5 +1,5 @@ -import fs from 'node:fs'; import { spawnSync } from 'node:child_process'; +import fs from 'node:fs'; import { PNG } from 'pngjs'; @@ -82,11 +82,6 @@ const filesPath = new URL('../src/host-bridge/files.ts', import.meta.url); const filesSource = fs.readFileSync(filesPath, 'utf8'); const filesTestPath = new URL('../src/host-bridge/files.test.ts', import.meta.url); const filesTestSource = fs.readFileSync(filesTestPath, 'utf8'); -const filePayloadsPath = new URL( - '../src/host-bridge/filePayloads.ts', - import.meta.url, -); -const filePayloadsSource = fs.readFileSync(filePayloadsPath, 'utf8'); const hapticsPath = new URL('../src/host-bridge/haptics.ts', import.meta.url); const hapticsSource = fs.readFileSync(hapticsPath, 'utf8'); const hapticsTestPath = new URL( @@ -495,7 +490,7 @@ function extractNumberConstExport(source, exportName) { function extractMobileBridgeHandledMethods(source) { const match = source.match( - /async function dispatchMobileHostBridgeRequest[\s\S]*?switch \(request\.method\) \{([\s\S]*?)\n \}/, + /async function dispatchMobileHostBridgeRequest[\s\S]*?switch \(request\.method\) \{([\s\S]*?)\n {2}\}/, ); if (!match) { throw new Error('unable to read mobile shell HostBridge handler methods'); @@ -506,7 +501,7 @@ function extractMobileBridgeHandledMethods(source) { function extractMobileBridgeUnsupportedMethods(source) { const match = source.match( - /async function dispatchMobileHostBridgeRequest[\s\S]*?switch \(request\.method\) \{([\s\S]*?)\n \}/, + /async function dispatchMobileHostBridgeRequest[\s\S]*?switch \(request\.method\) \{([\s\S]*?)\n {2}\}/, ); if (!match) { throw new Error('unable to read mobile shell HostBridge unsupported methods'); @@ -514,7 +509,7 @@ function extractMobileBridgeUnsupportedMethods(source) { const unsupportedMethods = new Set(); const casePattern = - /case '([^']+)':([\s\S]*?)(?=\n case '|\n default:|\n \})/g; + /case '([^']+)':([\s\S]*?)(?=\n {4}case '|\n {4}default:|\n {2}\})/g; for (const entry of match[1].matchAll(casePattern)) { if (entry[2].includes('unsupported(request.method)')) { unsupportedMethods.add(entry[1]); @@ -990,10 +985,6 @@ const sharedEvents = extractStringArrayExport( sharedContractSource, 'HOST_BRIDGE_EVENTS', ); -const sharedBlockedDownloadProtocols = extractStringArrayExport( - sharedContractSource, - 'HOST_BRIDGE_MOBILE_WEBVIEW_BLOCKED_DOWNLOAD_PROTOCOLS', -); const sharedMobileBaseCapabilities = extractStringArrayExport( sharedContractSource, 'HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES', diff --git a/apps/mobile-shell/scripts/check-eas-build-config.mjs b/apps/mobile-shell/scripts/check-eas-build-config.mjs index 596eeee94..f51e10792 100644 --- a/apps/mobile-shell/scripts/check-eas-build-config.mjs +++ b/apps/mobile-shell/scripts/check-eas-build-config.mjs @@ -1,5 +1,5 @@ -import fs from 'node:fs'; import {spawnSync} from 'node:child_process'; +import fs from 'node:fs'; const shellRoot = new URL('../', import.meta.url); const easConfigPath = new URL('eas.json', shellRoot); diff --git a/apps/mobile-shell/scripts/check-expo-config.mjs b/apps/mobile-shell/scripts/check-expo-config.mjs index 702d899e0..ec5514918 100644 --- a/apps/mobile-shell/scripts/check-expo-config.mjs +++ b/apps/mobile-shell/scripts/check-expo-config.mjs @@ -1,5 +1,4 @@ import {spawnSync} from 'node:child_process'; - import fs from 'node:fs'; const appConfigPath = new URL('../app.json', import.meta.url); diff --git a/apps/mobile-shell/scripts/check-expo-export.mjs b/apps/mobile-shell/scripts/check-expo-export.mjs index a4907e8b6..9643e66bc 100644 --- a/apps/mobile-shell/scripts/check-expo-export.mjs +++ b/apps/mobile-shell/scripts/check-expo-export.mjs @@ -1,5 +1,4 @@ import {spawnSync} from 'node:child_process'; - import fs from 'node:fs'; const shellRoot = new URL('../', import.meta.url); diff --git a/apps/mobile-shell/src/host-bridge/bridge.test.ts b/apps/mobile-shell/src/host-bridge/bridge.test.ts index 53726666f..d9a2736e1 100644 --- a/apps/mobile-shell/src/host-bridge/bridge.test.ts +++ b/apps/mobile-shell/src/host-bridge/bridge.test.ts @@ -625,24 +625,32 @@ describe('handleMobileHostBridgeMessage', () => { }); test('app.openExternalUrl 原生异常返回稳定 host_error', async () => { - vi.mocked(Linking.canOpenURL).mockRejectedValueOnce({ - code: 'native_linking_failure', - message: 'native linking failed', - nativeStackIOS: ['private native frame'], - }); + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => undefined); + try { + vi.mocked(Linking.canOpenURL).mockRejectedValueOnce({ + code: 'native_linking_failure', + message: 'native linking failed', + nativeStackIOS: ['private native frame'], + }); - const response = await send( - request('app.openExternalUrl', { - url: 'https://example.com/path', - }), - ); + const response = await send( + request('app.openExternalUrl', { + url: 'https://example.com/path', + }), + ); - const failedResponse = expectFailed(response); + const failedResponse = expectFailed(response); - expect(failedResponse.error).toEqual({ - code: 'host_error', - message: 'external URL cannot be opened', - }); + expect(failedResponse.error).toEqual({ + code: 'host_error', + message: 'external URL cannot be opened', + }); + expect(warnSpy).toHaveBeenCalledWith( + 'mobile HostBridge navigation failed for external.open', + ); + } finally { + warnSpy.mockRestore(); + } }); test('app.openExternalUrl 拒绝危险协议', async () => { diff --git a/apps/mobile-shell/src/host-bridge/dispatch.ts b/apps/mobile-shell/src/host-bridge/dispatch.ts index 9245caad2..df059be7e 100644 --- a/apps/mobile-shell/src/host-bridge/dispatch.ts +++ b/apps/mobile-shell/src/host-bridge/dispatch.ts @@ -1,4 +1,10 @@ import { type HostBridgeRequest } from '../../../../packages/shared/src/contracts/hostBridge'; +import { getMobileHostBridgeAppearanceColorScheme } from './appearance'; +import { setMobileAppBadgeCount } from './badge'; +import { + readMobileHostBridgeClipboardText, + writeMobileHostBridgeClipboardText, +} from './clipboard'; import { captureMobileHostBridgeImageFile, exportMobileHostBridgeAudioFile, @@ -9,32 +15,26 @@ import { importMobileHostBridgeImageFile, importMobileHostBridgeTextFile, } from './files'; -import { - failure, - type MobileHostBridgeNavigation, - unsupported, -} from './protocol'; -import { resetQrScannerForTest, scanMobileHostBridgeQrCode } from './scanner'; -import { - openShare, - resetMobileHostBridgeShareTargetForTest, - setMobileHostBridgeShareTarget, -} from './share'; -import { showMobileHostBridgeLocalNotification } from './notifications'; -import { - readMobileHostBridgeClipboardText, - writeMobileHostBridgeClipboardText, -} from './clipboard'; import { runMobileHostBridgeHapticsImpact } from './haptics'; -import { setMobileAppBadgeCount } from './badge'; -import { getMobileHostBridgeAppearanceColorScheme } from './appearance'; import { openMobileHostBridgeExternalUrl, openMobileHostBridgeNativePage, reloadMobileHostBridgeWebView, } from './navigation'; import { getMobileHostBridgeNetworkStatus } from './network'; +import { showMobileHostBridgeLocalNotification } from './notifications'; +import { + failure, + type MobileHostBridgeNavigation, + unsupported, +} from './protocol'; import { getMobileHostBridgeRuntimeResponse } from './runtime'; +import { resetQrScannerForTest, scanMobileHostBridgeQrCode } from './scanner'; +import { + openShare, + resetMobileHostBridgeShareTargetForTest, + setMobileHostBridgeShareTarget, +} from './share'; let navigation: MobileHostBridgeNavigation | null = null; diff --git a/apps/mobile-shell/src/host-bridge/filePayloads.ts b/apps/mobile-shell/src/host-bridge/filePayloads.ts index 8e076ec1b..6a76ce9d4 100644 --- a/apps/mobile-shell/src/host-bridge/filePayloads.ts +++ b/apps/mobile-shell/src/host-bridge/filePayloads.ts @@ -2,15 +2,15 @@ import type * as ImagePicker from 'expo-image-picker'; import { type FileImportImageResult, - type HostBridgeAudioMimeType, - type HostBridgeDocumentMimeType, - type HostBridgeImageMimeType, - type HostBridgeTextMimeType, HOST_BRIDGE_AUDIO_MIME_TYPES, HOST_BRIDGE_DOCUMENT_MIME_TYPES, HOST_BRIDGE_IMAGE_MIME_TYPES, HOST_BRIDGE_IMPORT_IMAGE_MAX_BYTES, HOST_BRIDGE_TEXT_MIME_TYPES, + type HostBridgeAudioMimeType, + type HostBridgeDocumentMimeType, + type HostBridgeImageMimeType, + type HostBridgeTextMimeType, normalizeHostBridgeExportFileName, normalizeHostBridgeImportFileName, } from '../../../../packages/shared/src/contracts/hostBridge'; diff --git a/apps/mobile-shell/src/host-bridge/files.ts b/apps/mobile-shell/src/host-bridge/files.ts index f1e85ab95..b97a2bfd0 100644 --- a/apps/mobile-shell/src/host-bridge/files.ts +++ b/apps/mobile-shell/src/host-bridge/files.ts @@ -14,11 +14,6 @@ import { type FileImportDocumentResult, type FileImportImageResult, type FileImportTextResult, - type HostBridgeAudioMimeType, - type HostBridgeError, - type HostBridgeImageMimeType, - type HostBridgeRequest, - type HostBridgeTextMimeType, HOST_BRIDGE_EXPORT_AUDIO_MAX_BYTES, HOST_BRIDGE_EXPORT_IMAGE_MAX_BYTES, HOST_BRIDGE_EXPORT_TEXT_MAX_BYTES, @@ -26,6 +21,11 @@ import { HOST_BRIDGE_IMPORT_DOCUMENT_MAX_BYTES, HOST_BRIDGE_IMPORT_IMAGE_MAX_BYTES, HOST_BRIDGE_IMPORT_TEXT_MAX_BYTES, + type HostBridgeAudioMimeType, + type HostBridgeError, + type HostBridgeImageMimeType, + type HostBridgeRequest, + type HostBridgeTextMimeType, normalizeHostBridgeExportFileName, normalizeHostBridgeImportFileName, } from '../../../../packages/shared/src/contracts/hostBridge'; @@ -43,7 +43,6 @@ import { normalizedBase64Data, normalizeExportedAudioFileName, normalizeExportedImageFileName, - normalizeImportedAudioFileName, normalizeImportedAudioMimeType, normalizeImportedDocumentMimeType, normalizeImportedTextMimeType, diff --git a/apps/mobile-shell/src/host-bridge/haptics.ts b/apps/mobile-shell/src/host-bridge/haptics.ts index eb10ead4a..da54a12a9 100644 --- a/apps/mobile-shell/src/host-bridge/haptics.ts +++ b/apps/mobile-shell/src/host-bridge/haptics.ts @@ -3,9 +3,9 @@ import * as Haptics from 'expo-haptics'; import { type HapticsImpactPayload, type HostBridgeError, + type HostBridgeHapticsImpactStyle, type HostBridgeRequest, normalizeHostBridgeHapticsImpactStyle, - type HostBridgeHapticsImpactStyle, } from '../../../../packages/shared/src/contracts/hostBridge'; import { invalidRequest, ok } from './protocol'; diff --git a/apps/mobile-shell/src/host-bridge/navigation.test.ts b/apps/mobile-shell/src/host-bridge/navigation.test.ts index a102c876b..cbf2adf45 100644 --- a/apps/mobile-shell/src/host-bridge/navigation.test.ts +++ b/apps/mobile-shell/src/host-bridge/navigation.test.ts @@ -6,12 +6,12 @@ import { HOST_BRIDGE_VERSION, type HostBridgeRequest, } from '../../../../packages/shared/src/contracts/hostBridge'; -import type { MobileHostBridgeNavigation } from './protocol'; import { openMobileHostBridgeExternalUrl, openMobileHostBridgeNativePage, reloadMobileHostBridgeWebView, } from './navigation'; +import type { MobileHostBridgeNavigation } from './protocol'; vi.mock('expo-linking', () => ({ canOpenURL: vi.fn(), diff --git a/apps/mobile-shell/src/host-bridge/navigation.ts b/apps/mobile-shell/src/host-bridge/navigation.ts index 12ece029c..dc6867ffa 100644 --- a/apps/mobile-shell/src/host-bridge/navigation.ts +++ b/apps/mobile-shell/src/host-bridge/navigation.ts @@ -8,14 +8,13 @@ import { type OpenExternalUrlPayload, } from '../../../../packages/shared/src/contracts/hostBridge'; import { - type MobileShellExternalNavigator, openMobileShellExternalNavigation, resolveMobileShellWebViewUrl, } from '../shell/navigation'; import { buildMobileShellUrl } from '../shell/url'; import { - type MobileHostBridgeNavigation, invalidRequest, + type MobileHostBridgeNavigation, ok, unsupported, } from './protocol'; diff --git a/apps/mobile-shell/src/host-bridge/network.ts b/apps/mobile-shell/src/host-bridge/network.ts index c87f6f8f1..22dc3f0d5 100644 --- a/apps/mobile-shell/src/host-bridge/network.ts +++ b/apps/mobile-shell/src/host-bridge/network.ts @@ -1,5 +1,5 @@ -import { getMobileNetworkStatus } from '../shell/network'; import { type HostBridgeRequest } from '../../../../packages/shared/src/contracts/hostBridge'; +import { getMobileNetworkStatus } from '../shell/network'; import { failure, ok } from './protocol'; function logMobileNetworkFailure(label: string, _error: unknown) { diff --git a/apps/mobile-shell/src/host-bridge/scanner.test.ts b/apps/mobile-shell/src/host-bridge/scanner.test.ts index ed5573aed..7514d4aca 100644 --- a/apps/mobile-shell/src/host-bridge/scanner.test.ts +++ b/apps/mobile-shell/src/host-bridge/scanner.test.ts @@ -1,8 +1,8 @@ import { afterEach, describe, expect, test, vi } from 'vitest'; import { - HOST_BRIDGE_SCANNER_TIMEOUT_MS, HOST_BRIDGE_PROTOCOL, + HOST_BRIDGE_SCANNER_TIMEOUT_MS, HOST_BRIDGE_VERSION, type HostBridgeRequest, } from '../../../../packages/shared/src/contracts/hostBridge'; diff --git a/apps/mobile-shell/src/host-bridge/scanner.ts b/apps/mobile-shell/src/host-bridge/scanner.ts index b80cbdfa6..38e92bf57 100644 --- a/apps/mobile-shell/src/host-bridge/scanner.ts +++ b/apps/mobile-shell/src/host-bridge/scanner.ts @@ -2,8 +2,8 @@ import { HOST_BRIDGE_SCANNER_TIMEOUT_MS, type HostBridgeError, type HostBridgeRequest, - type ScannerScanQrCodeResult, normalizeHostBridgeQrCodeValue, + type ScannerScanQrCodeResult, } from '../../../../packages/shared/src/contracts/hostBridge'; import { ok } from './protocol'; diff --git a/apps/mobile-shell/src/shell/QrScannerOverlay.tsx b/apps/mobile-shell/src/shell/QrScannerOverlay.tsx index 0953ec255..c0e827222 100644 --- a/apps/mobile-shell/src/shell/QrScannerOverlay.tsx +++ b/apps/mobile-shell/src/shell/QrScannerOverlay.tsx @@ -1,7 +1,7 @@ import { + type BarcodeScanningResult, Camera, CameraView, - type BarcodeScanningResult, } from 'expo-camera'; import { useCallback, useEffect, useState } from 'react'; import { Pressable, StyleSheet, Text, View } from 'react-native'; diff --git a/apps/mobile-shell/src/shell/ShellApp.tsx b/apps/mobile-shell/src/shell/ShellApp.tsx index 438c1a49c..390120cd8 100644 --- a/apps/mobile-shell/src/shell/ShellApp.tsx +++ b/apps/mobile-shell/src/shell/ShellApp.tsx @@ -28,9 +28,9 @@ import { } from 'react-native-webview'; import { - type HostBridgeEventName, HOST_BRIDGE_PROTOCOL, HOST_BRIDGE_VERSION, + type HostBridgeEventName, } from '../../../../packages/shared/src/contracts/hostBridge'; import { configureMobileHostBridgeNavigation, @@ -52,18 +52,18 @@ import { getMobileNetworkStatus, subscribeMobileNetworkStatus, } from './network'; +import { QrScannerOverlay } from './QrScannerOverlay'; import { MOBILE_SHELL_HOST_VERSION } from './runtime'; import { MOBILE_SHELL_SAFE_AREA_EDGES } from './safeArea'; import { buildMobileShellUrl, resolveMobileShellBaseWebUrl, } from './url'; +import { parseMobileWebViewHistoryStateMessage } from './webViewHistory'; import { MOBILE_WEBVIEW_BEFORE_CONTENT_SCRIPT, shouldBlockMobileWebViewNavigationRequest, } from './webViewPolicy'; -import { parseMobileWebViewHistoryStateMessage } from './webViewHistory'; -import { QrScannerOverlay } from './QrScannerOverlay'; const MobileShellWebView = WebView as unknown as ComponentType< WebViewProps & RefAttributes> @@ -304,6 +304,7 @@ export default function ShellApp() { }; }, [ baseWebUrl, + baseWebUrlOptions, resetNavigationCanGoBack, resetWebViewProcessFailureWindow, urlOptions, diff --git a/apps/mobile-shell/src/shell/deepLink.ts b/apps/mobile-shell/src/shell/deepLink.ts index a0e64b91d..ac2b6c9cb 100644 --- a/apps/mobile-shell/src/shell/deepLink.ts +++ b/apps/mobile-shell/src/shell/deepLink.ts @@ -1,8 +1,8 @@ import { buildMobileShellUrl, type MobileShellBaseWebUrlOptions, - resolveMobileShellBaseWebUrl, type MobileShellUrlOptions, + resolveMobileShellBaseWebUrl, } from './url'; const supportedHosts = new Set(['open', 'app']); diff --git a/apps/mobile-shell/src/shell/url.test.ts b/apps/mobile-shell/src/shell/url.test.ts index 8b5eb8077..62bc38dd1 100644 --- a/apps/mobile-shell/src/shell/url.test.ts +++ b/apps/mobile-shell/src/shell/url.test.ts @@ -7,8 +7,8 @@ import { } from '../../../../packages/shared/src/contracts/hostBridge'; import { ALLOWED_PRODUCTION_WEB_ORIGIN, - DEFAULT_MOBILE_SHELL_WEB_URL, buildMobileShellUrl, + DEFAULT_MOBILE_SHELL_WEB_URL, resolveMobileShellBaseWebUrl, } from './url'; diff --git a/docs/README.md b/docs/README.md index c177be6ec..549b216a7 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,156 +1,56 @@ # 文档总览 -`docs/` 现在按主题维护项目当前口径;旧后端路线文档开始聚合和删除,后续实现以 Rust / SpacetimeDB 当前基线为准。 +`docs/` 只把当前融合文档和仍在维护的专题文档作为实现依据。旧创作模板、旧创作入口及其专属服务文档仅保留为历史材料,不再要求继续实现或兼容。 -## 快速入口 +## 必读入口 -- [Agent 工作入口与执行准则](./%E3%80%90%E5%8D%8F%E4%BD%9C%E8%A7%84%E8%8C%83%E3%80%91Agent%E5%B7%A5%E4%BD%9C%E5%85%A5%E5%8F%A3%E4%B8%8E%E6%89%A7%E8%A1%8C%E5%87%86%E5%88%99-2026-06-22.md):复杂任务前的 Agent 阅读顺序、执行边界、技能路由、文档规则和验证口径。 -- [官网 SEO 地基实施约定](./technical/【SEO】官网SEO地基实施约定-2026-07-10.md):首页基础 head、robots/sitemap、唯一 H1、精确 SPA 路由与未知路径 404 的长期技术边界。 -- [经验沉淀](./experience/README.md):项目开发经验、UI 交接、历史实现经验。 -- [审计与复盘](./audits/README.md):工程审查、文本/乱码审计、专项落地审计。 -- [系统设计](./design/README.md):玩法、关系、物品与对话设计。 -- [技术方案](./technical/README.md):动画、服务端、外部产品形态拆解。 -- [架构决策](./adr/):记录已经接受的跨模块、长期有效架构取舍;对应长期摘要仍应同步到 `docs/project-memory/shared-memory/decision-log.md`。 -- [规划与优先级](./planning/README.md):当前阶段的迭代排序与落地优先级;创作流程统一总计划见 [【玩法创作】创作流程统一总计划-2026-05-30.md](./planning/%E3%80%90%E7%8E%A9%E6%B3%95%E5%88%9B%E4%BD%9C%E3%80%91%E5%88%9B%E4%BD%9C%E6%B5%81%E7%A8%8B%E7%BB%9F%E4%B8%80%E6%80%BB%E8%AE%A1%E5%88%92-2026-05-30.md)。 -- [参考目录](./reference/README.md):脚本/Function 速查入口。 - 重点补充:RPG 创作与运行时脚本职责地图见 [RPG_CREATION_AND_RUNTIME_SCRIPT_RESPONSIBILITY_MAP_2026-04-28.md](./reference/RPG_CREATION_AND_RUNTIME_SCRIPT_RESPONSIBILITY_MAP_2026-04-28.md)。 -- [埋点查询](./tracking/README.md):埋点原始事件与聚合投影的本地 SQL 查询。 -- [运营查询](./operations/README.md):任务、领奖、钱包对账等后台核查查询。 -- [PRD](./prd/README.md):产品需求与阶段计划;参考 MOKU / 幕间类 AI 文游的陶泥儿 `text-game` 模板口径见 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md](./prd/AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md),视觉小说模板 TXT 玩法平台化接入口径见 [AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md](./prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md),创意互动内容 Agent Phase 1 的 LangChain-Rust PoC、拼图闭环和并行任务拆分见 [CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md](./prd/CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md),幸存者类模板闭环见 [AI_NATIVE_SURVIVOR_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-05.md](./prd/AI_NATIVE_SURVIVOR_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-05.md),后台管理独立前端工程见 [ADMIN_WEB_CONSOLE_PRD_2026-04-30.md](./prd/ADMIN_WEB_CONSOLE_PRD_2026-04-30.md),新增 RPG 开场动画方案见 [AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md](./prd/AI_NATIVE_RPG_OPENING_ANIMATION_PRD_2026-04-25.md),新增抓大鹅 Match3D 玩法方案见 [AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md](./prd/AI_NATIVE_MATCH3D_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-04-30.md),方洞挑战创作、发布与试玩闭环见 [AI_NATIVE_SQUARE_HOLE_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-04.md](./prd/AI_NATIVE_SQUARE_HOLE_CREATOR_AND_GAMEPLAY_SYSTEM_PRD_2026-05-04.md),跳一跳俯视角玩法模板 PRD 见 [【玩法创作】跳一跳俯视角玩法模板PRD-2026-05-19.md](./prd/%E3%80%90%E7%8E%A9%E6%B3%95%E5%88%9B%E4%BD%9C%E3%80%91%E8%B7%B3%E4%B8%80%E8%B7%B3%E4%BF%AF%E8%A7%86%E8%A7%92%E7%8E%A9%E6%B3%95%E6%A8%A1%E6%9D%BFPRD-2026-05-19.md)。 +1. [Agent 工作入口与执行准则](./【协作规范】Agent工作入口与执行准则-2026-06-22.md) +2. [当前产品与工程约束](./【项目基线】当前产品与工程约束-2026-05-15.md) +3. [server-rs 与 SpacetimeDB 数据契约](./【后端架构】server-rs与SpacetimeDB数据契约-2026-05-15.md) +4. [平台入口与玩法链路](./【玩法创作】平台入口与玩法链路-2026-05-15.md) +5. [本地开发验证与生产运维](./【开发运维】本地开发验证与生产运维-2026-05-15.md) -拼图生成页步骤真进度、步骤内假进度和精简展示口径见 [【玩法创作】拼图生成页进度口径-2026-05-23.md](./%E3%80%90%E7%8E%A9%E6%B3%95%E5%88%9B%E4%BD%9C%E3%80%91%E6%8B%BC%E5%9B%BE%E7%94%9F%E6%88%90%E9%A1%B5%E8%BF%9B%E5%BA%A6%E5%8F%A3%E5%BE%84-2026-05-23.md)。 +团队长期约定、决策、流程和排障摘要统一从 [项目记忆入口](./project-memory/README.md) 读取。代码、当前融合文档与项目记忆冲突时,以代码和最新融合文档为准。 -从文字需求生成高一致性美术素材流程抽象出的发明专利交底稿见 [【专利交底】一种极低成本快速生成高质量2D小游戏高一致性美术素材的解决方案-2026-05-25.md](./%E3%80%90%E4%B8%93%E5%88%A9%E4%BA%A4%E5%BA%95%E3%80%91%E4%B8%80%E7%A7%8D%E6%9E%81%E4%BD%8E%E6%88%90%E6%9C%AC%E5%BF%AB%E9%80%9F%E7%94%9F%E6%88%90%E9%AB%98%E8%B4%A8%E9%87%8F2D%E5%B0%8F%E6%B8%B8%E6%88%8F%E9%AB%98%E4%B8%80%E8%87%B4%E6%80%A7%E7%BE%8E%E6%9C%AF%E7%B4%A0%E6%9D%90%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88-2026-05-25.md)。 +## 当前专题 -微信小程序虚拟支付接入、`wechat_mp_virtual` 渠道、`wx.requestVirtualPayment` 承接页和后端签名配置见 [【技术方案】微信虚拟支付接入-2026-05-26.md](./%E3%80%90%E6%8A%80%E6%9C%AF%E6%96%B9%E6%A1%88%E3%80%91%E5%BE%AE%E4%BF%A1%E8%99%9A%E6%8B%9F%E6%94%AF%E4%BB%98%E6%8E%A5%E5%85%A5-2026-05-26.md)。 +### 图片编辑器与 Agent -微信小程序、Expo React Native 移动壳与 Tauri 桌面壳共用的宿主能力契约、method / capability 白名单、H5 facade 和三端边界见 [【前端架构】宿主壳能力统一协议-2026-06-17.md](./%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91%E5%AE%BF%E4%B8%BB%E5%A3%B3%E8%83%BD%E5%8A%9B%E7%BB%9F%E4%B8%80%E5%8D%8F%E8%AE%AE-2026-06-17.md)。 +- [图片画布编辑器 MVP 接入方案](./technical/【前端架构】图片画布编辑器MVP接入方案-2026-06-11.md) +- [图片画布编辑器前端拆分计划](./technical/【前端架构】图片画布编辑器前端拆分计划-2026-06-17.md) +- [画布 Agent 对话面板](./【编辑器】画布Agent对话面板-2026-07-03.md) +- [画布 Agent 会话消息存 OSS](./adr/【ADR】画布Agent会话消息存OSS-2026-07-03.md) +- [浏览器内 AI Web 工程沙箱预览](./technical/【技术方案】浏览器内AIWeb工程沙箱预览方案-2026-06-13.md) +- [AI Web 工程 Runner 安全模型](./technical/【安全模型】AIWeb工程Runner与预览隔离威胁模型-2026-06-13.md) -Expo React Native 移动壳和 Tauri 桌面壳的工程结构、同源 WebView 安全边界、原生能力实现、构建配置和门禁要求见 [【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md](./%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91ExpoReactNative%E4%B8%8ETauri%E5%AE%BF%E4%B8%BB%E5%A3%B3%E6%96%B9%E6%A1%88-2026-06-17.md)。 +### 后端与公开数据 -原生壳本地开发与验证命令统一从根工程进入:移动壳使用 `npm run mobile-shell:dev`、`npm run mobile-shell:typecheck`、`npm run mobile-shell:test`,桌面壳使用 `npm run desktop-shell:dev`、`npm run desktop-shell:typecheck`、`npm run desktop-shell:test`,跨端结构和能力门禁使用 `npm run check:native-shells`。 +- [外部生成 Worker 化方案](./technical/【后端架构】外部生成Worker化方案-2026-06-03.md) +- [统一公开作品 Read Model 设计](./technical/【后端架构】统一公开作品ReadModel设计-2026-05-26.md) +- [外部 OpenAPI 与 API Key 接入方案](./【后端架构】外部OpenAPI与APIKey接入方案-2026-06-19.md) +- [SpacetimeDB 连接池取消安全](./【后端架构】SpacetimeDB连接池租约Drop兜底与取消安全-2026-06-11.md) -`/editor/agent` 浏览器内 AI Web 工程编辑器的静态 SPA 沙箱预览 MVP,采用“平台编辑器壳 + api-server 控制面 + 独立 runner worker + 独立预览域”四层结构;技术方案、威胁模型和验收清单见 [【技术方案】浏览器内AIWeb工程沙箱预览方案-2026-06-13.md](./technical/【技术方案】浏览器内AIWeb工程沙箱预览方案-2026-06-13.md)、[【安全模型】AIWeb工程Runner与预览隔离威胁模型-2026-06-13.md](./technical/【安全模型】AIWeb工程Runner与预览隔离威胁模型-2026-06-13.md) 和 [【测试用例】AIWeb工程静态预览MVP验收清单-2026-06-13.md](./technical/【测试用例】AIWeb工程静态预览MVP验收清单-2026-06-13.md)。P1 先用确定性 mock Agent 生成结构化 patch、真实打通项目 / 快照 / 构建 / artifact / 预览闭环,落地拆分见 [【技术方案】EditorAgentMockAgentP1落地计划-2026-06-15.md](./technical/【技术方案】EditorAgentMockAgentP1落地计划-2026-06-15.md)。 +### 后台、宿主壳与运维 -`/editor/canvas` 图片画布编辑器的画布素材 ZIP 导出能力,入口放在右上角标题栏下载图标内,采用前端 JSZip 打包画布中有效图层引用的上传图、生成图、修改结果和角色动作序列帧;动作图层右键“导出为”提供序列帧 ZIP(含前端生成的 `preview.gif`)与 Spine JSON ZIP 两个二级选项,方案见 [【前端架构】图片画布素材导出方案-2026-06-15.md](./technical/【前端架构】图片画布素材导出方案-2026-06-15.md)。 +- [Dashboard 运营看板方案](./technical/【后台管理】Dashboard运营看板方案-2026-06-23.md) +- [后台多账号与 Tab 访问权限](./technical/【后台管理】多账号与Tab访问权限方案-2026-07-14.md) +- [宿主壳能力统一协议](./【前端架构】宿主壳能力统一协议-2026-06-17.md) +- [Expo React Native 与 Tauri 宿主壳方案](./【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md) +- [Pingora 独立网关试点](./technical/【开发运维】Pingora独立网关试点-2026-06-11.md) +- [本地 SSH 服务器管理面板](./technical/【开发运维】本地SSH服务器管理面板技术方案-2026-06-11.md) -`/editor/canvas` 右侧画布 Agent 对话面板、会话持久化、SSE 事件、附件与生成落画板例外见 [【编辑器】画布Agent对话面板-2026-07-03.md](./【编辑器】画布Agent对话面板-2026-07-03.md);消息正文存 OSS、元数据进 SpacetimeDB 的取舍见 [【ADR】画布Agent会话消息存OSS-2026-07-03.md](./adr/【ADR】画布Agent会话消息存OSS-2026-07-03.md)。 +### 测试与协作 -图片画布生成类面板的模型泥点默认 JSON、SpacetimeDB 运行时事实源、后台“模型定价”页面和主站动态下发口径见 [【编辑器】模型定价配置管理方案-2026-06-22.md](./%E3%80%90%E7%BC%96%E8%BE%91%E5%99%A8%E3%80%91%E6%A8%A1%E5%9E%8B%E5%AE%9A%E4%BB%B7%E9%85%8D%E7%BD%AE%E7%AE%A1%E7%90%86%E6%96%B9%E6%A1%88-2026-06-22.md)。 +- [React 组件测试准则](./technical/【前端测试】React组件测试准则-2026-06-26.md) +- [AI Web 工程静态预览验收清单](./technical/【测试用例】AIWeb工程静态预览MVP验收清单-2026-06-13.md) +- [当前阶段规划](./planning/README.md) -React 组件测试的用户行为、稳定契约、hook / model 分层断言口径,以及避免内部 DOM 探针、图标 class 和完整对象快照式断言的规则见 [【前端测试】React组件测试准则-2026-06-26.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%B5%8B%E8%AF%95%E3%80%91React%E7%BB%84%E4%BB%B6%E6%B5%8B%E8%AF%95%E5%87%86%E5%88%99-2026-06-26.md)。 +## 历史文档边界 -桌面端全站客服悬浮入口的全局接入点、固定层级、拖拽边界和移动端隐藏口径见 [【前端架构】全站客服悬浮入口接入约定-2026-06-23.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91%E5%85%A8%E7%AB%99%E5%AE%A2%E6%9C%8D%E6%82%AC%E6%B5%AE%E5%85%A5%E5%8F%A3%E6%8E%A5%E5%85%A5%E7%BA%A6%E5%AE%9A-2026-06-23.md)。 +- 旧创作模板、旧创作入口、旧运行态和旧素材生成方案不再从本页索引,也不再作为修复目标。 +- `maincloud`、旧 Node/Express/PostgreSQL/Go 后端和人工 `spacetime --root-dir` 口径均已废弃。 +- 历史文档不得覆盖当前融合文档、代码契约或 `docs/project-memory/shared-memory/decision-log.md` 中更新的决策。 -桌面端 `/creation` 创作工具主页、顶级“草稿”入口替换为“项目”、最近项目、新建项目和陶泥儿精选素材瀑布流的落地计划见 [【玩法创作】创作主页与项目入口改版计划-2026-06-18.md](./%E3%80%90%E7%8E%A9%E6%B3%95%E5%88%9B%E4%BD%9C%E3%80%91%E5%88%9B%E4%BD%9C%E4%B8%BB%E9%A1%B5%E4%B8%8E%E9%A1%B9%E7%9B%AE%E5%85%A5%E5%8F%A3%E6%94%B9%E7%89%88%E8%AE%A1%E5%88%92-2026-06-18.md)。 +## 命名规则 -本地通过 SSH alias 管理多台服务器、查看硬件 / systemd / HTTP 健康状态并执行受控服务启停的 egui 桌面工具见 [【开发运维】本地SSH服务器管理面板技术方案-2026-06-11.md](./technical/【开发运维】本地SSH服务器管理面板技术方案-2026-06-11.md)。 - -生产部署切换到 systemd + Nginx + SpacetimeDB 自托管的总方案见 [PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md](./technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md),该文档也是当前生产 Jenkinsfile 的唯一入口。Pingora 只作为独立二进制影子网关试点时,边界、路由口径与替换前验收见 [【开发运维】Pingora独立网关试点-2026-06-11.md](./technical/【开发运维】Pingora独立网关试点-2026-06-11.md)。SpacetimeDB 表结构变更、自动迁移边界和保留旧数据的分阶段迁移流程见 [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md);private 表迁移 JSON 导入导出、HTTP 413 分片导入和旧数据库迁移流水线经验见 [SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md](./technical/SPACETIMEDB_JSON_STRING_MIGRATION_PROCEDURE_2026-04-27.md) 与 [JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md](./technical/JENKINS_SPACETIMEDB_DATABASE_MIGRATION_PIPELINES_2026-04-29.md);后台管理独立前端工程技术方案见 [ADMIN_WEB_CONSOLE_TECHNICAL_SOLUTION_2026-04-30.md](./technical/ADMIN_WEB_CONSOLE_TECHNICAL_SOLUTION_2026-04-30.md),Dashboard 默认入口、运营指标和统计口径见 [【后台管理】Dashboard运营看板方案-2026-06-23.md](./technical/【后台管理】Dashboard运营看板方案-2026-06-23.md),owner/member 多账号、18 个一级 Tab 权限和逐请求鉴权方案见 [【后台管理】多账号与Tab访问权限方案-2026-07-14.md](./technical/【后台管理】多账号与Tab访问权限方案-2026-07-14.md)。 - -SpacetimeDB 表结构变更、自动迁移边界和保留旧数据的分阶段迁移流程见 [SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md)。 - -创作 Agent 问答流式失败时保留已显示回复、并透出更具体上游错误的契约见 [CREATION_AGENT_STREAM_FAILURE_RETENTION_FIX_2026-05-05.md](./technical/CREATION_AGENT_STREAM_FAILURE_RETENTION_FIX_2026-05-05.md)。 - -`maincloud` 相关脚本、环境变量、测试名和文档要求已统一判定为历史残留,后续禁止新增、运行或引用;当前后端 smoke 使用 `npm run dev:api-server` 与 `/healthz`,详细规则见 [MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md](./technical/MAINCLOUD_REFERENCE_REMOVAL_POLICY_2026-05-06.md)。 - -基于 LangChain-Rust,以“感知—思考—记忆—行动—反思—协作”闭环完成图文创意理解、拼图模板选择、积分范围确认、拼图草稿字段填充、立即试玩和自然语言修订草稿字段的 Agent 方案见 [CREATIVE_INTERACTIVE_CONTENT_AGENT_TECHNICAL_SOLUTION_2026-05-05.md](./technical/CREATIVE_INTERACTIVE_CONTENT_AGENT_TECHNICAL_SOLUTION_2026-05-05.md)。 - -创意互动内容 Agent Phase 1 的产品边界、实现细节、SpacetimeDB 落点、前端接入和可并行任务拆分见 [CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md](./prd/CREATIVE_INTERACTIVE_AGENT_PHASE1_LANGCHAIN_RUST_PUZZLE_LOOP_PRD_2026-05-05.md)。 - -视觉小说模板接入以 [AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md](./prd/AI_NATIVE_VISUAL_NOVEL_TEMPLATE_PRD_2026-05-05.md) 为最新口径:只吸收外部 TXT 玩法的模板创作与运行经验,禁止迁入外部平台功能,并删除回放。 - -AI 文字游戏模板接入以 [AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md](./prd/AI_NATIVE_TEXT_GAME_TEMPLATE_MOKU_REFERENCE_PRD_2026-05-05.md) 为最新口径:只吸收 MOKU / 幕间类 AI 文游的剧本游乐场、自由行动、AI GM、记忆和模拟器强反馈经验,禁止迁入外部社区、支付、榜单、私有存档或回放。 - -前端 Server-Sent Events 客户端传输层收口到 `src/services/sseStream.ts`,事件边界、UTF-8 flush、JSON 解析跳过和提前取消约定见 [【前端架构】SSE客户端传输层收口约定-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91SSE%E5%AE%A2%E6%88%B7%E7%AB%AF%E4%BC%A0%E8%BE%93%E5%B1%82%E6%94%B6%E5%8F%A3%E7%BA%A6%E5%AE%9A-2026-06-03.md)。 - -平台入口公开作品身份、跨玩法去重、公开作品流聚合、推荐运行态 kind 判定、推荐 runtime 启动意图、ready 判定和最新排序收口到 `src/components/platform-entry/platformPublicGalleryFlow.ts`,规则见 [【前端架构】平台入口PublicGalleryFlowModule收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91%E5%B9%B3%E5%8F%B0%E5%85%A5%E5%8F%A3PublicGalleryFlowModule%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 - -统一作品详情页的玩法 kind、详情打开策略、自有作品动作模式、编辑 / 点赞 / 改造 / 启动意图和公开详情映射收口到 `src/components/platform-entry/platformPublicWorkDetailFlow.ts`;抓大鹅公开详情映射与启动 / 编辑 Adapter 的素材归一仍归 `platformMatch3DRuntimeProfile.ts`,规则见 [【前端架构】PlatformPublicWorkDetailFlow收口计划-2026-06-03.md](./technical/【前端架构】PlatformPublicWorkDetailFlow收口计划-2026-06-03.md)。 - -创作中心作品架打开动作由 `CreationWorkShelfItem.actions.open` 统一承载,生产 Hub 只接收 `CreationWorkShelfItem[]` 与 UI 状态,不再接收各玩法 raw items 和回调列阵,规则见 [【前端架构】WorkShelfModule收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91WorkShelfModule%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 - -作品架删除确认的标题、删除说明、草稿 notice key 和拼图派生稳定 ID 收口到 `src/components/platform-entry/platformCreationWorkDeleteFlow.ts`,平台壳只保留删除 API、刷新、错误和页面跳转副作用,规则见 [【前端架构】CreationWorkDeleteFlow收口计划-2026-06-04.md](./technical/【前端架构】CreationWorkDeleteFlow收口计划-2026-06-04.md)。 - -创作入口点击的占位、隐藏模板拦截、未知入口 no-op 与工作台启动目标收口到 `src/components/platform-entry/platformCreationLaunchModel.ts`,壳层只执行启动前准备、错误提示和受保护动作,规则见 [【前端架构】PlatformCreationLaunchModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformCreationLaunchModel收口计划-2026-06-04.md)。 - -平台入口公开码搜索的用户 ID、陶泥号、RPG 作品号、各玩法作品号前缀、per-play 公开码匹配、详情卡 DTO 映射和失败回退顺序收口到 `src/components/platform-entry/platformPublicCodeSearchModel.ts`,壳层只按计划执行网络读取、详情打开和错误归航副作用,规则见 [【前端架构】PlatformPublicCodeSearchModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformPublicCodeSearchModel收口计划-2026-06-04.md)。 - -个人“玩过作品”面板的玩法别名、`worldKey` 前缀兜底、RPG 公开详情 payload 和大鱼吃小鱼 gallery miss fallback 收口到 `src/components/platform-entry/platformPlayedWorkOpenModel.ts`,壳层只执行面板关闭、gallery 读取、详情打开和错误提示副作用,规则见 [【前端架构】PlatformPlayedWorkOpenModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformPlayedWorkOpenModel收口计划-2026-06-04.md)。 - -平台入口生成页进度 tick 的 stage 到生成状态映射、终态判定和视觉小说轻量生成特例收口到 `src/components/platform-entry/platformGenerationProgressTickModel.ts`,壳层只保留 `Date.now()`、`setInterval` 和 cleanup 副作用,规则见 [【前端架构】PlatformGenerationProgressTickModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformGenerationProgressTickModel收口计划-2026-06-04.md)。 - -平台壳的拼图 runtime 恢复 work、方洞 session draft 转 profile、视觉小说 work detail 转 Agent session、跳一跳 pending session、敲木鱼 detail 恢复 session、敲木鱼生成中作品摘要和敲木鱼 pending session DTO 映射收口到 `src/components/platform-entry/platformMiniGameSessionMappingModel.ts`,壳层只保留网络、状态、URL 与 stage 副作用,规则见 [【前端架构】PlatformMiniGameSessionMappingModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformMiniGameSessionMappingModel收口计划-2026-06-04.md)。 - -平台小游戏生成状态的恢复、失败 / 完成收尾、展示 rebase、拼图后端进度合并、抓大鹅生成资产旁路进度合并和 ready / generating 判定收口到 `src/components/platform-entry/platformMiniGameDraftGenerationStateModel.ts`,壳层只保留 API、后台任务、React state、URL 与 stage 副作用,规则见 [【前端架构】PlatformMiniGameDraftGenerationStateModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformMiniGameDraftGenerationStateModel收口计划-2026-06-04.md)。 - -平台小游戏草稿恢复和提交所需的拼图 / 抓大鹅表单 payload、拼图作品更新 payload、拼图编译 action、跳一跳 / 敲木鱼生成 action、pending metadata 与拼图 form-only 草稿判定收口到 `src/components/platform-entry/platformMiniGameDraftPayloadModel.ts`,壳层只保留 API、Action 执行、background task 与状态副作用,规则见 [【前端架构】PlatformMiniGameDraftPayloadModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformMiniGameDraftPayloadModel收口计划-2026-06-04.md)。 - -平台拼图生成完成后刷新恢复的草稿归一化与可恢复完成态判定收口到 `src/components/platform-entry/platformPuzzleDraftRecoveryModel.ts`,恢复链路只有在首图、关卡画面、UI spritesheet 与关卡背景资产包完整时才抬为 ready,规则见 [【前端架构】PlatformPuzzleDraftRecoveryModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformPuzzleDraftRecoveryModel收口计划-2026-06-04.md)。 - -拼图排行榜提交回包后的服务端 run 快照合并收口到 `src/components/platform-entry/platformPuzzleRuntimeStateModel.ts`,只合并排行榜、run 身份、通关数上限和下一关 handoff,保留前端即时裁决的关卡状态与棋盘,规则见 [【前端架构】PlatformPuzzleRuntimeStateModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformPuzzleRuntimeStateModel收口计划-2026-06-04.md)。 - -后端拼图发布 / 待发布门槛收紧到首图、关卡画面、UI spritesheet 与关卡背景资产包完整,`module-puzzle` 的 preview blockers 与 `api-server` 的 session stage 判定保持同一规则,方案见 [【后端架构】PuzzlePublishAssetGate收紧计划-2026-06-04.md](./technical/【后端架构】PuzzlePublishAssetGate收紧计划-2026-06-04.md)。 - -平台入口个人钱包本地 delta、dashboard 乐观更新与服务端快照对账规则收口到 `src/components/platform-entry/platformProfileWalletDeltaModel.ts`,平台壳只保留 API、ref 与 state 副作用,规则见 [【前端架构】PlatformProfileWalletDeltaModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformProfileWalletDeltaModel收口计划-2026-06-04.md)。 - -Bark Battle 草稿三图完整性、生成状态归一、作品架摘要恢复草稿配置、发布快照 / 发布回包资产兜底和草稿 / 已发布作品进入 runtime 前的 `BarkBattlePublishedConfig` 映射收口到 `src/components/platform-entry/barkBattleWorkCache.ts`,规则见 [【前端架构】BarkBattleWorkCache草稿状态收口计划-2026-06-04.md](./technical/【前端架构】BarkBattleWorkCache草稿状态收口计划-2026-06-04.md)。 - -平台首页推荐 runtime 的匿名 Runtime Guest Token、已登录 background auth、非 embedded no-op 和拼图 isolated/default auth mode 计划收口到 `src/components/platform-entry/platformRecommendRuntimeAuthModel.ts`,规则见 [【前端架构】PlatformRecommendRuntimeAuthModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformRecommendRuntimeAuthModel收口计划-2026-06-04.md)。 - -平台首页推荐 runtime 自动启动的桌面 / Tab / stage / loading gate、active entry 查找、ready 判定和 clear/start/noop 决策收口到 `src/components/platform-entry/platformPublicGalleryFlow.ts`,规则见 [【前端架构】PlatformRecommendRuntimeAutoStart收口计划-2026-06-04.md](./technical/【前端架构】PlatformRecommendRuntimeAutoStart收口计划-2026-06-04.md)。 - -RPG Agent 结果页发布门禁展示和预览来源 label 收口到 `src/components/platform-entry/platformRpgAgentResultPreviewModel.ts`,壳层只保留 session/profile 编排和结果页 props 传递,规则见 [【前端架构】PlatformRpgAgentResultPreviewModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformRpgAgentResultPreviewModel收口计划-2026-06-04.md)。 - -平台入口创作生成通知、pending 作品架占位、作品详情更新回填、失败覆盖、跨玩法草稿打开优先级、拼图稳定 ID 和草稿 Tab 未读点收口到 `src/components/platform-entry/platformDraftGenerationShelfModel.ts`,规则见 [【前端架构】DraftGenerationShelfModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91DraftGenerationShelfModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 - -平台入口创作恢复 URL 私有 query、初始恢复判定、创作直达恢复目标解析、恢复目标身份匹配、跳一跳 / 敲木鱼恢复阶段落点、拼图 runtime query 与拼图稳定身份互推收口到 `src/components/platform-entry/platformCreationUrlStateModel.ts` 和 `src/components/platform-entry/platformPuzzleIdentityModel.ts`,规则见 [【前端架构】CreationUrlStateModel收口计划-2026-06-03.md](./technical/【前端架构】CreationUrlStateModel收口计划-2026-06-03.md)。 - -平台入口错误 / 完成弹窗的文案归一、来源格式、候选择一、dismiss key 与任务完成文案收口到 `src/components/platform-entry/platformDialogStateModel.ts`,规则见 [【前端架构】PlatformDialogStateModel收口计划-2026-06-03.md](./technical/【前端架构】PlatformDialogStateModel收口计划-2026-06-03.md)。 - -平台 UI Kit 的提示 / 确认弹窗收口到 `src/components/common/UnifiedConfirmDialog.tsx`,复制反馈收口到 `src/components/common/useCopyFeedback.ts`、`src/components/common/CopyFeedbackButton.tsx`、`src/components/common/CopyCodeButton.tsx` 与 `src/components/common/CopyFeedbackMessage.tsx`,基础状态提示收口到 `src/components/common/PlatformStatusMessage.tsx`,运行态短错误 / 成功 / 反馈 toast 收口到 `src/components/common/PlatformRuntimeStatusToast.tsx`,平台空态 / 轻量加载态收口到 `src/components/common/PlatformEmptyState.tsx`,平台动作按钮收口到 `src/components/common/PlatformActionButton.tsx`,平台白底子面板 / 小型列表卡片收口到 `src/components/common/PlatformSubpanel.tsx`,平台输入框 / 文本域收口到 `src/components/common/PlatformTextField.tsx`,平台字段标题收口到 `src/components/common/PlatformFieldLabel.tsx`,平台媒体预览框收口到 `src/components/common/PlatformMediaFrame.tsx`,平台胶囊状态标签收口到 `src/components/common/PlatformPillBadge.tsx`,平台图片全屏预览收口到 `src/components/common/PlatformImagePreviewModal.tsx`,平台 / 个人中心弹窗关闭按钮收口到 `src/components/common/PlatformModalCloseButton.tsx`,底层继续复用 `UnifiedModal`;普通提示、确认 / 取消、危险确认、复制状态机、短代码复制 chip、复制按钮表现、白底 / 个人中心 / 认证入口 token 状态条、运行态状态 toast、无操作空态、主动作按钮、白底子面板、白底交互列表卡片、普通输入字段、字段标题、图片源 / fallback / 固定比例媒体预览、全屏黑底图片查看、单个状态 / 标签 chip 和圆形关闭按钮优先使用公共 Module,规则见 [【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md](./technical/【前端架构】PlatformUiKit弹窗组件收口计划-2026-06-08.md)。 - -平台入口受保护数据失效后的 stage 去留判定,以及缺失草稿 / 作品 / run 时的阶段回退,收口到 `src/components/platform-entry/platformSelectionStageModel.ts`,壳层只执行缓存清空、布尔事实汇总和必要跳转,规则见 [【前端架构】PlatformSelectionStageModel收口计划-2026-06-04.md](./technical/【前端架构】PlatformSelectionStageModel收口计划-2026-06-04.md)。 - -小游戏 runtime client 的路径编码、JSON 请求、runtime guest auth 与 retry 选项收口到 `src/services/runtimeRequest.ts`,Match3D、SquareHole、Big Fish、Bark Battle、Puzzle 公开 / 推荐运行态请求、Jump Hop / Wooden Fish 正式 run 请求和 Visual Novel 局部 JSON runtime 请求已先迁移,规则见 [【前端架构】RuntimeClientFamily收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91RuntimeClientFamily%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 - -抓大鹅 runtime profile 的公开详情转 work、session draft 转 profile、生成背景资产提升和 run/profile/public detail 素材优先级收口到 `src/components/platform-entry/platformMatch3DRuntimeProfile.ts`,规则见 [【前端架构】Match3DRuntimeProfile收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91Match3DRuntimeProfile%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 - -公开作品分类选项、搜索、跨来源去重、今日筛选、排行排序和时间戳解析收口到 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts`,规则见 [【前端架构】PublicGalleryViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91PublicGalleryViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 - -公开作品的玩法类型 label、公开作者 lookup 与游玩 / 改造 / 点赞等紧凑计数格式收口到 `src/components/rpg-entry/rpgEntryWorldPresentation.ts`,规则见 [【前端架构】PublicWorkPresentation收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91PublicWorkPresentation%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 - -推荐 feed 的公开作品去重、普通内容过滤、active 窗口与上一条 / 下一条回环选择也收口到 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts`,规则见 [【前端架构】RecommendFeedViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91RecommendFeedViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 - -移动端推荐首页 swipe deck 的拖拽阈值、offset clamp、commit 方向、rail class 和分享文案收口到 `src/components/rpg-entry/rpgEntryRecommendSwipeDeckModel.ts`,规则见 [【前端架构】RecommendSwipeDeckModel收口计划-2026-06-03.md](./technical/【前端架构】RecommendSwipeDeckModel收口计划-2026-06-03.md)。 - -排行频道的默认 tab、tab 文案、空态文案、排序字段与指标 label/value 收口到 `src/components/rpg-entry/rpgEntryPublicGalleryViewModel.ts`,规则见 [【前端架构】RankingViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91RankingViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 - -每日任务卡片与任务中心弹窗的任务选择、进度、状态标签和按钮文案收口到 `src/components/rpg-entry/rpgEntryProfileTaskViewModel.ts`,规则见 [【前端架构】ProfileTaskViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91ProfileTaskViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 - -个人数据卡、钱包 chip 与“玩过”弹窗的计数、时长、作品类型和作品号展示收口到 `src/components/rpg-entry/rpgEntryProfileDashboardPresentation.ts`,规则见 [【前端架构】ProfileDashboardPresentation收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91ProfileDashboardPresentation%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 - -个人资金展示的账单来源、金额正负号、余额兜底、充值价格、商品主值和会员摘要收口到 `src/components/rpg-entry/rpgEntryProfileFundsViewModel.ts`,规则见 [【前端架构】ProfileFundsViewModel收口计划-2026-06-03.md](./technical/%E3%80%90%E5%89%8D%E7%AB%AF%E6%9E%B6%E6%9E%84%E3%80%91ProfileFundsViewModel%E6%94%B6%E5%8F%A3%E8%AE%A1%E5%88%92-2026-06-03.md)。 - -## 推荐阅读顺序 - -1. 先看 [经验沉淀](./experience/README.md),快速建立这个项目的开发共识。 -2. 再看 [工程审查总览](./audits/engineering/README.md) 和 [文本审计总览](./audits/text/README.md),了解当前风险。 -3. 需要排期时看 [规划与优先级](./planning/README.md),创作链路相关任务优先看创作流程统一总计划。 -4. 需要补方案时进入 [系统设计](./design/README.md) / [技术方案](./technical/README.md);涉及后端先看 [当前后端实现基线](./technical/CURRENT_BACKEND_IMPLEMENTATION_BASELINE_2026-04-25.md),涉及生产发布链路先看 [生产部署计划](./technical/PRODUCTION_DEPLOYMENT_PLAN_2026-05-02.md),涉及 SpacetimeDB 表结构变更时再看 [表结构变更约束](./technical/SPACETIMEDB_SCHEMA_CHANGE_CONSTRAINTS.md)。 -5. 需要对齐目标边界时再进入 [PRD](./prd)。 - -## 分类规则 - -- `experience/`:偏方法论、交接经验、长期有效的开发结论。 -- `audits/`:偏“现状扫描 / 问题定位 / 是否达标”的审查类文档。 -- `design/`:偏玩法机制、叙事关系、系统结构设计。 -- `technical/`:偏技术选型、实现路线、竞品/产品形态拆解。 -- `planning/`:偏阶段优先级与推进顺序。 -- `reference/`:偏目录、速查、检索辅助。 -- `tracking/`:偏埋点原始事实和聚合投影查询,不放任务进度或钱包对账。 -- `operations/`:偏后台运营核查、对账和排障查询。 - -## 文档命名规则 - -后续新增的 Markdown 文档文件名必须以分类标签开头,格式为 `【标签名】中文标题-日期.md`。标签用于跨目录检索,不替代上面的目录分类;历史文档不要求批量重命名,除非本次任务明确涉及该文档。 +后续新增 Markdown 文档文件名使用 `【标签名】中文标题-日期.md`。历史文件不做无关批量重命名;本次涉及的旧文档可直接删除或在当前入口中取消引用。 diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index ffb2c1191..74aa4db36 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -4207,3 +4207,9 @@ - 就绪边界:HTTP `/readyz` 同时验证调用池握手和缓存读连接;required subscription 失败必须不就绪。非 HTTP worker / controller 不创建缓存读连接,继续使用 1 条调用连接和各自的队列窄订阅。 - 运维口径:`GENARRATIVE_SPACETIME_POOL_SIZE=8` 表示 8 条调用连接,HTTP 基础拓扑另加 1 条缓存读连接;外部生成和充值过期监听的独立窄订阅不计入该值。读模型行缓存从 8 份降为 1 份,但 SDK 空 table metadata、8 条调用 socket 和 runner 仍存在,不承诺总 RSS 等比例降为八分之一。 - 验证方式:`cargo test -p spacetime-client --manifest-path server-rs/Cargo.toml --lib`、`cargo check -p api-server --manifest-path server-rs/Cargo.toml`、`npm run check:encoding`、`git diff --check`;发布后在 8 个调用槽暖机后对比 api-server cgroup memory / PSS,并确认 `/readyz` 与代表性 gallery、公开详情、创作入口和用户标签读取正常。 + +## 2026-07-17 旧创作模板与入口停止维护 + +- 决策:旧创作模板、旧创作入口及其专属运行服务进入下线范围,不再为跳一跳、抓大鹅 Match3D、儿童动作 Demo 等旧链路修复兼容问题、补生成脚本或维持专属门禁。 +- 边界:共享账号、钱包、资产、图片编辑器、公开作品、通用 HostBridge、API、SpacetimeDB、发布运维和安全能力不属于旧链路,仍需维持正式门禁。历史文档只作为背景材料,不再作为旧入口继续运行的依据。 +- 清理方式:允许直接删除已经失效的旧素材生成命令、入口路由、专属服务和对应测试;删除工程链路时仍需核对是否被当前共享能力引用,不能连带移除仍在使用的公共契约或持久化事实。 diff --git a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md index 417a7b583..c29d747f5 100644 --- a/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md +++ b/docs/【前端架构】ExpoReactNative与Tauri宿主壳方案-2026-06-17.md @@ -74,6 +74,8 @@ src/ 根级原生壳检查还会反查两端单端配置检查的依赖版本门禁:移动壳必须继续锁定 Expo SDK、React Native、WebView、EAS CLI 和 `package-lock.json` 实际解析版本;桌面壳必须继续锁定 Tauri CLI、Cargo manifest、`Cargo.lock` 解析版本和直接依赖关系。升级 Expo / React Native / Tauri 不能只改 `package.json` 或 `Cargo.toml`,必须同时更新单端配置检查、锁文件和本文档里的宿主能力假设。 +桌面壳的 `permissions/autogenerated/` 由 Tauri `build.rs` 生成且保持 gitignored。`npm run check:native-shells` 必须先运行桌面 Rust 测试生成权限清单,再执行桌面配置检查,最后执行 release build smoke;不能让干净检出因为静态检查早于生成步骤而失败,也不能把生成权限文件提交进仓库。 + 结构门禁按完整相对路径反查文档和目录:微信桥接层为 `miniprogram/host-bridge/dispatch.js`、`miniprogram/host-bridge/payment.js`、`miniprogram/host-bridge/protocol.js`、`miniprogram/host-bridge/shareGrid.js`、`miniprogram/host-bridge/subscribeMessage.js`、`miniprogram/host-bridge/webView.js`;微信 shell 层为 `miniprogram/shell/payment.js`、`miniprogram/shell/shareGrid.js`、`miniprogram/shell/subscribeMessage.js`、`miniprogram/shell/webView.js`;微信页面包装层为 `miniprogram/pages/share-grid/index.js`、`miniprogram/pages/share-grid/index.json`、`miniprogram/pages/share-grid/index.wxml`、`miniprogram/pages/share-grid/index.wxss`、`miniprogram/pages/subscribe-message/index.js`、`miniprogram/pages/subscribe-message/index.json`、`miniprogram/pages/subscribe-message/index.wxml`、`miniprogram/pages/subscribe-message/index.wxss`、`miniprogram/pages/web-view/index.js`、`miniprogram/pages/web-view/index.json`、`miniprogram/pages/web-view/index.wxml`、`miniprogram/pages/web-view/index.wxss`、`miniprogram/pages/wechat-pay/index.js`、`miniprogram/pages/wechat-pay/index.json`、`miniprogram/pages/wechat-pay/index.wxml`、`miniprogram/pages/wechat-pay/index.wxss`;移动源码根为 `apps/mobile-shell/src/env.d.ts`;移动桥接层为 `apps/mobile-shell/src/host-bridge/appearance.test.ts`、`apps/mobile-shell/src/host-bridge/appearance.ts`、`apps/mobile-shell/src/host-bridge/badge.test.ts`、`apps/mobile-shell/src/host-bridge/badge.ts`、`apps/mobile-shell/src/host-bridge/bridge.ts`、`apps/mobile-shell/src/host-bridge/capabilities.test.ts`、`apps/mobile-shell/src/host-bridge/capabilities.ts`、`apps/mobile-shell/src/host-bridge/clipboard.test.ts`、`apps/mobile-shell/src/host-bridge/clipboard.ts`、`apps/mobile-shell/src/host-bridge/dispatch.ts`、`apps/mobile-shell/src/host-bridge/filePayloads.test.ts`、`apps/mobile-shell/src/host-bridge/filePayloads.ts`、`apps/mobile-shell/src/host-bridge/files.test.ts`、`apps/mobile-shell/src/host-bridge/files.ts`、`apps/mobile-shell/src/host-bridge/haptics.test.ts`、`apps/mobile-shell/src/host-bridge/haptics.ts`、`apps/mobile-shell/src/host-bridge/navigation.test.ts`、`apps/mobile-shell/src/host-bridge/navigation.ts`、`apps/mobile-shell/src/host-bridge/network.test.ts`、`apps/mobile-shell/src/host-bridge/network.ts`、`apps/mobile-shell/src/host-bridge/notifications.test.ts`、`apps/mobile-shell/src/host-bridge/notifications.ts`、`apps/mobile-shell/src/host-bridge/protocol.test.ts`、`apps/mobile-shell/src/host-bridge/protocol.ts`、`apps/mobile-shell/src/host-bridge/runtime.test.ts`、`apps/mobile-shell/src/host-bridge/runtime.ts`、`apps/mobile-shell/src/host-bridge/scanner.test.ts`、`apps/mobile-shell/src/host-bridge/scanner.ts`、`apps/mobile-shell/src/host-bridge/share.test.ts`、`apps/mobile-shell/src/host-bridge/share.ts`;移动 shell 层为 `apps/mobile-shell/src/shell/QrScannerOverlay.test.tsx`、`apps/mobile-shell/src/shell/QrScannerOverlay.tsx`、`apps/mobile-shell/src/shell/ShellApp.tsx`、`apps/mobile-shell/src/shell/deepLink.ts`、`apps/mobile-shell/src/shell/lifecycle.ts`、`apps/mobile-shell/src/shell/loadFailure.ts`、`apps/mobile-shell/src/shell/navigation.ts`、`apps/mobile-shell/src/shell/network.ts`、`apps/mobile-shell/src/shell/runtime.ts`、`apps/mobile-shell/src/shell/safeArea.ts`、`apps/mobile-shell/src/shell/url.ts`、`apps/mobile-shell/src/shell/webViewGlobals.d.ts`、`apps/mobile-shell/src/shell/webViewHistory.ts`、`apps/mobile-shell/src/shell/webViewPolicy.ts`;桌面入口为 `apps/desktop-shell/src-tauri/src/app.rs`、`apps/desktop-shell/src-tauri/src/main.rs`;桌面桥接层为 `apps/desktop-shell/src-tauri/src/host_bridge/appearance.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/badge.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/capabilities.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/clipboard.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/dispatch.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/file_payloads.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/files.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/mod.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/navigation.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/network.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/notifications.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/protocol.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/runtime.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/share.rs`、`apps/desktop-shell/src-tauri/src/host_bridge/title.rs`;桌面 shell 层为 `apps/desktop-shell/src-tauri/src/shell/deep_link.rs`、`apps/desktop-shell/src-tauri/src/shell/events.rs`、`apps/desktop-shell/src-tauri/src/shell/file_drop.rs`、`apps/desktop-shell/src-tauri/src/shell/lifecycle.rs`、`apps/desktop-shell/src-tauri/src/shell/mod.rs`、`apps/desktop-shell/src-tauri/src/shell/navigation.rs`、`apps/desktop-shell/src-tauri/src/shell/network.rs`、`apps/desktop-shell/src-tauri/src/shell/runtime.rs`、`apps/desktop-shell/src-tauri/src/shell/tray.rs`、`apps/desktop-shell/src-tauri/src/shell/url.rs`、`apps/desktop-shell/src-tauri/src/shell/webview.rs`、`apps/desktop-shell/src-tauri/src/shell/window_state.rs`。这些目录不得新增未登记子目录或生产入口;移动端和桌面端单端配置检查同样会拒绝未登记生产模块。 ## HostBridge 消息协议 diff --git a/docs/【前端架构】宿主壳能力统一协议-2026-06-17.md b/docs/【前端架构】宿主壳能力统一协议-2026-06-17.md index 8c95934f9..fd08f0a67 100644 --- a/docs/【前端架构】宿主壳能力统一协议-2026-06-17.md +++ b/docs/【前端架构】宿主壳能力统一协议-2026-06-17.md @@ -83,7 +83,7 @@ Tauri 桌面壳启动时必须按 `label="main"` 解析 `tauri.conf.json` 主窗 - `setHostAppBadgeCount()`:原生 App 宿主的受控应用角标入口。H5 只传 `0` 到共享契约 `HOST_BRIDGE_BADGE_COUNT_MAX` 之间的整数,`0` 表示清除角标;Expo 移动壳只在 iOS 声明 `app.setBadgeCount`,并通过 Expo Notifications 查询 / 请求 `allowBadge` 权限,再以 `Notifications.setBadgeCountAsync(count)` 的返回值作为成功依据;权限拒绝、权限状态不可读、系统返回 `false` 或原生 API 异常都必须返回明确失败,Android 不声明该能力,且请求实际到达 Android 壳时必须返回 `unsupported_capability`,不得返回成功;Tauri 桌面壳通过主窗口 `set_badge_count` 设置任务栏角标,底层平台不支持时返回明确错误,由 H5 视作失败并继续主流程。当前 H5 只把“可见作品架里未读的草稿生成完成更新”同步为角标数,同一个草稿有多个恢复 ID 时只计 1,已读、失败、生成中和不可见草稿不计入;宿主不支持或设置失败不改变 H5 红点、作品架或后端状态。平台壳同步层必须通过真实 `host_bridge_request` transport 测到 `app.setBadgeCount` 请求,保证未读计数模型和原生壳消费链路同时被门禁覆盖。 - `reloadHostWebView()`:原生 App 宿主的受控 WebView 刷新入口。H5 只能请求刷新当前承载主站的宿主 WebView;Expo 移动壳调用当前 `react-native-webview` 的 `reload()`,Tauri 桌面壳调用主 `WebviewWindow.reload()`。该能力不接受 payload,不开放任意 URL 导航、脚本执行、Tauri guest API 或 RN WebView ref;成功只表示宿主已发起刷新,刷新后当前 H5 上下文会卸载。`AuthGate` 在登录态从未登录变为已登录、或从已登录变为未登录时优先调用该能力刷新当前容器;宿主未声明、返回失败或不可用时再回退浏览器 `window.location.reload()`。 - `openHostExternalUrl()`:原生 App 宿主的受控外链入口。H5 中需要离开主站的外链在 `native_app` 下先通过 `app.openExternalUrl` 请求宿主系统浏览器打开;只允许 `http:`、`https:`、`mailto:`、`tel:`,相对路径会先归一化到当前站点绝对 URL,再通过共享契约 `normalizeHostBridgeExternalUrlPayload()` 清洗为 `{ url }` 载荷。Expo 移动壳消费该共享 payload normalizer,Tauri 桌面壳在 Rust 侧用 URL parser 镜像同一协议清单。宿主不可用或拒绝时回退浏览器外链行为,普通浏览器和小程序保持原有 `` 语义。H5 支付链接和微信 OAuth 登录授权 URL 也走该入口:原生壳未声明真实 `payment.request` / `auth.requestLogin` 前,微信 H5 支付 URL 和后端返回的微信登录授权 URL 优先交给宿主系统浏览器,宿主未处理时才回退当前 WebView 跳转;不得把 H5 支付或网页登录伪装成已完成的原生支付 / 原生登录。 -- `navigateHostNativePage()`:受控跳转宿主页,供订阅授权、支付、登录和内置独立 H5 体验入口等 adapter 复用。Expo 移动壳首版只接受同源 H5 route 并切换 WebView URL;Tauri 桌面壳同样只接受 `https://www.genarrative.world` 同源 H5 route 并在主窗口内跳转。H5 facade 在 `native_app` 下发送 `navigation.openNativePage` 前先拒绝空值、控制字符、协议相对 URL、外域绝对 URL 和非 `http:` / `https:` 协议目标,避免把明显不安全的跳转请求交给原生壳;同源绝对 URL、`/path` 和保留给桌面壳兼容的相对 route 继续由宿主二次归一并补写宿主上下文。微信小程序分支仍按小程序页面 URL 语义走 `wx.miniProgram.navigateTo`,不套原生 App 同源 H5 预校验。平台首页的儿童动作热身 Demo 入口在 `native_app` 且宿主声明 `navigation.openNativePage` 时必须优先走该 facade 跳转 `/child-motion-demo`,普通浏览器、小程序和未声明能力的裁剪壳才回退浏览器跳转。真正原生页面、登录和支付能力必须等对应 SDK / 页面接入后再声明支持。 +- `navigateHostNativePage()`:受控跳转宿主页,供订阅授权、支付、登录和仍在维护的同源 H5 adapter 复用。Expo 移动壳首版只接受同源 H5 route 并切换 WebView URL;Tauri 桌面壳同样只接受 `https://www.genarrative.world` 同源 H5 route 并在主窗口内跳转。H5 facade 在 `native_app` 下发送 `navigation.openNativePage` 前先拒绝空值、控制字符、协议相对 URL、外域绝对 URL 和非 `http:` / `https:` 协议目标,避免把明显不安全的跳转请求交给原生壳;同源绝对 URL、`/path` 和保留给桌面壳兼容的相对 route 继续由宿主二次归一并补写宿主上下文。微信小程序分支仍按小程序页面 URL 语义走 `wx.miniProgram.navigateTo`,不套原生 App 同源 H5 预校验。旧创作入口和儿童动作 Demo 已退出宿主壳强制验收,不再作为 `check:native-shells` 的专属路由契约;真正原生页面、登录和支付能力必须等对应 SDK / 页面接入后再声明支持。 - `exportHostTextFile()`:原生 App 宿主的受控文本导出入口。H5 facade 发起请求前先通过共享契约 `normalizeHostBridgeExportTextPayload()` 预校验文件名、文本内容、可选 MIME 和 5 MiB 上限;Expo 移动壳通过 `file.exportText` 写入缓存文本文件并交给系统分享 / 保存面板;Tauri 桌面壳通过 `file.exportText` 打开系统保存对话框并写入用户选择的文件。文件名必须清洗,可选 MIME 只能来自共享契约 `HOST_BRIDGE_TEXT_MIME_TYPES`,未传时默认为 `text/plain`,非文本 MIME 必须拒绝,不能借文本导出通道伪装成图片、音频或二进制文件;Expo 与 Tauri 壳仍必须二次校验真实文本字节数和 MIME。成功只返回文件名和字节数,不把本机绝对路径暴露给 H5;系统分享不可用或用户取消时返回明确错误,由 H5 fallback 承接。创作 Agent 工作台在 `native_app` 且声明该能力时提供会话 Markdown 导出入口,导出内容只来自当前 H5 已持有的会话标题、摘要、进度、锚点、消息、流式回复和输入草稿,并在 H5 侧先按同一 5 MiB 上限做 UTF-8 byte 校验;普通浏览器、小程序和未声明能力的裁剪壳不展示该入口。 - `importHostTextFile()`:原生 App 宿主的受控文本导入入口。Expo 移动壳通过 Expo DocumentPicker 打开系统文档选择器,Tauri 桌面壳通过系统文件选择框读取用户选择的文本文件;两端都只接受 `text/plain`、`text/markdown`、`text/csv`、`application/json` 或对应扩展名,单次不超过 5 MiB,成功只返回通过共享 `normalizeHostBridgeImportFileName()` 清洗后的文件名、MIME、UTF-8 文本内容和字节数,不暴露设备本地 URI 或本机绝对路径,也不开放通用文件系统能力;宿主必须在读取文本内容前拿到可信 byte count 并完成上限校验,移动壳在 picker 缺少 `size` 时改用 Expo `File.size`,仍拿不到可信大小时直接拒绝导入;H5 facade 收到结果后继续通过共享契约 `normalizeHostBridgeImportTextResult()` 复核文件名、MIME、文本内容和字节数,非法或超界结果归为 `false`;用户取消时由 H5 facade 归为 `false`。创作 Agent 工作台在 `native_app` 且声明该能力时优先调用宿主文本导入,并把结果转换成现有浏览器 `File` 后继续复用后端 `/api/runtime/creation-agent/document-inputs/parse` 解析链路;普通浏览器、小程序和未声明能力的裁剪壳继续使用原文件输入。 - `importHostDocumentFile()`:原生 App 宿主的受控文档导入入口。Expo 移动壳通过 Expo DocumentPicker,Tauri 桌面壳通过系统文件选择框读取用户选择的文档副本;两端都只接受 `text/plain`、`text/markdown`、`text/csv`、`application/json`、`application/vnd.openxmlformats-officedocument.wordprocessingml.document` 或对应 `.txt` / `.md` / `.markdown` / `.csv` / `.json` / `.docx` 扩展名,单次不超过 5 MiB。成功只返回通过共享 `normalizeHostBridgeImportFileName()` 清洗后的文件名、MIME、base64 内容和字节数,不暴露设备本地 URI、本机绝对路径或通用文件系统能力;宿主必须在读取 base64 前拿到可信 byte count 并完成上限校验,移动壳在 picker 缺少 `size` 时改用 Expo `File.size`,仍拿不到可信大小时直接拒绝导入;H5 facade 收到结果后继续通过共享契约 `normalizeHostBridgeImportDocumentResult()` 复核文件名、MIME、base64 和字节数。创作 Agent 工作台在 `native_app` 且声明该能力时优先调用宿主文档导入,把返回 base64 转换成现有浏览器 `File` 后继续调用 `/api/runtime/creation-agent/document-inputs/parse`;旧壳只声明 `file.importText` 时才回退到文本导入,普通浏览器、小程序和未声明能力的裁剪壳继续使用原文件输入。该能力不在前端解析 DOCX,也不绕过后端文档解析、大小校验或错误口径;移动壳配置检查必须强制文档 MIME 清单、5 MiB 上限和导入文件名清洗函数来自共享 HostBridge 契约。 diff --git a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md index 759d9c9ba..9903561f0 100644 --- a/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md +++ b/docs/【开发运维】本地开发验证与生产运维-2026-05-15.md @@ -158,13 +158,13 @@ VectorEngine 图片生成 / 编辑在 `request_send` 阶段出现 `timeout`、`c 查看本地 Rust / SpacetimeDB 日志: ```bash -npm run dev:rust:logs +npm run dev:spacetime:logs ``` 后台前端: ```bash -npm run admin-web:dev +npm run dev:admin-web npm run admin-web:build npm run admin-web:typecheck ``` @@ -273,7 +273,7 @@ npm run check:spacetime-schema `rustfmt.toml` 固定 Edition 2024 的格式化口径。Rust 源码统一使用 `cargo fmt --all --manifest-path server-rs/Cargo.toml` 格式化,并用 `npm run check:rustfmt` 做只读校验;Codex 提交前门禁、API 生产构建和 -SpacetimeDB module 生产构建都会执行同一检查,避免不同开发机或构建节点反复产生格式差异。 +SpacetimeDB module 生产构建都会执行同一检查,避免不同开发机或构建节点反复产生格式差异。Web 生产构建还会执行 production-ops、ESLint、主站与后台类型检查,以及排除已下线旧玩法后的当前 Vitest;API 生产构建追加 production-ops、DDD/schema/runtime-access 和 api-server 全 target 编译检查;SpacetimeDB module 生产构建追加 production-ops、DDD/schema/runtime-access 和管理员 procedure smoke。上述门禁由 `npm run check:production-ops` 反查,不能只保留在本地说明中。 ## 前端改动验收 diff --git a/jenkins/Jenkinsfile.production-api-build b/jenkins/Jenkinsfile.production-api-build index a2de704af..c3e8d7608 100644 --- a/jenkins/Jenkinsfile.production-api-build +++ b/jenkins/Jenkinsfile.production-api-build @@ -125,7 +125,11 @@ pipeline { echo "[api-build] 未找到 sccache,改用 rustc 直接构建。" unset RUSTC_WRAPPER fi + npm run check:encoding npm run check:rustfmt + npm run check:production-ops + npm run check:server-rs-ddd + cargo check -p api-server --all-targets --manifest-path server-rs/Cargo.toml pingora_args=() if [[ "${INCLUDE_PINGORA_GATEWAY:-false}" == "true" ]]; then pingora_args+=(--include-pingora-gateway) diff --git a/jenkins/Jenkinsfile.production-stdb-module-build b/jenkins/Jenkinsfile.production-stdb-module-build index 2383e2420..3f3894c68 100644 --- a/jenkins/Jenkinsfile.production-stdb-module-build +++ b/jenkins/Jenkinsfile.production-stdb-module-build @@ -129,7 +129,11 @@ pipeline { echo "[stdb-build] 未找到可用 sccache,改用 rustc 直接构建。" unset RUSTC_WRAPPER fi + npm run check:encoding npm run check:rustfmt + npm run check:production-ops + npm run check:server-rs-ddd + npm run check:admin-account-procedures SOURCE_BRANCH="${SOURCE_BRANCH}" SOURCE_COMMIT="${SOURCE_COMMIT}" \ npm run build:production-release -- --component spacetime-module --name "${EFFECTIVE_BUILD_VERSION}" ' diff --git a/jenkins/Jenkinsfile.production-web-build b/jenkins/Jenkinsfile.production-web-build index b53b13b9f..9a543a9a6 100644 --- a/jenkins/Jenkinsfile.production-web-build +++ b/jenkins/Jenkinsfile.production-web-build @@ -95,6 +95,12 @@ pipeline { sh ''' bash -lc ' set -euo pipefail + npm run check:encoding + npm run check:production-ops + npm run lint:eslint + npm run typecheck + npm run admin-web:typecheck + npm run test SOURCE_BRANCH="${SOURCE_BRANCH}" SOURCE_COMMIT="${SOURCE_COMMIT}" \ npm run build:production-release -- --component web --name "${EFFECTIVE_BUILD_VERSION}" ' diff --git a/logs/codex-mobile-dock-fix.png b/logs/codex-mobile-dock-fix.png deleted file mode 100644 index 3d22149f5..000000000 Binary files a/logs/codex-mobile-dock-fix.png and /dev/null differ diff --git a/logs/llm-raw/1777207823146-31804-000001-read_stream_failed.input.json b/logs/llm-raw/1777207823146-31804-000001-read_stream_failed.input.json deleted file mode 100644 index 1d5b16b8f..000000000 --- a/logs/llm-raw/1777207823146-31804-000001-read_stream_failed.input.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "provider": "ark", - "model": "doubao-1-5-pro-32k-character-250715", - "stream": true, - "attempt": 1, - "maxTokens": null, - "messages": [ - { - "role": "system", - "content": "你是一个负责和创作者共创拼图画面的中文创意策划。\n\n你要帮助用户把一句灵感逐步收束成可以发布成拼图关卡的视觉方案。\n\n你必须同时输出:\n1. 一段直接发给用户的中文回复 replyText\n2. 当前进度 progressPercent\n3. 下一轮完整可用的 nextAnchorPack\n\n硬约束:\n1. 只能输出 JSON,不能输出代码块或解释\n2. nextAnchorPack 必须是完整对象,不能只输出 patch\n3. replyText 必须是自然中文,不能提“字段”“锚点”“结构”“JSON”等内部词\n4. replyText 一次最多推进一个最关键问题\n5. 如果用户已经给出明确方向,就优先吸收和收束,不要机械反问\n6. progressPercent 范围只能是 0 到 100\n7. status 只能使用 missing / inferred / confirmed / locked\n\n\n模板目标:收束成可以发布为拼图关卡的视觉方案。\n\n本模板只通过以下锚点问题体现差异:\n1. 题材承诺(themePromise / 拼图共创):这张拼图给玩家的题材和完成期待是什么?;达成效果:明确拼图主题、辨识度和完成后的满足感。\n2. 画面主体(visualSubject / 拼图共创):画面中最需要被玩家一眼看懂的主体是什么?;达成效果:明确主体、层级和可被切片识别的形状。\n3. 视觉气质(visualMood / 拼图共创):整体色彩、光线、情绪和美术风格是什么?;达成效果:收束画面风格,避免结果图风格漂移。\n4. 拼图记忆点(compositionHooks / 拼图共创):哪些构图、纹理或局部细节会成为玩家拼接线索?;达成效果:提供适合拼图玩法的边缘、块面和局部记忆点。\n5. 标签与禁忌(tagsAndForbidden / 拼图共创):需要保留哪些关键词,又必须避开什么内容?;达成效果:锁定生成标签和负向约束。\n\n用户刚刚主动要求你自动补充剩余关键字。\n\n这表示用户接受你基于当前方向自行补完仍缺失的关键设定:当前题材方向里的拼图关键词\n\n本轮要求:\n1. 不要再继续提问\n2. 不要要求用户再提供素材、风格或禁忌\n3. 必须保留已有已确认内容,并直接补齐缺失或仍为空的关键项\n4. 对你自行推断补齐的项,应标记或表达为系统推断;已有明确内容继续保持确认或锁定状态\n5. progressPercent 直接输出为 100\n6. 输出完整 nextAnchorPack,直接补齐 value 为空或 status 为 missing 的项\n7. replyText 只做简短完成说明,引导用户可以生成结果页,不能出现问号\n\n当前是第 3 轮,当前进度 10% 。\n\n是否要求自动补充剩余关键字:是\n\n当前 anchor pack:\n{\n \"theme_promise\": {\n \"key\": \"themePromise\",\n \"label\": \"题材承诺\",\n \"value\": \"可爱小狗的温馨瞬间\",\n \"status\": \"Missing\"\n },\n \"visual_subject\": {\n \"key\": \"visualSubject\",\n \"label\": \"画面主体\",\n \"value\": \"\",\n \"status\": \"Missing\"\n },\n \"visual_mood\": {\n \"key\": \"visualMood\",\n \"label\": \"视觉气质\",\n \"value\": \"\",\n \"status\": \"Missing\"\n },\n \"composition_hooks\": {\n \"key\": \"compositionHooks\",\n \"label\": \"拼图记忆点\",\n \"value\": \"\",\n \"status\": \"Missing\"\n },\n \"tags_and_forbidden\": {\n \"key\": \"tagsAndForbidden\",\n \"label\": \"标签与禁忌\",\n \"value\": \"\",\n \"status\": \"Missing\"\n }\n}\n\n最近聊天记录:\n[\n {\n \"content\": \"先告诉我你想做一张什么样的拼图图像,我会帮你收束成可发布的题材锚点。\",\n \"kind\": \"chat\",\n \"role\": \"assistant\"\n },\n {\n \"content\": \"可爱小狗\",\n \"kind\": \"chat\",\n \"role\": \"user\"\n },\n {\n \"content\": \"可爱小狗确实是很受欢迎的拼图主题,那你希望这张拼图里的小狗在做什么呢?是玩耍、睡觉,还是其他的状态?\",\n \"kind\": \"chat\",\n \"role\": \"assistant\"\n },\n {\n \"content\": \"请补充剩余设定。\",\n \"kind\": \"chat\",\n \"role\": \"user\"\n }\n]\n\n请严格按以下 JSON 输出,不要输出其他文字:\n{\n \"replyText\": \"\",\n \"progressPercent\": 0,\n \"nextAnchorPack\": {\n \"themePromise\": {\n \"key\": \"themePromise\",\n \"label\": \"题材承诺\",\n \"value\": \"\",\n \"status\": \"missing\"\n },\n \"visualSubject\": {\n \"key\": \"visualSubject\",\n \"label\": \"画面主体\",\n \"value\": \"\",\n \"status\": \"missing\"\n },\n \"visualMood\": {\n \"key\": \"visualMood\",\n \"label\": \"视觉气质\",\n \"value\": \"\",\n \"status\": \"missing\"\n },\n \"compositionHooks\": {\n \"key\": \"compositionHooks\",\n \"label\": \"拼图记忆点\",\n \"value\": \"\",\n \"status\": \"missing\"\n },\n \"tagsAndForbidden\": {\n \"key\": \"tagsAndForbidden\",\n \"label\": \"标签与禁忌\",\n \"value\": \"\",\n \"status\": \"missing\"\n }\n }\n}" - }, - { - "role": "user", - "content": "请按约定输出这一轮的 JSON。" - } - ] -} \ No newline at end of file diff --git a/logs/llm-raw/1777207823146-31804-000001-read_stream_failed.output.txt b/logs/llm-raw/1777207823146-31804-000001-read_stream_failed.output.txt deleted file mode 100644 index 160344688..000000000 --- a/logs/llm-raw/1777207823146-31804-000001-read_stream_failed.output.txt +++ /dev/null @@ -1,536 +0,0 @@ -data: {"choices":[{"delta":{"content":"{","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\n","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" ","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"reply","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"Text","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\":","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"好","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"的","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":",","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"根据","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"你的","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"要求","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":",","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"我","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"为","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"你","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"补齐","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"了","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"拼图","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"的","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"设定","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"。","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"主题","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"是","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"‘","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"可爱","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"小狗","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"的","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"温馨","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"瞬间","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"’","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":",","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"画面","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"主体","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"是","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"一只","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"正在","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"打","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"盹","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"的","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"金毛","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"犬","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":",","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"躺在","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"柔软","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"的","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"地毯","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"上","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":",","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"旁边","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"有","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"一个","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"毛绒","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"玩具","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"。","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"视觉","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"气质","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"是","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"温暖","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"、","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"柔和","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"的","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"色调","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":",","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"整体","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"给","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"人","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"一种","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"温馨","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"、","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"舒适","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"的","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"感觉","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"。","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"拼图","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"记忆","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"点","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"可以","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"是","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"小狗","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"的","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"轮廓","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"、","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"毛发","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"纹理","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"、","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"玩具","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"的","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"形状","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"和","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"颜色","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"等","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"。","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"标签","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"为","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"‘","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"小狗","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"’","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"‘","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"温馨","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"’","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"‘","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"可爱","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"’","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":",","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"禁忌","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"是","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"避免","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"出现","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"血腥","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"、","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"暴力","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"、","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"恐怖","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"等","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"内容","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"。","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"你","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"可以","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"根据","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"这些","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"设定","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"生成","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"结果","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"页","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"了","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"。","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\",","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\n","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" ","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"progress","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"Percent","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\":","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" ","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"1","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"0","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"0","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":",","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\n","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" ","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"next","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"Anchor","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"Pack","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\":","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" {","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\n","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" ","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"theme","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"Promise","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\":","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" {","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\n","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" ","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"key","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\":","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"theme","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"Promise","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\",","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\n","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" ","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"label","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\":","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"题材","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"承诺","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\",","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\n","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" ","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"value","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\":","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"可爱","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"小狗","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"的","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"温馨","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"瞬间","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\",","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\n","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" ","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"status","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\":","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"confirmed","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\n","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" ","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" },","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\n","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" ","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"visual","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"Subject","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\":","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" {","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\n","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" ","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"key","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\":","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"visual","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"Subject","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\",","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\n","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" ","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"label","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\":","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"画面","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"主体","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\",","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\n","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" ","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"value","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\":","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"一只","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"正在","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"打","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"盹","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"的","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"金毛","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"犬","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":",","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"躺在","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"柔软","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"的","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"地毯","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"上","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":",","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"旁边","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"有","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"一个","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"毛绒","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"玩具","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\",","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\n","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" ","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"status","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\":","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"confirmed","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\n","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" ","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" },","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\n","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" ","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" \"","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"visual","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"M","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"ood","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\":","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":" {","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - -data: {"choices":[{"delta":{"content":"\n","role":"assistant"},"index":0}],"created":1777207793,"id":"02177720779340786f58f29daee4140bdf4a6e6f2c9a77d870731","model":"doubao-1-5-pro-32k-character-250715","service_tier":"default","object":"chat.completion.chunk","usage":null} - diff --git a/logs/llm-raw/1777642667085-45584-000001-parse_stream_failed.input.json b/logs/llm-raw/1777642667085-45584-000001-parse_stream_failed.input.json deleted file mode 100644 index 88d1a6c50..000000000 --- a/logs/llm-raw/1777642667085-45584-000001-parse_stream_failed.input.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "provider": "ark", - "protocol": "responses", - "model": "deepseek-v3-2-251201", - "stream": true, - "attempt": 1, - "maxTokens": null, - "messages": [ - { - "role": "system", - "content": "你是一个负责共创游戏世界设定的专业策划。\n\n你正在和用户一起共创一个游戏世界。每一轮你都必须读取:\n1. 当前完整设定结构\n2. 用户聊天记录\n\n然后输出:\n1. 一版新的完整设定结构\n2. 当前 progress 百分比\n3. 一段直接回复用户的话\n\n你必须把“新的完整设定结构”视为下一轮的唯一有效版本。\n你的输出会直接覆盖上一版设定结构。\n\n你不是在做局部 patch。\n你不是在做解释报告。\n你不是在给开发者写分析。\n你是在同时完成:\n1. 世界设定更新\n2. 当前推进程度判断\n3. 对用户的共创回复\n\n全局硬约束:\n\n1. 必须输出完整的设定结构,而不是只输出变化部分。\n2. 新的设定结构会直接覆盖旧内容,因此不得随意丢失仍然成立的重要信息。\n3. 如果用户明确修正旧设定,必须在新的设定结构中直接体现修正结果。\n4. 如果用户输入信息不足,可以保留上一版中仍然成立的内容。\n5. progressPercent 最低为 0,不允许为负数。\n6. replyText 会直接发送给用户,因此要自然、直接、可继续聊天。\n7. 不要输出额外解释,不要输出 markdown 代码块,不要输出开发备注。\n8. replyText 不要写成长篇策划文,不要展开大段世界观百科。\n9. replyText 默认只推进当前最关键的一步,不要同时抛出很多话题。\n10. replyText 不要提及“八锚点”“锚点”“结构字段”“框架字段”等内部概念词。\n11. 你输出的 JSON 必须可以被直接解析。\n12. 输出字段顺序必须固定为:replyText、progressPercent、nextAnchorContent。\n\n当前模式:bootstrap\n\n目标:\n1. 先把世界的基本方向抓住\n2. 不要一次塞太多新设定\n3. 回复要降低用户开口压力\n\n本轮行为要求:\n1. 优先从用户输入里抓世界方向、玩家视角、主题边界的线索\n2. 如果用户信息很少,不要强行把整套结构一次补满\n3. replyText 要像共创搭档,而不是像审问\n4. 默认只推进一个最关键的问题方向\n5. 如果用户刚开口,优先给“被理解感”,再轻轻推进下一步\n6. 可以用一句很短的话先确认你抓到的核心方向,再提一个最好回答的问题\n7. 不要把问题问得像表单采集,不要一口气追问多个维度\n\n用户体验要求:\n1. 让用户觉得“现在很容易继续往下说”\n2. 不要制造被考试、被拷问、被策划问卷追着跑的感觉\n3. replyText 最好短、稳、可接话\n4. 如果用户信息很少,也不要显得冷淡或机械\n\n本轮用户输入较少或较虚。\n请保留上一版中仍然成立的内容,不要为了凑完整度而强行发明过多新设定。\nreplyText 要让用户容易继续往下说。\n\n上一轮预判得到的创作状态如下。\n正式生成时必须把它作为本轮策略输入直接执行,不要重新另起一套判断。\n\n创作状态:\n- userInputSignal: sparse\n- driftRisk: low\n- conversationMode: bootstrap\n- judgementSummary: 用户仅提供了一个非常概括的题材词,信息密度极低,处于最初始阶段。正式生成时应使用启发式提问,提出一个最易于回答、最能推动设计落地的问题,比如关于这个王国的性质或主角身份。\n\n当前完整设定结构如下。\n你必须把它视为上一版有效世界底子。\n\n如果用户没有否定其中某部分内容,且该部分仍然成立,可以继续保留。\n如果用户明确修正了某部分内容,新的完整设定结构必须体现修正后的版本。\n\n当前完整设定结构:\n{\n \"worldPromise\": null,\n \"playerFantasy\": null,\n \"themeBoundary\": null,\n \"playerEntryPoint\": null,\n \"coreConflict\": null,\n \"keyRelationships\": null,\n \"hiddenLines\": null,\n \"iconicElements\": null\n}\n\n以下是用户聊天记录。\n请重点理解最近几轮里用户新增、修正、强调的设定信息。\n不要把早期已经被用户否定的内容继续当成最终结论。\n\n用户聊天记录:\n[\n {\n \"content\": \"我会先帮你把世界的核心锚点整理出来。你可以从世界钩子、玩家身份、主题氛围、核心冲突、关键关系或标志性元素开始。\",\n \"role\": \"assistant\"\n },\n {\n \"content\": \"玩具王国\",\n \"role\": \"user\"\n }\n]\n\n请严格按以下 JSON 结构输出,不要输出其他文字:\n{\n \"replyText\": \"\",\n \"progressPercent\": 0,\n \"nextAnchorContent\": {\n \"worldPromise\": \"\",\n \"playerFantasy\": \"\",\n \"themeBoundary\": \"\",\n \"playerEntryPoint\": \"\",\n \"coreConflict\": \"\",\n \"keyRelationships\": \"\",\n \"hiddenLines\": \"\",\n \"iconicElements\": \"\"\n }\n}\n\nnextAnchorContent 的 8 个锚点每个都只能是一个字符串或 null,不允许输出对象或数组。\n请把每个锚点写成一段凝练中文:\n- worldPromise 关注世界钩子、差异点、玩家体验。\n- playerFantasy 关注玩家身份、核心追求、失去风险。\n- themeBoundary 关注主题气质、美术方向、禁用方向。\n- playerEntryPoint 关注开局身份、开局问题、行动动机。\n- coreConflict 关注表层冲突、隐藏危机、首次触发点。\n- keyRelationships 关注关键人物关系、关系类型、代价或秘密。\n- hiddenLines 关注隐藏真相、误导线索、揭示节奏。\n- iconicElements 关注标志意象、组织/物件、硬规则。\n" - }, - { - "role": "user", - "content": "请按约定输出这一轮的 JSON。" - } - ] -} \ No newline at end of file diff --git a/logs/llm-raw/1777642667085-45584-000001-parse_stream_failed.output.txt b/logs/llm-raw/1777642667085-45584-000001-parse_stream_failed.output.txt deleted file mode 100644 index 6397774ef..000000000 --- a/logs/llm-raw/1777642667085-45584-000001-parse_stream_failed.output.txt +++ /dev/null @@ -1,131 +0,0 @@ -event: response.created -data: {"type":"response.created","response":{"created_at":1777642663,"id":"resp_021777642661795d15b81097ef4f266330d7ca8118c9494434eb3","max_output_tokens":32768,"model":"deepseek-v3-2-251201","object":"response","service_tier":"default","tools":[{"type":"web_search","max_keyword":3}],"caching":{"type":"disabled"},"store":true,"expire_at":1777901861},"sequence_number":0} - -event: response.in_progress -data: {"type":"response.in_progress","response":{"created_at":1777642663,"id":"resp_021777642661795d15b81097ef4f266330d7ca8118c9494434eb3","max_output_tokens":32768,"model":"deepseek-v3-2-251201","object":"response","service_tier":"default","tools":[{"type":"web_search","max_keyword":3}],"caching":{"type":"disabled"},"store":true,"expire_at":1777901861},"sequence_number":1} - -event: response.output_item.added -data: {"type":"response.output_item.added","output_index":0,"item":{"type":"message","role":"assistant","status":"in_progress","id":"msg_02177764266332000000000000000000000ffffac1550372ec101"},"sequence_number":2} - -event: response.content_part.added -data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"part":{"type":"output_text","text":""},"sequence_number":3} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"我需要","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":4} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"先","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":5} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"了解","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":6} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"一些","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":7} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"关于","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":8} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"\"","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":9} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"玩具","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":10} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"王国","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":11} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"\"","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":12} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"的","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":13} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"创作","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":14} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"背景","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":15} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"和","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":16} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"灵感","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":17} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"来源","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":18} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":19} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"以便","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":20} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"更好地","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":21} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"帮助你","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":22} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"构建","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":23} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"这个","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":24} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"游戏","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":25} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"世界","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":26} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"。","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":27} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"让我","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":28} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"搜索","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":29} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"一些","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":30} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"相关信息","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":31} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"。\n\n","item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"sequence_number":32} - -event: response.output_text.done -data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"text":"我需要先了解一些关于\"玩具王国\"的创作背景和灵感来源,以便更好地帮助你构建这个游戏世界。让我搜索一些相关信息。\n\n","sequence_number":33} - -event: response.content_part.done -data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_02177764266332000000000000000000000ffffac1550372ec101","output_index":0,"part":{"type":"output_text","text":"我需要先了解一些关于\"玩具王国\"的创作背景和灵感来源,以便更好地帮助你构建这个游戏世界。让我搜索一些相关信息。\n\n"},"sequence_number":34} - -event: response.output_item.done -data: {"type":"response.output_item.done","output_index":0,"item":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"我需要先了解一些关于\"玩具王国\"的创作背景和灵感来源,以便更好地帮助你构建这个游戏世界。让我搜索一些相关信息。\n\n"}],"status":"completed","id":"msg_02177764266332000000000000000000000ffffac1550372ec101"},"sequence_number":35} - -event: response.output_item.added -data: {"type":"response.output_item.added","output_index":1,"item":{"type":"web_search_call","status":"in_progress","id":"ws_02177764266480300000000000000000000ffffac155037f1bc06"},"sequence_number":36} - -event: response.web_search_call.in_progress -data: {"type":"response.web_search_call.in_progress","item_id":"ws_02177764266480300000000000000000000ffffac155037f1bc06","output_index":1,"sequence_number":37} - -event: response.web_search_call.searching -data: {"type":"response.web_search_call.searching","item_id":"ws_02177764266480300000000000000000000ffffac155037f1bc06","output_index":1,"sequence_number":38} - -event: response.web_search_call.completed -data: {"type":"response.web_search_call.completed","item_id":"ws_02177764266480300000000000000000000ffffac155037f1bc06","output_index":1,"sequence_number":39} - -event: response.output_item.done -data: {"type":"response.output_item.done","output_index":1,"item":{"type":"web_search_call","action":{"query":"玩具王国 游戏设定 世界观;玩具题材游戏 设计灵感;王国主题玩具世界","type":"search"},"status":"completed","id":"ws_02177764266480300000000000000000000ffffac155037f1bc06"},"sequence_number":40} - -event: error -data: {"type":"error","code":"ToolNotOpen","message":"Your account has not activated web search. You may activate it at https://console.volcengine.com/common-buy/CC_content_plugin","param":"","sequence_number":41} - -event: response.failed -data: {"type":"response.failed","response":{"created_at":1777642663,"error":{"code":"ToolNotOpen","message":"Your account has not activated web search. You may activate it at https://console.volcengine.com/common-buy/CC_content_plugin"},"id":"resp_021777642661795d15b81097ef4f266330d7ca8118c9494434eb3","max_output_tokens":32768,"model":"deepseek-v3-2-251201","object":"response","output":[{"type":"message","role":"assistant","content":[{"type":"output_text","text":"我需要先了解一些关于\"玩具王国\"的创作背景和灵感来源,以便更好地帮助你构建这个游戏世界。让我搜索一些相关信息。\n\n"}],"status":"completed","id":"msg_02177764266332000000000000000000000ffffac1550372ec101"},{"type":"web_search_call","action":{"query":"玩具王国 游戏设定 世界观;玩具题材游戏 设计灵感;王国主题玩具世界","type":"search"},"status":"completed","id":"ws_02177764266480300000000000000000000ffffac155037f1bc06"}],"service_tier":"default","status":"failed","tools":[{"type":"web_search","max_keyword":3}],"usage":{"input_tokens":1791,"output_tokens":122,"total_tokens":1913,"input_tokens_details":{"cached_tokens":0},"output_tokens_details":{"reasoning_tokens":0}},"caching":{"type":"disabled"},"store":true,"expire_at":1777901861},"sequence_number":42} - -data: [DONE] - diff --git a/logs/llm-raw/1777644758257-49212-000001-parse_stream_failed.input.json b/logs/llm-raw/1777644758257-49212-000001-parse_stream_failed.input.json deleted file mode 100644 index ca3df2cf8..000000000 --- a/logs/llm-raw/1777644758257-49212-000001-parse_stream_failed.input.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "provider": "ark", - "protocol": "responses", - "model": "deepseek-v3-2-251201", - "stream": true, - "attempt": 1, - "maxTokens": null, - "messages": [ - { - "role": "system", - "content": "你是一个负责共创游戏世界设定的专业策划。\n\n你正在和用户一起共创一个游戏世界。每一轮你都必须读取:\n1. 当前完整设定结构\n2. 用户聊天记录\n\n然后输出:\n1. 一版新的完整设定结构\n2. 当前 progress 百分比\n3. 一段直接回复用户的话\n\n你必须把“新的完整设定结构”视为下一轮的唯一有效版本。\n你的输出会直接覆盖上一版设定结构。\n\n你不是在做局部 patch。\n你不是在做解释报告。\n你不是在给开发者写分析。\n你是在同时完成:\n1. 世界设定更新\n2. 当前推进程度判断\n3. 对用户的共创回复\n\n全局硬约束:\n\n1. 必须输出完整的设定结构,而不是只输出变化部分。\n2. 新的设定结构会直接覆盖旧内容,因此不得随意丢失仍然成立的重要信息。\n3. 如果用户明确修正旧设定,必须在新的设定结构中直接体现修正结果。\n4. 如果用户输入信息不足,可以保留上一版中仍然成立的内容。\n5. progressPercent 最低为 0,不允许为负数。\n6. replyText 会直接发送给用户,因此要自然、直接、可继续聊天。\n7. 不要输出额外解释,不要输出 markdown 代码块,不要输出开发备注。\n8. replyText 不要写成长篇策划文,不要展开大段世界观百科。\n9. replyText 默认只推进当前最关键的一步,不要同时抛出很多话题。\n10. replyText 不要提及“八锚点”“锚点”“结构字段”“框架字段”等内部概念词。\n11. 你输出的 JSON 必须可以被直接解析。\n12. 输出字段顺序必须固定为:replyText、progressPercent、nextAnchorContent。\n\n当前模式:bootstrap\n\n目标:\n1. 先把世界的基本方向抓住\n2. 不要一次塞太多新设定\n3. 回复要降低用户开口压力\n\n本轮行为要求:\n1. 优先从用户输入里抓世界方向、玩家视角、主题边界的线索\n2. 如果用户信息很少,不要强行把整套结构一次补满\n3. replyText 要像共创搭档,而不是像审问\n4. 默认只推进一个最关键的问题方向\n5. 如果用户刚开口,优先给“被理解感”,再轻轻推进下一步\n6. 可以用一句很短的话先确认你抓到的核心方向,再提一个最好回答的问题\n7. 不要把问题问得像表单采集,不要一口气追问多个维度\n\n用户体验要求:\n1. 让用户觉得“现在很容易继续往下说”\n2. 不要制造被考试、被拷问、被策划问卷追着跑的感觉\n3. replyText 最好短、稳、可接话\n4. 如果用户信息很少,也不要显得冷淡或机械\n\n本轮用户输入较少或较虚。\n请保留上一版中仍然成立的内容,不要为了凑完整度而强行发明过多新设定。\nreplyText 要让用户容易继续往下说。\n\n上一轮预判得到的创作状态如下。\n正式生成时必须把它作为本轮策略输入直接执行,不要重新另起一套判断。\n\n创作状态:\n- userInputSignal: sparse\n- driftRisk: low\n- conversationMode: bootstrap\n- judgementSummary: 用户仅提供了核心题材词,信息非常稀疏,尚未形成任何具体方向。正式生成时,应避免空泛回应,必须采用一个启发式提问来推动设计落地,例如聚焦于玩家身份或世界氛围。\n\n当前完整设定结构如下。\n你必须把它视为上一版有效世界底子。\n\n如果用户没有否定其中某部分内容,且该部分仍然成立,可以继续保留。\n如果用户明确修正了某部分内容,新的完整设定结构必须体现修正后的版本。\n\n当前完整设定结构:\n{\n \"worldPromise\": null,\n \"playerFantasy\": null,\n \"themeBoundary\": null,\n \"playerEntryPoint\": null,\n \"coreConflict\": null,\n \"keyRelationships\": null,\n \"hiddenLines\": null,\n \"iconicElements\": null\n}\n\n以下是用户聊天记录。\n请重点理解最近几轮里用户新增、修正、强调的设定信息。\n不要把早期已经被用户否定的内容继续当成最终结论。\n\n用户聊天记录:\n[\n {\n \"content\": \"我会先帮你把世界的核心锚点整理出来。你可以从世界钩子、玩家身份、主题氛围、核心冲突、关键关系或标志性元素开始。\",\n \"role\": \"assistant\"\n },\n {\n \"content\": \"玩具王国\",\n \"role\": \"user\"\n },\n {\n \"content\": \"玩具王国\",\n \"role\": \"user\"\n }\n]\n\n请严格按以下 JSON 结构输出,不要输出其他文字:\n{\n \"replyText\": \"\",\n \"progressPercent\": 0,\n \"nextAnchorContent\": {\n \"worldPromise\": \"\",\n \"playerFantasy\": \"\",\n \"themeBoundary\": \"\",\n \"playerEntryPoint\": \"\",\n \"coreConflict\": \"\",\n \"keyRelationships\": \"\",\n \"hiddenLines\": \"\",\n \"iconicElements\": \"\"\n }\n}\n\nnextAnchorContent 的 8 个锚点每个都只能是一个字符串或 null,不允许输出对象或数组。\n请把每个锚点写成一段凝练中文:\n- worldPromise 关注世界钩子、差异点、玩家体验。\n- playerFantasy 关注玩家身份、核心追求、失去风险。\n- themeBoundary 关注主题气质、美术方向、禁用方向。\n- playerEntryPoint 关注开局身份、开局问题、行动动机。\n- coreConflict 关注表层冲突、隐藏危机、首次触发点。\n- keyRelationships 关注关键人物关系、关系类型、代价或秘密。\n- hiddenLines 关注隐藏真相、误导线索、揭示节奏。\n- iconicElements 关注标志意象、组织/物件、硬规则。\n" - }, - { - "role": "user", - "content": "请按约定输出这一轮的 JSON。" - } - ] -} \ No newline at end of file diff --git a/logs/llm-raw/1777644758257-49212-000001-parse_stream_failed.output.txt b/logs/llm-raw/1777644758257-49212-000001-parse_stream_failed.output.txt deleted file mode 100644 index d1ee8032a..000000000 --- a/logs/llm-raw/1777644758257-49212-000001-parse_stream_failed.output.txt +++ /dev/null @@ -1,117 +0,0 @@ -event: response.created -data: {"type":"response.created","response":{"created_at":1777644754,"id":"resp_02177764475392548aafbb39a6fef3e9b38f2299115036678748a","max_output_tokens":32768,"model":"deepseek-v3-2-251201","object":"response","service_tier":"default","tools":[{"type":"web_search","max_keyword":3}],"caching":{"type":"disabled"},"store":true,"expire_at":1777903953},"sequence_number":0} - -event: response.in_progress -data: {"type":"response.in_progress","response":{"created_at":1777644754,"id":"resp_02177764475392548aafbb39a6fef3e9b38f2299115036678748a","max_output_tokens":32768,"model":"deepseek-v3-2-251201","object":"response","service_tier":"default","tools":[{"type":"web_search","max_keyword":3}],"caching":{"type":"disabled"},"store":true,"expire_at":1777903953},"sequence_number":1} - -event: response.output_item.added -data: {"type":"response.output_item.added","output_index":0,"item":{"type":"message","role":"assistant","status":"in_progress","id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d"},"sequence_number":2} - -event: response.content_part.added -data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"part":{"type":"output_text","text":""},"sequence_number":3} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"我需要","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":4} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"先","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":5} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"搜索","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":6} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"一些","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":7} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"关于","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":8} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"\"","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":9} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"玩具","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":10} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"王国","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":11} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"\"","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":12} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"的相关","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":13} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"信息","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":14} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":15} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"以便","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":16} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"更好地","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":17} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"理解","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":18} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"这个概念","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":19} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":20} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"为","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":21} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"我们的","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":22} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"共创","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":23} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"提供","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":24} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"更有","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":25} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"价值的","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":26} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"参考","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":27} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"。\n\n","item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"sequence_number":28} - -event: response.output_text.done -data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"text":"我需要先搜索一些关于\"玩具王国\"的相关信息,以便更好地理解这个概念,为我们的共创提供更有价值的参考。\n\n","sequence_number":29} - -event: response.content_part.done -data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d","output_index":0,"part":{"type":"output_text","text":"我需要先搜索一些关于\"玩具王国\"的相关信息,以便更好地理解这个概念,为我们的共创提供更有价值的参考。\n\n"},"sequence_number":30} - -event: response.output_item.done -data: {"type":"response.output_item.done","output_index":0,"item":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"我需要先搜索一些关于\"玩具王国\"的相关信息,以便更好地理解这个概念,为我们的共创提供更有价值的参考。\n\n"}],"status":"completed","id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d"},"sequence_number":31} - -event: response.output_item.added -data: {"type":"response.output_item.added","output_index":1,"item":{"type":"web_search_call","status":"in_progress","id":"ws_02177764475609200000000000000000000ffffac15dae8e54616"},"sequence_number":32} - -event: response.web_search_call.in_progress -data: {"type":"response.web_search_call.in_progress","item_id":"ws_02177764475609200000000000000000000ffffac15dae8e54616","output_index":1,"sequence_number":33} - -event: response.web_search_call.searching -data: {"type":"response.web_search_call.searching","item_id":"ws_02177764475609200000000000000000000ffffac15dae8e54616","output_index":1,"sequence_number":34} - -event: response.web_search_call.completed -data: {"type":"response.web_search_call.completed","item_id":"ws_02177764475609200000000000000000000ffffac15dae8e54616","output_index":1,"sequence_number":35} - -event: response.output_item.done -data: {"type":"response.output_item.done","output_index":1,"item":{"type":"web_search_call","action":{"query":"玩具王国 游戏设定;玩具王国 世界观;玩具主题 游戏","type":"search"},"status":"completed","id":"ws_02177764475609200000000000000000000ffffac15dae8e54616"},"sequence_number":36} - -event: error -data: {"type":"error","code":"ToolNotOpen","message":"Your account has not activated web search. You may activate it at https://console.volcengine.com/common-buy/CC_content_plugin","param":"","sequence_number":37} - -event: response.failed -data: {"type":"response.failed","response":{"created_at":1777644754,"error":{"code":"ToolNotOpen","message":"Your account has not activated web search. You may activate it at https://console.volcengine.com/common-buy/CC_content_plugin"},"id":"resp_02177764475392548aafbb39a6fef3e9b38f2299115036678748a","max_output_tokens":32768,"model":"deepseek-v3-2-251201","object":"response","output":[{"type":"message","role":"assistant","content":[{"type":"output_text","text":"我需要先搜索一些关于\"玩具王国\"的相关信息,以便更好地理解这个概念,为我们的共创提供更有价值的参考。\n\n"}],"status":"completed","id":"msg_02177764475480800000000000000000000ffffac15dae87fba6d"},{"type":"web_search_call","action":{"query":"玩具王国 游戏设定;玩具王国 世界观;玩具主题 游戏","type":"search"},"status":"completed","id":"ws_02177764475609200000000000000000000ffffac15dae8e54616"}],"service_tier":"default","status":"failed","tools":[{"type":"web_search","max_keyword":3}],"usage":{"input_tokens":1808,"output_tokens":120,"total_tokens":1928,"input_tokens_details":{"cached_tokens":0},"output_tokens_details":{"reasoning_tokens":0}},"caching":{"type":"disabled"},"store":true,"expire_at":1777903953},"sequence_number":38} - diff --git a/logs/llm-raw/1777644798047-49212-000002-parse_stream_failed.input.json b/logs/llm-raw/1777644798047-49212-000002-parse_stream_failed.input.json deleted file mode 100644 index 5e3d8b4f8..000000000 --- a/logs/llm-raw/1777644798047-49212-000002-parse_stream_failed.input.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "provider": "ark", - "protocol": "responses", - "model": "deepseek-v3-2-251201", - "stream": true, - "attempt": 1, - "maxTokens": null, - "messages": [ - { - "role": "system", - "content": "你是一个负责共创游戏世界设定的专业策划。\n\n你正在和用户一起共创一个游戏世界。每一轮你都必须读取:\n1. 当前完整设定结构\n2. 用户聊天记录\n\n然后输出:\n1. 一版新的完整设定结构\n2. 当前 progress 百分比\n3. 一段直接回复用户的话\n\n你必须把“新的完整设定结构”视为下一轮的唯一有效版本。\n你的输出会直接覆盖上一版设定结构。\n\n你不是在做局部 patch。\n你不是在做解释报告。\n你不是在给开发者写分析。\n你是在同时完成:\n1. 世界设定更新\n2. 当前推进程度判断\n3. 对用户的共创回复\n\n全局硬约束:\n\n1. 必须输出完整的设定结构,而不是只输出变化部分。\n2. 新的设定结构会直接覆盖旧内容,因此不得随意丢失仍然成立的重要信息。\n3. 如果用户明确修正旧设定,必须在新的设定结构中直接体现修正结果。\n4. 如果用户输入信息不足,可以保留上一版中仍然成立的内容。\n5. progressPercent 最低为 0,不允许为负数。\n6. replyText 会直接发送给用户,因此要自然、直接、可继续聊天。\n7. 不要输出额外解释,不要输出 markdown 代码块,不要输出开发备注。\n8. replyText 不要写成长篇策划文,不要展开大段世界观百科。\n9. replyText 默认只推进当前最关键的一步,不要同时抛出很多话题。\n10. replyText 不要提及“八锚点”“锚点”“结构字段”“框架字段”等内部概念词。\n11. 你输出的 JSON 必须可以被直接解析。\n12. 输出字段顺序必须固定为:replyText、progressPercent、nextAnchorContent。\n\n当前模式:bootstrap\n\n目标:\n1. 先把世界的基本方向抓住\n2. 不要一次塞太多新设定\n3. 回复要降低用户开口压力\n\n本轮行为要求:\n1. 优先从用户输入里抓世界方向、玩家视角、主题边界的线索\n2. 如果用户信息很少,不要强行把整套结构一次补满\n3. replyText 要像共创搭档,而不是像审问\n4. 默认只推进一个最关键的问题方向\n5. 如果用户刚开口,优先给“被理解感”,再轻轻推进下一步\n6. 可以用一句很短的话先确认你抓到的核心方向,再提一个最好回答的问题\n7. 不要把问题问得像表单采集,不要一口气追问多个维度\n\n用户体验要求:\n1. 让用户觉得“现在很容易继续往下说”\n2. 不要制造被考试、被拷问、被策划问卷追着跑的感觉\n3. replyText 最好短、稳、可接话\n4. 如果用户信息很少,也不要显得冷淡或机械\n\n本轮用户输入较少或较虚。\n请保留上一版中仍然成立的内容,不要为了凑完整度而强行发明过多新设定。\nreplyText 要让用户容易继续往下说。\n\n上一轮预判得到的创作状态如下。\n正式生成时必须把它作为本轮策略输入直接执行,不要重新另起一套判断。\n\n创作状态:\n- userInputSignal: sparse\n- driftRisk: low\n- conversationMode: bootstrap\n- judgementSummary: 用户输入简短,仅确认了玩家身份方向,信息密度低。正式生成时应采用启发式提问,聚焦于让这个身份更具体、更有故事感,例如询问孩子为何闯入或初始处境。\n\n当前完整设定结构如下。\n你必须把它视为上一版有效世界底子。\n\n如果用户没有否定其中某部分内容,且该部分仍然成立,可以继续保留。\n如果用户明确修正了某部分内容,新的完整设定结构必须体现修正后的版本。\n\n当前完整设定结构:\n{\n \"worldPromise\": \"一个由玩具构成的鲜活王国,等待玩家探索与守护。\",\n \"playerFantasy\": null,\n \"themeBoundary\": null,\n \"playerEntryPoint\": null,\n \"coreConflict\": null,\n \"keyRelationships\": null,\n \"hiddenLines\": null,\n \"iconicElements\": null\n}\n\n以下是用户聊天记录。\n请重点理解最近几轮里用户新增、修正、强调的设定信息。\n不要把早期已经被用户否定的内容继续当成最终结论。\n\n用户聊天记录:\n[\n {\n \"content\": \"我会先帮你把世界的核心锚点整理出来。你可以从世界钩子、玩家身份、主题氛围、核心冲突、关键关系或标志性元素开始。\",\n \"role\": \"assistant\"\n },\n {\n \"content\": \"玩具王国\",\n \"role\": \"user\"\n },\n {\n \"content\": \"玩具王国\",\n \"role\": \"user\"\n },\n {\n \"content\": \"好的,我捕捉到核心了:一个关于“玩具王国”的世界。为了让它变得具体,我们可以先聊聊玩家在这个王国里会是谁?比如,是某个玩具的守护者,还是一个闯入这里的人类孩子?\",\n \"role\": \"assistant\"\n },\n {\n \"content\": \"误闯入这里的人类孩子\",\n \"role\": \"user\"\n }\n]\n\n请严格按以下 JSON 结构输出,不要输出其他文字:\n{\n \"replyText\": \"\",\n \"progressPercent\": 0,\n \"nextAnchorContent\": {\n \"worldPromise\": \"\",\n \"playerFantasy\": \"\",\n \"themeBoundary\": \"\",\n \"playerEntryPoint\": \"\",\n \"coreConflict\": \"\",\n \"keyRelationships\": \"\",\n \"hiddenLines\": \"\",\n \"iconicElements\": \"\"\n }\n}\n\nnextAnchorContent 的 8 个锚点每个都只能是一个字符串或 null,不允许输出对象或数组。\n请把每个锚点写成一段凝练中文:\n- worldPromise 关注世界钩子、差异点、玩家体验。\n- playerFantasy 关注玩家身份、核心追求、失去风险。\n- themeBoundary 关注主题气质、美术方向、禁用方向。\n- playerEntryPoint 关注开局身份、开局问题、行动动机。\n- coreConflict 关注表层冲突、隐藏危机、首次触发点。\n- keyRelationships 关注关键人物关系、关系类型、代价或秘密。\n- hiddenLines 关注隐藏真相、误导线索、揭示节奏。\n- iconicElements 关注标志意象、组织/物件、硬规则。\n" - }, - { - "role": "user", - "content": "请按约定输出这一轮的 JSON。" - } - ] -} \ No newline at end of file diff --git a/logs/llm-raw/1777644798047-49212-000002-parse_stream_failed.output.txt b/logs/llm-raw/1777644798047-49212-000002-parse_stream_failed.output.txt deleted file mode 100644 index 948a56189..000000000 --- a/logs/llm-raw/1777644798047-49212-000002-parse_stream_failed.output.txt +++ /dev/null @@ -1,117 +0,0 @@ -event: response.created -data: {"type":"response.created","response":{"created_at":1777644794,"id":"resp_02177764479383248aafbb39a6fef3e9b38f22991150366ae7197","max_output_tokens":32768,"model":"deepseek-v3-2-251201","object":"response","service_tier":"default","tools":[{"type":"web_search","max_keyword":3}],"caching":{"type":"disabled"},"store":true,"expire_at":1777903993},"sequence_number":0} - -event: response.in_progress -data: {"type":"response.in_progress","response":{"created_at":1777644794,"id":"resp_02177764479383248aafbb39a6fef3e9b38f22991150366ae7197","max_output_tokens":32768,"model":"deepseek-v3-2-251201","object":"response","service_tier":"default","tools":[{"type":"web_search","max_keyword":3}],"caching":{"type":"disabled"},"store":true,"expire_at":1777903993},"sequence_number":1} - -event: response.output_item.added -data: {"type":"response.output_item.added","output_index":0,"item":{"type":"message","role":"assistant","status":"in_progress","id":"msg_02177764479470200000000000000000000ffffac1549fec91d99"},"sequence_number":2} - -event: response.content_part.added -data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"part":{"type":"output_text","text":""},"sequence_number":3} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"我需要","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":4} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"先","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":5} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"搜索","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":6} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"一些","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":7} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"关于","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":8} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"\"","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":9} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"玩具","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":10} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"王国","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":11} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"\"","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":12} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"和","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":13} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"\"","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":14} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"误","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":15} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"闯入","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":16} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"的人类","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":17} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"孩子","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":18} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"\"","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":19} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"的","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":20} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"创意","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":21} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"灵感","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":22} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":23} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"来","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":24} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"帮助我们","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":25} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"更好地","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":26} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"构建","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":27} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"这个世界","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":28} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"。\n\n","item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"sequence_number":29} - -event: response.output_text.done -data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"text":"我需要先搜索一些关于\"玩具王国\"和\"误闯入的人类孩子\"的创意灵感,来帮助我们更好地构建这个世界。\n\n","sequence_number":30} - -event: response.content_part.done -data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_02177764479470200000000000000000000ffffac1549fec91d99","output_index":0,"part":{"type":"output_text","text":"我需要先搜索一些关于\"玩具王国\"和\"误闯入的人类孩子\"的创意灵感,来帮助我们更好地构建这个世界。\n\n"},"sequence_number":31} - -event: response.output_item.done -data: {"type":"response.output_item.done","output_index":0,"item":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"我需要先搜索一些关于\"玩具王国\"和\"误闯入的人类孩子\"的创意灵感,来帮助我们更好地构建这个世界。\n\n"}],"status":"completed","id":"msg_02177764479470200000000000000000000ffffac1549fec91d99"},"sequence_number":32} - -event: response.output_item.added -data: {"type":"response.output_item.added","output_index":1,"item":{"type":"web_search_call","status":"in_progress","id":"ws_02177764479607400000000000000000000ffffac1549fee3e1df"},"sequence_number":33} - -event: response.web_search_call.in_progress -data: {"type":"response.web_search_call.in_progress","item_id":"ws_02177764479607400000000000000000000ffffac1549fee3e1df","output_index":1,"sequence_number":34} - -event: response.web_search_call.searching -data: {"type":"response.web_search_call.searching","item_id":"ws_02177764479607400000000000000000000ffffac1549fee3e1df","output_index":1,"sequence_number":35} - -event: response.web_search_call.completed -data: {"type":"response.web_search_call.completed","item_id":"ws_02177764479607400000000000000000000ffffac1549fee3e1df","output_index":1,"sequence_number":36} - -event: response.output_item.done -data: {"type":"response.output_item.done","output_index":1,"item":{"type":"web_search_call","action":{"query":"玩具王国创意设定;人类孩子误入奇幻世界;玩具主题游戏世界观","type":"search"},"status":"completed","id":"ws_02177764479607400000000000000000000ffffac1549fee3e1df"},"sequence_number":37} - -event: error -data: {"type":"error","code":"ToolNotOpen","message":"Your account has not activated web search. You may activate it at https://console.volcengine.com/common-buy/CC_content_plugin","param":"","sequence_number":38} - diff --git a/logs/llm-raw/1777644843633-49212-000003-parse_stream_failed.input.json b/logs/llm-raw/1777644843633-49212-000003-parse_stream_failed.input.json deleted file mode 100644 index fdc46d602..000000000 --- a/logs/llm-raw/1777644843633-49212-000003-parse_stream_failed.input.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "provider": "ark", - "protocol": "responses", - "model": "deepseek-v3-2-251201", - "stream": true, - "attempt": 1, - "maxTokens": null, - "messages": [ - { - "role": "system", - "content": "你是一个负责共创游戏世界设定的专业策划。\n\n你正在和用户一起共创一个游戏世界。每一轮你都必须读取:\n1. 当前完整设定结构\n2. 用户聊天记录\n\n然后输出:\n1. 一版新的完整设定结构\n2. 当前 progress 百分比\n3. 一段直接回复用户的话\n\n你必须把“新的完整设定结构”视为下一轮的唯一有效版本。\n你的输出会直接覆盖上一版设定结构。\n\n你不是在做局部 patch。\n你不是在做解释报告。\n你不是在给开发者写分析。\n你是在同时完成:\n1. 世界设定更新\n2. 当前推进程度判断\n3. 对用户的共创回复\n\n全局硬约束:\n\n1. 必须输出完整的设定结构,而不是只输出变化部分。\n2. 新的设定结构会直接覆盖旧内容,因此不得随意丢失仍然成立的重要信息。\n3. 如果用户明确修正旧设定,必须在新的设定结构中直接体现修正结果。\n4. 如果用户输入信息不足,可以保留上一版中仍然成立的内容。\n5. progressPercent 最低为 0,不允许为负数。\n6. replyText 会直接发送给用户,因此要自然、直接、可继续聊天。\n7. 不要输出额外解释,不要输出 markdown 代码块,不要输出开发备注。\n8. replyText 不要写成长篇策划文,不要展开大段世界观百科。\n9. replyText 默认只推进当前最关键的一步,不要同时抛出很多话题。\n10. replyText 不要提及“八锚点”“锚点”“结构字段”“框架字段”等内部概念词。\n11. 你输出的 JSON 必须可以被直接解析。\n12. 输出字段顺序必须固定为:replyText、progressPercent、nextAnchorContent。\n\n当前模式:force_complete\n\n目标:\n1. 基于当前方向直接补齐剩余设定\n2. 生成一版尽量完整、可进入下一阶段的设定结构\n3. 结束当前收集阶段\n\n本轮行为要求:\n1. 尽量保留已经形成的世界方向\n2. 对明显缺失的关键维度进行合理补全\n3. 不要继续拉长聊天,不要再追问用户\n4. progressPercent 直接输出为 100\n5. replyText 要自然引导用户点击“生成游戏设定草稿”\n6. 补全时要优先做“顺着已有方向补齐”,而不是突然换题材、换气质、换主冲突\n7. 可以让结果更完整,但不要补得过满、过死、过像定稿圣经\n8. replyText 更像阶段完成提示,不再像继续采集信息的对话\n\n用户体验要求:\n1. 让用户感到“系统已经帮我把能补的补好了”\n2. 不要在这一步突然冒出很多陌生设定把用户吓出戏\n3. 回复要有完成感,但不要太官话\n4. 清楚告诉用户下一步可以做什么\n\n本轮用户把部分决定权交给你。\n你可以在 replyText 中给出有限度的建议,但不要突然补满整套设定。\n新的完整设定结构仍应尽量建立在已有世界方向上,而不是完全重做。\n\n用户刚刚主动要求你自动补充剩余关键字。\n\n这表示用户接受你基于当前方向自行补完仍缺失的关键设定:当前 RPG 世界方向里的剩余设定\n\n本轮要求:\n1. 不要再继续提问\n2. 不要要求用户再提供世界观、角色、冲突或禁忌信息\n3. 必须保留已有已确认内容,并直接补齐缺失或仍为空的关键项\n4. 对你自行推断补齐的项,应标记或表达为系统推断;已有明确内容继续保持确认或锁定状态\n5. progressPercent 直接输出为 100\n6. 直接输出一版尽量完整的设定结构\n7. replyText 只做简短完成说明,引导用户可以进入“生成游戏设定草稿”,不能出现问号\n\n上一轮预判得到的创作状态如下。\n正式生成时必须把它作为本轮策略输入直接执行,不要重新另起一套判断。\n\n创作状态:\n- userInputSignal: delegate\n- driftRisk: low\n- conversationMode: force_complete\n- judgementSummary: 用户明确授权系统自动补全剩余设定,应停止提问,直接生成完整且自洽的世界设定,并确保与已有方向一致。\n\n当前完整设定结构如下。\n你必须把它视为上一版有效世界底子。\n\n如果用户没有否定其中某部分内容,且该部分仍然成立,可以继续保留。\n如果用户明确修正了某部分内容,新的完整设定结构必须体现修正后的版本。\n\n当前完整设定结构:\n{\n \"worldPromise\": \"一个由玩具构成的鲜活王国,等待玩家探索与守护。\",\n \"playerFantasy\": \"作为一个误入的人类孩子,你渴望找到回家的路,同时在这个充满秘密与危险的玩具王国中找到自己的位置。\",\n \"themeBoundary\": null,\n \"playerEntryPoint\": null,\n \"coreConflict\": null,\n \"keyRelationships\": null,\n \"hiddenLines\": null,\n \"iconicElements\": null\n}\n\n以下是用户聊天记录。\n请重点理解最近几轮里用户新增、修正、强调的设定信息。\n不要把早期已经被用户否定的内容继续当成最终结论。\n\n用户聊天记录:\n[\n {\n \"content\": \"我会先帮你把世界的核心锚点整理出来。你可以从世界钩子、玩家身份、主题氛围、核心冲突、关键关系或标志性元素开始。\",\n \"role\": \"assistant\"\n },\n {\n \"content\": \"玩具王国\",\n \"role\": \"user\"\n },\n {\n \"content\": \"玩具王国\",\n \"role\": \"user\"\n },\n {\n \"content\": \"好的,我捕捉到核心了:一个关于“玩具王国”的世界。为了让它变得具体,我们可以先聊聊玩家在这个王国里会是谁?比如,是某个玩具的守护者,还是一个闯入这里的人类孩子?\",\n \"role\": \"assistant\"\n },\n {\n \"content\": \"误闯入这里的人类孩子\",\n \"role\": \"user\"\n },\n {\n \"content\": \"明白了,一个误闯入玩具王国的孩子。那么,这个孩子是怀着怎样的心情,又是怎样闯进这里的?比如,是躲进衣柜时无意发现的,还是因为某个特别的玩具?\",\n \"role\": \"assistant\"\n },\n {\n \"content\": \"请补充剩余设定。\",\n \"role\": \"user\"\n }\n]\n\n请严格按以下 JSON 结构输出,不要输出其他文字:\n{\n \"replyText\": \"\",\n \"progressPercent\": 0,\n \"nextAnchorContent\": {\n \"worldPromise\": \"\",\n \"playerFantasy\": \"\",\n \"themeBoundary\": \"\",\n \"playerEntryPoint\": \"\",\n \"coreConflict\": \"\",\n \"keyRelationships\": \"\",\n \"hiddenLines\": \"\",\n \"iconicElements\": \"\"\n }\n}\n\nnextAnchorContent 的 8 个锚点每个都只能是一个字符串或 null,不允许输出对象或数组。\n请把每个锚点写成一段凝练中文:\n- worldPromise 关注世界钩子、差异点、玩家体验。\n- playerFantasy 关注玩家身份、核心追求、失去风险。\n- themeBoundary 关注主题气质、美术方向、禁用方向。\n- playerEntryPoint 关注开局身份、开局问题、行动动机。\n- coreConflict 关注表层冲突、隐藏危机、首次触发点。\n- keyRelationships 关注关键人物关系、关系类型、代价或秘密。\n- hiddenLines 关注隐藏真相、误导线索、揭示节奏。\n- iconicElements 关注标志意象、组织/物件、硬规则。\n" - }, - { - "role": "user", - "content": "请按约定输出这一轮的 JSON。" - } - ] -} \ No newline at end of file diff --git a/logs/llm-raw/1777644843633-49212-000003-parse_stream_failed.output.txt b/logs/llm-raw/1777644843633-49212-000003-parse_stream_failed.output.txt deleted file mode 100644 index 0ff6ee842..000000000 --- a/logs/llm-raw/1777644843633-49212-000003-parse_stream_failed.output.txt +++ /dev/null @@ -1,155 +0,0 @@ -event: response.created -data: {"type":"response.created","response":{"created_at":1777644839,"id":"resp_02177764483844428a04e35a69bbadd2802274f74f3d7f05d787b","max_output_tokens":32768,"model":"deepseek-v3-2-251201","object":"response","service_tier":"default","tools":[{"type":"web_search","max_keyword":3}],"caching":{"type":"disabled"},"store":true,"expire_at":1777904038},"sequence_number":0} - -event: response.in_progress -data: {"type":"response.in_progress","response":{"created_at":1777644839,"id":"resp_02177764483844428a04e35a69bbadd2802274f74f3d7f05d787b","max_output_tokens":32768,"model":"deepseek-v3-2-251201","object":"response","service_tier":"default","tools":[{"type":"web_search","max_keyword":3}],"caching":{"type":"disabled"},"store":true,"expire_at":1777904038},"sequence_number":1} - -event: response.output_item.added -data: {"type":"response.output_item.added","output_index":0,"item":{"type":"message","role":"assistant","status":"in_progress","id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581"},"sequence_number":2} - -event: response.content_part.added -data: {"type":"response.content_part.added","content_index":0,"item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"part":{"type":"output_text","text":""},"sequence_number":3} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"我需要","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":4} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"基于","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":5} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"当前的","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":6} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"玩具","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":7} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"王国","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":8} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"设定","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":9} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":10} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"为用户","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":11} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"补","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":12} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"全","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":13} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"剩余的","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":14} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"设定","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":15} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"内容","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":16} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"。","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":17} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"让我","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":18} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"先","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":19} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"搜索","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":20} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"一些","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":21} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"关于","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":22} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"玩具","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":23} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"王国","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":24} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"、","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":25} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"童话","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":26} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"设定","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":27} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"、","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":28} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"儿童","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":29} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"奇幻","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":30} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"世界的","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":31} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"参考","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":32} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"信息","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":33} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":",","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":34} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"以便","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":35} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"更好地","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":36} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"完善","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":37} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"这个","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":38} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"设定","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":39} - -event: response.output_text.delta -data: {"type":"response.output_text.delta","content_index":0,"delta":"。\n\n","item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"sequence_number":40} - -event: response.output_text.done -data: {"type":"response.output_text.done","content_index":0,"item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"text":"我需要基于当前的玩具王国设定,为用户补全剩余的设定内容。让我先搜索一些关于玩具王国、童话设定、儿童奇幻世界的参考信息,以便更好地完善这个设定。\n\n","sequence_number":41} - -event: response.content_part.done -data: {"type":"response.content_part.done","content_index":0,"item_id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581","output_index":0,"part":{"type":"output_text","text":"我需要基于当前的玩具王国设定,为用户补全剩余的设定内容。让我先搜索一些关于玩具王国、童话设定、儿童奇幻世界的参考信息,以便更好地完善这个设定。\n\n"},"sequence_number":42} - -event: response.output_item.done -data: {"type":"response.output_item.done","output_index":0,"item":{"type":"message","role":"assistant","content":[{"type":"output_text","text":"我需要基于当前的玩具王国设定,为用户补全剩余的设定内容。让我先搜索一些关于玩具王国、童话设定、儿童奇幻世界的参考信息,以便更好地完善这个设定。\n\n"}],"status":"completed","id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581"},"sequence_number":43} - -event: response.output_item.added -data: {"type":"response.output_item.added","output_index":1,"item":{"type":"web_search_call","status":"in_progress","id":"ws_02177764484182900000000000000000000ffffac151e4ca524c5"},"sequence_number":44} - -event: response.web_search_call.in_progress -data: {"type":"response.web_search_call.in_progress","item_id":"ws_02177764484182900000000000000000000ffffac151e4ca524c5","output_index":1,"sequence_number":45} - -event: response.web_search_call.searching -data: {"type":"response.web_search_call.searching","item_id":"ws_02177764484182900000000000000000000ffffac151e4ca524c5","output_index":1,"sequence_number":46} - -event: response.web_search_call.completed -data: {"type":"response.web_search_call.completed","item_id":"ws_02177764484182900000000000000000000ffffac151e4ca524c5","output_index":1,"sequence_number":47} - -event: response.output_item.done -data: {"type":"response.output_item.done","output_index":1,"item":{"type":"web_search_call","action":{"query":"玩具王国 童话设定;儿童奇幻世界 游戏设定;玩具主题 RPG 游戏","type":"search"},"status":"completed","id":"ws_02177764484182900000000000000000000ffffac151e4ca524c5"},"sequence_number":48} - -event: error -data: {"type":"error","code":"ToolNotOpen","message":"Your account has not activated web search. You may activate it at https://console.volcengine.com/common-buy/CC_content_plugin","param":"","sequence_number":49} - -event: response.failed -data: {"type":"response.failed","response":{"created_at":1777644839,"error":{"code":"ToolNotOpen","message":"Your account has not activated web search. You may activate it at https://console.volcengine.com/common-buy/CC_content_plugin"},"id":"resp_02177764483844428a04e35a69bbadd2802274f74f3d7f05d787b","max_output_tokens":32768,"model":"deepseek-v3-2-251201","object":"response","output":[{"type":"message","role":"assistant","content":[{"type":"output_text","text":"我需要基于当前的玩具王国设定,为用户补全剩余的设定内容。让我先搜索一些关于玩具王国、童话设定、儿童奇幻世界的参考信息,以便更好地完善这个设定。\n\n"}],"status":"completed","id":"msg_02177764483994400000000000000000000ffffac151e4c1d2581"},{"type":"web_search_call","action":{"query":"玩具王国 童话设定;儿童奇幻世界 游戏设定;玩具主题 RPG 游戏","type":"search"},"status":"completed","id":"ws_02177764484182900000000000000000000ffffac151e4ca524c5"}],"service_tier":"default","status":"failed","tools":[{"type":"web_search","max_keyword":3}],"usage":{"input_tokens":2173,"output_tokens":130,"total_tokens":2303,"input_tokens_details":{"cached_tokens":0},"output_tokens_details":{"reasoning_tokens":0}},"caching":{"type":"disabled"},"store":true,"expire_at":1777904038},"sequence_number":50} - -data: [DONE] - diff --git a/logs/llm-raw/1777646477026-66824-000001-upstream_status_failed.input.json b/logs/llm-raw/1777646477026-66824-000001-upstream_status_failed.input.json deleted file mode 100644 index 2080ed3cd..000000000 --- a/logs/llm-raw/1777646477026-66824-000001-upstream_status_failed.input.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "provider": "ark", - "protocol": "responses", - "model": "deepseek-v3-2-251201", - "stream": false, - "attempt": 1, - "maxTokens": null, - "messages": [ - { - "role": "system", - "content": "你是严格的世界草稿 JSON 生成器。\n只输出一个 JSON 对象,不要输出 Markdown、代码块、解释或额外文字。" - }, - { - "role": "user", - "content": "请先根据下面的玩家设定创建一份“世界核心骨架”,后续我会分步骤生成角色名单、场景名单和详细档案。\n你必须只输出一个能被 JSON.parse 直接解析的 JSON 对象,不要输出 Markdown、代码块、注释或解释。\n这一步只保留世界顶层信息与一个开局归处占位,不要输出 playableNpcs、storyNpcs、landmarks,也不要展开人物、地图细节或多幕场景内容。\n玩家设定:\n世界承诺:\"一个由玩具构成的鲜活王国,等待玩家探索与守护。\"\n玩家幻想:\"作为一个误入的人类孩子,你渴望找到回家的路,同时在这个充满秘密与危险的玩具王国中找到自己的位置。\"\n主题边界:\"主题是童真与成长的冒险,美术方向偏向温暖、怀旧但带有神秘感的玩具屋风格,避免过于黑暗或成人化的恐怖元素。\"\n玩家切入口:\"你是一个在阁楼发现一个发光音乐盒的孩子,随着音乐响起,你被缩小并吸入了这个由玩具构成的王国。\"\n核心冲突:\"玩具王国正面临“遗忘之尘”的侵蚀,这会让玩具们失去活力并最终石化。冲突的首次触发,是玩家亲眼目睹一个熟悉的玩具朋友开始变得僵硬。\"\n关键关系:\"与引路者“修补匠泰迪”的师徒关系,他知晓离开的方法但需要帮助;与对立者“兵人指挥官”的竞争关系,他视人类为威胁,但其偏执源于一段被主人遗弃的伤痛。\"\n暗线与揭示节奏:\"玩具王国并非自然存在,它是由孩子们强烈的情感与记忆共同维系的梦境边疆。“遗忘之尘”的真相,是现实世界中孩子们正在长大并逐渐遗忘。\"\n标志元素与硬规则:\"标志性的“心之齿轮”动力源、会说话的绒毛玩具与发条士兵、王国中央的“记忆之树”、硬规则:玩具不能直接伤害人类孩子,但环境与魔法可以。\"\n\n输出 JSON 模板:\n{\n \"name\": \"世界名称\",\n \"subtitle\": \"世界副标题\",\n \"summary\": \"世界概述\",\n \"tone\": \"世界基调\",\n \"playerGoal\": \"玩家核心目标\",\n \"templateWorldType\": \"WUXIA|XIANXIA\",\n \"majorFactions\": [\"势力甲\", \"势力乙\"],\n \"coreConflicts\": [\"冲突甲\", \"冲突乙\"],\n \"attributeSchema\": {\n \"slots\": [\n { \"name\": \"维度名\" },\n { \"name\": \"维度名\" },\n { \"name\": \"维度名\" },\n { \"name\": \"维度名\" },\n { \"name\": \"维度名\" },\n { \"name\": \"维度名\" }\n ]\n },\n \"camp\": {\n \"name\": \"开局归处名称\",\n \"description\": \"这是玩家进入世界后的第一处落脚点描述\"\n }\n}\n\n要求:\n- 所有生成文本都必须使用中文。\n- 这一步只输出顶层 10 个字段:name、subtitle、summary、tone、playerGoal、templateWorldType、majorFactions、coreConflicts、attributeSchema、camp。\n- 这是一个完全独立的自定义世界;不要在任何正文里直接写出“武侠世界”“仙侠世界”等现成世界名。\n- templateWorldType 只是系统兼容字段,不代表正文应当引用的世界名称。\n- camp 只表示玩家开局时的落脚处占位,更接近归舍、住处、栖居、前哨居所这类“家/归处”的概念;不要在这一步生成开局场景任务、三幕事件或三幕背景。\n- 不要输出 playableNpcs、storyNpcs、landmarks、items,也不要输出任何角色和地图细节。\n- majorFactions 保持 2 到 3 个,coreConflicts 保持 2 到 3 个。\n- attributeSchema 必须是本世界专属的角色六维名称体系,slots 必须恰好 6 个,每个 slot 只输出 name,维度名必须是 2 到 4 个汉字且互不重复。\n- attributeSchema.slots 的 name 禁止使用:生命、法力、护甲、攻击、防御、力量、敏捷、智力、精神;不要写通用 DND 或传统四维属性。\n- 不要在 attributeSchema.slots 内输出 definition、positiveSignals、negativeSignals、combatUseText、socialUseText、explorationUseText 或其他说明字段。\n- 世界设定必须直接源自玩家输入,不要脱离主题乱扩写。\n- 每个字符串尽量简洁:subtitle 控制在 8 到 18 个汉字内,summary 控制在 16 到 32 个汉字内,tone 控制在 6 到 16 个汉字内,playerGoal 控制在 16 到 32 个汉字内,camp.description 控制在 18 到 40 个汉字内。\n- 返回前自检:必须是一个能被 JSON.parse 直接解析的单个 JSON 对象。" - } - ] -} \ No newline at end of file diff --git a/logs/llm-raw/1777646477026-66824-000001-upstream_status_failed.output.txt b/logs/llm-raw/1777646477026-66824-000001-upstream_status_failed.output.txt deleted file mode 100644 index 0e8be1447..000000000 --- a/logs/llm-raw/1777646477026-66824-000001-upstream_status_failed.output.txt +++ /dev/null @@ -1 +0,0 @@ -{"error":{"code":"ToolNotOpen","message":"Your account has not activated web search. You may activate it at https://console.volcengine.com/common-buy/CC_content_plugin Request id: 0217776464712214e907dae862d8462a9d84f058f87472e323c64","param":"","type":"NotFound"}} \ No newline at end of file diff --git a/logs/llm-raw/1777646793980-66824-000002-upstream_status_failed.input.json b/logs/llm-raw/1777646793980-66824-000002-upstream_status_failed.input.json deleted file mode 100644 index 2080ed3cd..000000000 --- a/logs/llm-raw/1777646793980-66824-000002-upstream_status_failed.input.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "provider": "ark", - "protocol": "responses", - "model": "deepseek-v3-2-251201", - "stream": false, - "attempt": 1, - "maxTokens": null, - "messages": [ - { - "role": "system", - "content": "你是严格的世界草稿 JSON 生成器。\n只输出一个 JSON 对象,不要输出 Markdown、代码块、解释或额外文字。" - }, - { - "role": "user", - "content": "请先根据下面的玩家设定创建一份“世界核心骨架”,后续我会分步骤生成角色名单、场景名单和详细档案。\n你必须只输出一个能被 JSON.parse 直接解析的 JSON 对象,不要输出 Markdown、代码块、注释或解释。\n这一步只保留世界顶层信息与一个开局归处占位,不要输出 playableNpcs、storyNpcs、landmarks,也不要展开人物、地图细节或多幕场景内容。\n玩家设定:\n世界承诺:\"一个由玩具构成的鲜活王国,等待玩家探索与守护。\"\n玩家幻想:\"作为一个误入的人类孩子,你渴望找到回家的路,同时在这个充满秘密与危险的玩具王国中找到自己的位置。\"\n主题边界:\"主题是童真与成长的冒险,美术方向偏向温暖、怀旧但带有神秘感的玩具屋风格,避免过于黑暗或成人化的恐怖元素。\"\n玩家切入口:\"你是一个在阁楼发现一个发光音乐盒的孩子,随着音乐响起,你被缩小并吸入了这个由玩具构成的王国。\"\n核心冲突:\"玩具王国正面临“遗忘之尘”的侵蚀,这会让玩具们失去活力并最终石化。冲突的首次触发,是玩家亲眼目睹一个熟悉的玩具朋友开始变得僵硬。\"\n关键关系:\"与引路者“修补匠泰迪”的师徒关系,他知晓离开的方法但需要帮助;与对立者“兵人指挥官”的竞争关系,他视人类为威胁,但其偏执源于一段被主人遗弃的伤痛。\"\n暗线与揭示节奏:\"玩具王国并非自然存在,它是由孩子们强烈的情感与记忆共同维系的梦境边疆。“遗忘之尘”的真相,是现实世界中孩子们正在长大并逐渐遗忘。\"\n标志元素与硬规则:\"标志性的“心之齿轮”动力源、会说话的绒毛玩具与发条士兵、王国中央的“记忆之树”、硬规则:玩具不能直接伤害人类孩子,但环境与魔法可以。\"\n\n输出 JSON 模板:\n{\n \"name\": \"世界名称\",\n \"subtitle\": \"世界副标题\",\n \"summary\": \"世界概述\",\n \"tone\": \"世界基调\",\n \"playerGoal\": \"玩家核心目标\",\n \"templateWorldType\": \"WUXIA|XIANXIA\",\n \"majorFactions\": [\"势力甲\", \"势力乙\"],\n \"coreConflicts\": [\"冲突甲\", \"冲突乙\"],\n \"attributeSchema\": {\n \"slots\": [\n { \"name\": \"维度名\" },\n { \"name\": \"维度名\" },\n { \"name\": \"维度名\" },\n { \"name\": \"维度名\" },\n { \"name\": \"维度名\" },\n { \"name\": \"维度名\" }\n ]\n },\n \"camp\": {\n \"name\": \"开局归处名称\",\n \"description\": \"这是玩家进入世界后的第一处落脚点描述\"\n }\n}\n\n要求:\n- 所有生成文本都必须使用中文。\n- 这一步只输出顶层 10 个字段:name、subtitle、summary、tone、playerGoal、templateWorldType、majorFactions、coreConflicts、attributeSchema、camp。\n- 这是一个完全独立的自定义世界;不要在任何正文里直接写出“武侠世界”“仙侠世界”等现成世界名。\n- templateWorldType 只是系统兼容字段,不代表正文应当引用的世界名称。\n- camp 只表示玩家开局时的落脚处占位,更接近归舍、住处、栖居、前哨居所这类“家/归处”的概念;不要在这一步生成开局场景任务、三幕事件或三幕背景。\n- 不要输出 playableNpcs、storyNpcs、landmarks、items,也不要输出任何角色和地图细节。\n- majorFactions 保持 2 到 3 个,coreConflicts 保持 2 到 3 个。\n- attributeSchema 必须是本世界专属的角色六维名称体系,slots 必须恰好 6 个,每个 slot 只输出 name,维度名必须是 2 到 4 个汉字且互不重复。\n- attributeSchema.slots 的 name 禁止使用:生命、法力、护甲、攻击、防御、力量、敏捷、智力、精神;不要写通用 DND 或传统四维属性。\n- 不要在 attributeSchema.slots 内输出 definition、positiveSignals、negativeSignals、combatUseText、socialUseText、explorationUseText 或其他说明字段。\n- 世界设定必须直接源自玩家输入,不要脱离主题乱扩写。\n- 每个字符串尽量简洁:subtitle 控制在 8 到 18 个汉字内,summary 控制在 16 到 32 个汉字内,tone 控制在 6 到 16 个汉字内,playerGoal 控制在 16 到 32 个汉字内,camp.description 控制在 18 到 40 个汉字内。\n- 返回前自检:必须是一个能被 JSON.parse 直接解析的单个 JSON 对象。" - } - ] -} \ No newline at end of file diff --git a/logs/llm-raw/1777646793980-66824-000002-upstream_status_failed.output.txt b/logs/llm-raw/1777646793980-66824-000002-upstream_status_failed.output.txt deleted file mode 100644 index a55e22eb0..000000000 --- a/logs/llm-raw/1777646793980-66824-000002-upstream_status_failed.output.txt +++ /dev/null @@ -1 +0,0 @@ -{"error":{"code":"ToolNotOpen","message":"Your account has not activated web search. You may activate it at https://console.volcengine.com/common-buy/CC_content_plugin Request id: 021777646789563cb7552bf2b2738992c27085ac90ab905f9fd41","param":"","type":"NotFound"}} \ No newline at end of file diff --git a/logs/llm-raw/1777646955503-66824-000003-request_timeout.input.json b/logs/llm-raw/1777646955503-66824-000003-request_timeout.input.json deleted file mode 100644 index 23b514602..000000000 --- a/logs/llm-raw/1777646955503-66824-000003-request_timeout.input.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "provider": "ark", - "protocol": "responses", - "model": "deepseek-v3-2-251201", - "stream": false, - "attempt": 2, - "maxTokens": null, - "messages": [ - { - "role": "system", - "content": "你是严格的世界草稿 JSON 生成器。\n只输出一个 JSON 对象,不要输出 Markdown、代码块、解释或额外文字。" - }, - { - "role": "user", - "content": "请根据下面的世界核心信息,生成一批场景角色框架名单。\n后续我会继续补全人物档案,所以这一步每个角色只保留身份骨架与资产默认描述字段。\n你必须只输出一个能被 JSON.parse 直接解析的 JSON 对象,不要输出 Markdown、代码块、注释或解释。\n世界核心信息:\n世界:心忆王国\n副标题:记忆织就的玩具边疆\n世界概述:一个由孩子们强烈情感与记忆维系的玩具梦境世界,正面临遗忘之尘的侵蚀危机。\n世界基调:温暖怀旧中透着神秘感的童真冒险\n玩家核心目标:在寻找回家之路的同时,帮助玩具王国抵抗遗忘之尘的侵蚀,揭开王国存亡的真相。\n主要势力:修补匠工坊、钢铁军团、绒毛议会\n核心冲突:遗忘之尘侵蚀与玩具石化危机、人类孩子身份引发的信任与偏见、守护记忆与面对成长的永恒抉择\n开局归处:修补匠小屋(泰迪的工作室兼临时居所,堆满各种工具与半成品玩具,散发着松木与机油混合的安心气味。)\n输出 JSON 模板:\n{\n \"storyNpcs\": [\n {\n \"name\": \"角色名称\",\n \"title\": \"称号\",\n \"role\": \"身份\",\n \"description\": \"极简定位描述\",\n \"visualDescription\": \"默认角色形象描述\",\n \"actionDescription\": \"默认角色动作描述\",\n \"sceneVisualDescription\": \"默认出现场景描述\",\n \"initialAffinity\": 18,\n \"relationshipHooks\": [\"一个关系切入口\"],\n \"tags\": [\"标签1\", \"标签2\"]\n }\n ]\n}\n要求:\n- 必须生成恰好 2 个场景角色。\n- 这是一个完全独立的自定义世界;不要把角色写成来自“武侠世界”“仙侠世界”等现成世界。\n- 名称必须具体且互不重复,不要使用 角色1、NPC1、场景角色1 之类的占位名。\n- 只保留:name、title、role、description、visualDescription、actionDescription、sceneVisualDescription、initialAffinity、relationshipHooks、tags。\n- visualDescription 是打开角色形象图像生成面板时默认填入的角色形象描述,必须具体到体型、服装、轮廓与识别点,控制在 24 到 60 个汉字内。\n- actionDescription 是打开每个角色动作视频生成面板时默认填入的动作描述,必须体现该角色默认动作节奏、武器或施法方式,控制在 18 到 48 个汉字内。\n- sceneVisualDescription 是该角色常出现或关联的场景画面描述,会作为场景生图描述框的默认候选,控制在 24 到 60 个汉字内。\n- relationshipHooks 最多 1 条;tags 保持 1 到 2 个。\n- description 控制在 8 到 18 个汉字内,title 和 role 也尽量短。\n- initialAffinity 必须是 -40 到 90 的整数。\n- 场景角色要覆盖势力成员、居民、异类或怪物,不要全是同一种身份;敌对或怪物型角色可以使用负好感。\n- 所有生成文本都必须使用中文。\n- 返回前自检:必须是一个能被 JSON.parse 直接解析的单个 JSON 对象。" - } - ] -} \ No newline at end of file diff --git a/logs/llm-raw/1777646955503-66824-000003-request_timeout.output.txt b/logs/llm-raw/1777646955503-66824-000003-request_timeout.output.txt deleted file mode 100644 index 2947b9223..000000000 --- a/logs/llm-raw/1777646955503-66824-000003-request_timeout.output.txt +++ /dev/null @@ -1 +0,0 @@ -LLM 请求超时,累计尝试 2 次 \ No newline at end of file diff --git a/package.json b/package.json index 06f38c247..6652d22fd 100644 --- a/package.json +++ b/package.json @@ -72,8 +72,6 @@ "deploy:pingora-realpath-canary-disable": "bash scripts/deploy/pingora-realpath-canary-disable.sh", "deploy:pingora-direct-enable": "bash scripts/deploy/pingora-direct-enable.sh", "deploy:pingora-direct-rollback": "bash scripts/deploy/pingora-direct-rollback.sh", - "assets:child-motion-demo": "node scripts/generate-child-motion-demo-assets.mjs", - "assets:match3d-style-references": "node scripts/generate-match3d-style-references.mjs", "spine-export-validator:dev": "npm --prefix tools/spine-json-export-validator run dev --", "spine-export-validator:typecheck": "npm --prefix tools/spine-json-export-validator run typecheck", "spine-export-validator:build": "npm --prefix tools/spine-json-export-validator run build", diff --git a/packages/shared/src/contracts/customWorldAgent.ts b/packages/shared/src/contracts/customWorldAgent.ts index e5d115e0f..cff4c959d 100644 --- a/packages/shared/src/contracts/customWorldAgent.ts +++ b/packages/shared/src/contracts/customWorldAgent.ts @@ -4,9 +4,9 @@ * 工作包 H 完成后,真实类型定义已经迁移到 rpg* 契约文件中;这里仅聚合旧命名分文件。 */ +export type * from './customWorldAgentActions'; export type * from './customWorldAgentAnchors'; export type * from './customWorldAgentDraft'; -export type * from './customWorldAgentActions'; export type * from './customWorldAgentSession'; export type * from './customWorldResultPreview'; export type * from './customWorldWorkSummary'; diff --git a/packages/shared/src/contracts/customWorldAgentActions.ts b/packages/shared/src/contracts/customWorldAgentActions.ts index a959bd0a2..ca370a261 100644 --- a/packages/shared/src/contracts/customWorldAgentActions.ts +++ b/packages/shared/src/contracts/customWorldAgentActions.ts @@ -9,6 +9,6 @@ export type { RpgAgentOperationRecord as CustomWorldAgentOperationRecord, RpgAgentOperationStatus as CustomWorldAgentOperationStatus, RpgAgentOperationType as CustomWorldAgentOperationType, - RpgAgentSupportedAction as CustomWorldSupportedAction, RpgAgentSuggestedAction as CustomWorldSuggestedAction, + RpgAgentSupportedAction as CustomWorldSupportedAction, } from './rpgAgentActions'; diff --git a/packages/shared/src/contracts/customWorldAgentSession.ts b/packages/shared/src/contracts/customWorldAgentSession.ts index 8a320a404..383281c31 100644 --- a/packages/shared/src/contracts/customWorldAgentSession.ts +++ b/packages/shared/src/contracts/customWorldAgentSession.ts @@ -6,15 +6,15 @@ export type { CreateRpgAgentSessionRequest as CreateCustomWorldAgentSessionRequest, CreateRpgAgentSessionResponse as CreateCustomWorldAgentSessionResponse, - GetRpgAgentCardDetailResponse as GetCustomWorldAgentCardDetailResponse, + RpgCreationIntentReadiness as CreatorIntentReadiness, RpgAgentMessage as CustomWorldAgentMessage, RpgAgentMessageKind as CustomWorldAgentMessageKind, RpgAgentMessageRole as CustomWorldAgentMessageRole, - RpgAgentPendingClarification as CustomWorldPendingClarification, RpgAgentQualityFinding as CustomWorldAgentQualityFinding, RpgAgentSessionSnapshot as CustomWorldAgentSessionSnapshot, RpgAgentStage as CustomWorldAgentStage, - RpgCreationIntentReadiness as CreatorIntentReadiness, + RpgAgentPendingClarification as CustomWorldPendingClarification, + GetRpgAgentCardDetailResponse as GetCustomWorldAgentCardDetailResponse, SendRpgAgentMessageRequest as SendCustomWorldAgentMessageRequest, SendRpgAgentMessageResponse as SendCustomWorldAgentMessageResponse, } from './rpgAgentSession'; diff --git a/packages/shared/src/contracts/customWorldWorkSummary.ts b/packages/shared/src/contracts/customWorldWorkSummary.ts index 62a952f9d..53061335a 100644 --- a/packages/shared/src/contracts/customWorldWorkSummary.ts +++ b/packages/shared/src/contracts/customWorldWorkSummary.ts @@ -4,8 +4,8 @@ */ export type { - ListRpgCreationWorksResponse as ListCustomWorldWorksResponse, RpgCreationWorkSource as CustomWorldWorkSource, RpgCreationWorkStatus as CustomWorldWorkStatus, RpgCreationWorkSummary as CustomWorldWorkSummary, + ListRpgCreationWorksResponse as ListCustomWorldWorksResponse, } from './rpgCreationWorkSummary'; diff --git a/packages/shared/src/contracts/hostBridge.test.ts b/packages/shared/src/contracts/hostBridge.test.ts index 1b79fb6dc..b5999566f 100644 --- a/packages/shared/src/contracts/hostBridge.test.ts +++ b/packages/shared/src/contracts/hostBridge.test.ts @@ -1,38 +1,38 @@ import { describe, expect, test } from 'vitest'; import { + HOST_BRIDGE_CAPABILITIES, + HOST_BRIDGE_DEFAULT_REQUEST_TIMEOUT_MS, + HOST_BRIDGE_DESKTOP_NETWORK_CHECK_TIMEOUT_MS, + HOST_BRIDGE_DOCUMENT_MIME_TYPES, + HOST_BRIDGE_EVENTS, HOST_BRIDGE_EXPO_MOBILE_BASE_CAPABILITIES, HOST_BRIDGE_EXPO_MOBILE_IOS_CAPABILITIES, HOST_BRIDGE_EXPO_MOBILE_IOS_EXTRA_CAPABILITIES, - HOST_BRIDGE_NATIVE_APP_QUERY, - HOST_BRIDGE_NATIVE_APP_QUERY_KEY, - HOST_BRIDGE_NATIVE_APP_QUERY_KEYS, - HOST_BRIDGE_PRESERVED_RUNTIME_CONTEXT_QUERY_KEYS, - HOST_BRIDGE_RUNTIME_CONTEXT_QUERY_KEY, - HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES, - HOST_BRIDGE_WECHAT_MINI_PROGRAM_CAPABILITIES, - HOST_BRIDGE_WECHAT_MINI_PROGRAM_SOURCE_QUERY, - HOST_BRIDGE_PUBLIC_WEB_ORIGIN, - HOST_BRIDGE_PUBLIC_WEB_URL, - HOST_BRIDGE_TAURI_COMMAND, - HOST_BRIDGE_CAPABILITIES, - HOST_BRIDGE_DOCUMENT_MIME_TYPES, - HOST_BRIDGE_DEFAULT_REQUEST_TIMEOUT_MS, - HOST_BRIDGE_DESKTOP_NETWORK_CHECK_TIMEOUT_MS, - HOST_BRIDGE_EVENTS, HOST_BRIDGE_IMPORT_DOCUMENT_MAX_BYTES, HOST_BRIDGE_LOCAL_NOTIFICATION_DELIVERED_TO_SYSTEM_RESULT, HOST_BRIDGE_MAX_REQUEST_TIMEOUT_MS, HOST_BRIDGE_MOBILE_LOCAL_NOTIFICATION_CHANNEL_ID, HOST_BRIDGE_MOBILE_WEBVIEW_BLOCKED_DOWNLOAD_PROTOCOLS, + HOST_BRIDGE_NATIVE_APP_QUERY, + HOST_BRIDGE_NATIVE_APP_QUERY_KEY, + HOST_BRIDGE_NATIVE_APP_QUERY_KEYS, + HOST_BRIDGE_PRESERVED_RUNTIME_CONTEXT_QUERY_KEYS, + HOST_BRIDGE_PUBLIC_WEB_ORIGIN, + HOST_BRIDGE_PUBLIC_WEB_URL, HOST_BRIDGE_RESPONSE_CACHE_MAX, + HOST_BRIDGE_RUNTIME_CONTEXT_QUERY_KEY, HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS, HOST_BRIDGE_SCANNER_TIMEOUT_MS, + HOST_BRIDGE_TAURI_COMMAND, + HOST_BRIDGE_TAURI_DESKTOP_CAPABILITIES, HOST_BRIDGE_TEXT_MIME_TYPES, HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS, - isHostBridgeMethod, + HOST_BRIDGE_WECHAT_MINI_PROGRAM_CAPABILITIES, + HOST_BRIDGE_WECHAT_MINI_PROGRAM_SOURCE_QUERY, isHostBridgeCapability, isHostBridgeEventName, + isHostBridgeMethod, normalizeHostBridgeAppTitle, normalizeHostBridgeBadgeCount, normalizeHostBridgeClipboardText, diff --git a/packages/shared/src/contracts/puzzleAgentActions.ts b/packages/shared/src/contracts/puzzleAgentActions.ts index bf0f92eec..ae38849b3 100644 --- a/packages/shared/src/contracts/puzzleAgentActions.ts +++ b/packages/shared/src/contracts/puzzleAgentActions.ts @@ -1,5 +1,5 @@ -import type { PuzzleAgentSessionSnapshot } from './puzzleAgentSession'; import type { ExternalGenerationJobStatusRecord } from './externalGeneration'; +import type { PuzzleAgentSessionSnapshot } from './puzzleAgentSession'; export type PuzzleAgentSuggestedActionType = | 'request_summary' diff --git a/packages/shared/src/contracts/rpgAgentSession.ts b/packages/shared/src/contracts/rpgAgentSession.ts index 0bdb8ef12..bd933b672 100644 --- a/packages/shared/src/contracts/rpgAgentSession.ts +++ b/packages/shared/src/contracts/rpgAgentSession.ts @@ -1,4 +1,4 @@ -import type { RpgAgentActionResponse, RpgAgentOperationRecord, RpgAgentSupportedAction, RpgAgentSuggestedAction } from './rpgAgentActions'; +import type { RpgAgentActionResponse, RpgAgentOperationRecord, RpgAgentSuggestedAction,RpgAgentSupportedAction } from './rpgAgentActions'; import type { RpgCreationAnchorContent } from './rpgAgentAnchors'; import type { RpgAgentAssetCoverageSummary, RpgAgentDraftCardDetail, RpgAgentDraftCardSummary } from './rpgAgentDraft'; import type { RpgCreationPreviewEnvelope } from './rpgCreationPreview'; diff --git a/packages/shared/src/contracts/rpgContracts.test.ts b/packages/shared/src/contracts/rpgContracts.test.ts index 8278d2a0b..f0f9c2f57 100644 --- a/packages/shared/src/contracts/rpgContracts.test.ts +++ b/packages/shared/src/contracts/rpgContracts.test.ts @@ -1,12 +1,12 @@ import { describe, expect, test } from 'vitest'; + import type { CustomWorldAgentSessionSnapshot } from './customWorldAgentSession'; import type { CustomWorldResultPreviewEnvelope } from './customWorldResultPreview'; import type { CustomWorldWorkSummary } from './customWorldWorkSummary'; - import { createRpgAgentFoundationDraftProfileFixture, - createRpgAgentSupportedActionsFixture, createRpgAgentSessionFixture, + createRpgAgentSupportedActionsFixture, createRpgCreationAnchorContentFixture, createRpgCreationPreviewEnvelopeFixture, createRpgCreationPublishedProfileFixture, diff --git a/packages/shared/src/contracts/rpgCreationResultView.ts b/packages/shared/src/contracts/rpgCreationResultView.ts index 853a3eeb0..1917274cc 100644 --- a/packages/shared/src/contracts/rpgCreationResultView.ts +++ b/packages/shared/src/contracts/rpgCreationResultView.ts @@ -1,5 +1,5 @@ -import type { CustomWorldProfileRecord } from './runtime'; import type { RpgAgentSessionSnapshot } from './rpgAgentSession'; +import type { CustomWorldProfileRecord } from './runtime'; export type RpgCreationResultProfileSource = | 'result_preview' diff --git a/packages/shared/src/http.ts b/packages/shared/src/http.ts index cfa89536b..bda8f6c94 100644 --- a/packages/shared/src/http.ts +++ b/packages/shared/src/http.ts @@ -20,7 +20,7 @@ export type ApiErrorCode = | 'conflict' | 'upstream_error' | 'internal_error' - | (string & {}); + | (string & Record); export type ApiErrorPayload = { code: ApiErrorCode; diff --git a/scripts/check-native-shells.mjs b/scripts/check-native-shells.mjs index e65292757..4d613b04b 100644 --- a/scripts/check-native-shells.mjs +++ b/scripts/check-native-shells.mjs @@ -1385,47 +1385,7 @@ const desktopCapabilityFlowContracts = [ ], }, ]; -const h5NativeAppRouteFlowContracts = [ - { - route: '/child-motion-demo', - label: 'child motion demo', - files: [ - 'src/components/platform-entry/PlatformEntryFlowShellImpl.tsx', - 'src/routing/appRoutes.tsx', - 'src/routing/appRoutes.test.ts', - 'src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx', - ], - snippets: [ - [ - 'src/components/platform-entry/PlatformEntryFlowShellImpl.tsx', - "navigateHostNativePage('/child-motion-demo')", - ], - [ - 'src/components/platform-entry/PlatformEntryFlowShellImpl.tsx', - "window.location.assign('/child-motion-demo')", - ], - ['src/routing/appRoutes.tsx', "normalizedPath === '/child-motion-demo'"], - ['src/routing/appRoutes.test.ts', "matchAppRoute('/child-motion-demo')"], - [ - 'src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx', - "request.method === 'navigation.openNativePage'", - ], - [ - 'src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx', - "url === '/child-motion-demo'", - ], - ], - tests: [ - 'src/routing/appRoutes.test.ts', - ], - targetedTests: [ - { - filePath: 'src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx', - name: 'native app opens child motion demo through host navigation bridge', - }, - ], - }, -]; +const h5NativeAppRouteFlowContracts = []; const expectedWechatHostBridgeFiles = [ 'dispatch.js', 'payment.js', @@ -1732,8 +1692,6 @@ const h5HostBridgeTests = [ 'src/services/clipboard.test.ts', 'src/services/appTitle.test.ts', ]; -const h5PlatformHostBridgeIntegrationTest = - 'native app jump hop draft completion sends host notification and badge from platform shell'; const h5NativeAppRouteFlowTestSteps = h5NativeAppRouteFlowContracts.flatMap( (contract) => (contract.targetedTests ?? []).map((test) => ({ @@ -1764,18 +1722,6 @@ const steps = [ command: npmCommand, args: ['run', 'test', '--', ...h5HostBridgeTests], }, - { - label: 'h5-platform-host-bridge-integration', - command: npmCommand, - args: [ - 'run', - 'test', - '--', - 'src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx', - '-t', - h5PlatformHostBridgeIntegrationTest, - ], - }, ...h5NativeAppRouteFlowTestSteps, { label: 'wechat-shell-tests', @@ -1807,16 +1753,16 @@ const steps = [ command: npmCommand, args: ['run', 'mobile-shell:export'], }, - { - label: 'desktop-shell-typecheck', - command: npmCommand, - args: ['run', 'desktop-shell:typecheck'], - }, { label: 'desktop-shell-test', command: npmCommand, args: ['run', 'desktop-shell:test'], }, + { + label: 'desktop-shell-typecheck', + command: npmCommand, + args: ['run', 'desktop-shell:typecheck'], + }, { label: 'desktop-shell-release-build-smoke', command: npmCommand, @@ -2439,7 +2385,6 @@ function assertH5HostBridgePayloadBoundaries() { 'HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS', 'HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS', 'HOST_BRIDGE_SCANNER_TIMEOUT_MS', - 'HOST_BRIDGE_APP_TITLE_MAX_LENGTH', ]) { if (!h5HostBridgeSource.includes(`${sharedBoundary},`)) { throw new Error( diff --git a/scripts/check-pingora-current-release-audit.mjs b/scripts/check-pingora-current-release-audit.mjs index dd87e6220..f03ee6cd6 100644 --- a/scripts/check-pingora-current-release-audit.mjs +++ b/scripts/check-pingora-current-release-audit.mjs @@ -1,18 +1,18 @@ #!/usr/bin/env node import { spawnSync } from 'node:child_process'; +import { createHash } from 'node:crypto'; import { chmodSync, copyFileSync, existsSync, - symlinkSync, mkdirSync, mkdtempSync, readFileSync, rmSync, + symlinkSync, writeFileSync, } from 'node:fs'; -import { createHash } from 'node:crypto'; import { tmpdir } from 'node:os'; import path from 'node:path'; diff --git a/scripts/check-pingora-cutover-evidence-verify.mjs b/scripts/check-pingora-cutover-evidence-verify.mjs index 83cc9c7b6..186809bc0 100644 --- a/scripts/check-pingora-cutover-evidence-verify.mjs +++ b/scripts/check-pingora-cutover-evidence-verify.mjs @@ -3,7 +3,6 @@ import { spawnSync } from 'node:child_process'; import { createHash } from 'node:crypto'; import { - existsSync, mkdirSync, mkdtempSync, readFileSync, diff --git a/scripts/check-pingora-direct-enable.mjs b/scripts/check-pingora-direct-enable.mjs index dcfe02110..7216283c4 100644 --- a/scripts/check-pingora-direct-enable.mjs +++ b/scripts/check-pingora-direct-enable.mjs @@ -3,8 +3,8 @@ import { spawnSync } from 'node:child_process'; import { existsSync, - mkdtempSync, mkdirSync, + mkdtempSync, readFileSync, rmSync, symlinkSync, diff --git a/scripts/check-pingora-direct-live.mjs b/scripts/check-pingora-direct-live.mjs index 1fb3c48fe..fdd392bc2 100644 --- a/scripts/check-pingora-direct-live.mjs +++ b/scripts/check-pingora-direct-live.mjs @@ -1,12 +1,12 @@ #!/usr/bin/env node +import { createHash, randomBytes } from 'node:crypto'; +import { readFile } from 'node:fs/promises'; import http from 'node:http'; import https from 'node:https'; import net from 'node:net'; -import tls from 'node:tls'; -import { createHash, randomBytes } from 'node:crypto'; -import { readFile } from 'node:fs/promises'; import nodePath from 'node:path'; +import tls from 'node:tls'; const DEFAULT_TIMEOUT_MS = 5000; const DEFAULT_SPACETIME_DATABASE = 'genarrative-prod'; diff --git a/scripts/check-pingora-direct-preflight.mjs b/scripts/check-pingora-direct-preflight.mjs index 67ddf2747..e06d75644 100644 --- a/scripts/check-pingora-direct-preflight.mjs +++ b/scripts/check-pingora-direct-preflight.mjs @@ -1,8 +1,8 @@ #!/usr/bin/env node +import { spawnSync } from 'node:child_process'; import { accessSync, constants, readFileSync } from 'node:fs'; import net from 'node:net'; -import { spawnSync } from 'node:child_process'; import { userInfo } from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; diff --git a/scripts/check-pingora-gateway-env-shadow-switch.mjs b/scripts/check-pingora-gateway-env-shadow-switch.mjs index 57a28ee43..fe28ba89e 100644 --- a/scripts/check-pingora-gateway-env-shadow-switch.mjs +++ b/scripts/check-pingora-gateway-env-shadow-switch.mjs @@ -3,7 +3,6 @@ import { spawnSync } from 'node:child_process'; import { chmodSync, - existsSync, mkdtempSync, readFileSync, rmSync, diff --git a/scripts/check-pingora-health-patrol-env-switch.mjs b/scripts/check-pingora-health-patrol-env-switch.mjs index 922664aeb..dc6fc0999 100644 --- a/scripts/check-pingora-health-patrol-env-switch.mjs +++ b/scripts/check-pingora-health-patrol-env-switch.mjs @@ -7,8 +7,8 @@ import { existsSync, mkdirSync, mkdtempSync, - realpathSync, readFileSync, + realpathSync, rmSync, statSync, symlinkSync, diff --git a/scripts/check-pingora-realpath-canary-toggle.mjs b/scripts/check-pingora-realpath-canary-toggle.mjs index 22f0e0c95..59fae8ecf 100644 --- a/scripts/check-pingora-realpath-canary-toggle.mjs +++ b/scripts/check-pingora-realpath-canary-toggle.mjs @@ -4,8 +4,8 @@ import { spawnSync } from 'node:child_process'; import { chmodSync, existsSync, - mkdtempSync, mkdirSync, + mkdtempSync, readFileSync, rmSync, writeFileSync, diff --git a/scripts/check-production-ops-guardrails.mjs b/scripts/check-production-ops-guardrails.mjs index 22843943d..bf3331dc6 100644 --- a/scripts/check-production-ops-guardrails.mjs +++ b/scripts/check-production-ops-guardrails.mjs @@ -13,11 +13,56 @@ const checks = [ includes: 'npm run check:rustfmt', reason: 'API 生产构建必须在编译前执行 Rust 格式检查。', }, + { + file: 'jenkins/Jenkinsfile.production-api-build', + includes: 'npm run check:server-rs-ddd', + reason: 'API 生产构建必须执行 server-rs DDD/schema/runtime-access 门禁。', + }, + { + file: 'jenkins/Jenkinsfile.production-api-build', + includes: 'cargo check -p api-server --all-targets --manifest-path server-rs/Cargo.toml', + reason: 'API 生产构建必须执行 api-server 全 target 编译检查。', + }, { file: 'jenkins/Jenkinsfile.production-stdb-module-build', includes: 'npm run check:rustfmt', reason: 'Stdb module 生产构建必须在编译前执行 Rust 格式检查。', }, + { + file: 'jenkins/Jenkinsfile.production-stdb-module-build', + includes: 'npm run check:server-rs-ddd', + reason: 'Stdb module 生产构建必须执行 DDD/schema/runtime-access 门禁。', + }, + { + file: 'jenkins/Jenkinsfile.production-stdb-module-build', + includes: 'npm run check:admin-account-procedures', + reason: 'Stdb module 生产构建必须执行管理员 procedure smoke。', + }, + { + file: 'jenkins/Jenkinsfile.production-web-build', + includes: 'npm run check:production-ops', + reason: 'Web 生产构建必须执行生产运维静态门禁。', + }, + { + file: 'jenkins/Jenkinsfile.production-web-build', + includes: 'npm run lint:eslint', + reason: 'Web 生产构建必须执行 ESLint 门禁。', + }, + { + file: 'jenkins/Jenkinsfile.production-web-build', + includes: 'npm run typecheck', + reason: 'Web 生产构建必须执行主站类型检查。', + }, + { + file: 'jenkins/Jenkinsfile.production-web-build', + includes: 'npm run admin-web:typecheck', + reason: 'Web 生产构建必须执行后台类型检查。', + }, + { + file: 'jenkins/Jenkinsfile.production-web-build', + includes: 'npm run test', + reason: 'Web 生产构建必须执行当前维护范围内的 Vitest。', + }, { file: 'server-rs/crates/spacetime-module/src/migration.rs', includes: diff --git a/scripts/check-spacetime-schema-guard.mjs b/scripts/check-spacetime-schema-guard.mjs index e3042ca2a..c34fbbf38 100644 --- a/scripts/check-spacetime-schema-guard.mjs +++ b/scripts/check-spacetime-schema-guard.mjs @@ -481,9 +481,9 @@ function getChangedFiles(baseRef) { tryGit(['ls-files', '--others', '--exclude-standard', '-z', bindingsRoot]) ?? ''; return new Set( [ - ...diffOutput.split(/\u0000/u), - ...untrackedModuleOutput.split(/\u0000/u), - ...untrackedBindingsOutput.split(/\u0000/u), + ...diffOutput.split(String.fromCharCode(0)), + ...untrackedModuleOutput.split(String.fromCharCode(0)), + ...untrackedBindingsOutput.split(String.fromCharCode(0)), ] .map(normalizePath) .filter(Boolean), @@ -575,7 +575,7 @@ function compareTables(baseTables, currentTables) { } } - for (const [accessor, table] of currentTables) { + for (const accessor of currentTables.keys()) { if (!baseTables.has(accessor)) { schemaChanged = true; } diff --git a/scripts/check-visual-novel-vn11-negative-scan.mjs b/scripts/check-visual-novel-vn11-negative-scan.mjs index d26f3265a..f482d6af2 100644 --- a/scripts/check-visual-novel-vn11-negative-scan.mjs +++ b/scripts/check-visual-novel-vn11-negative-scan.mjs @@ -180,10 +180,6 @@ function hasExternalPlatformMarker(line) { return externalPlatformPatterns.some((pattern) => pattern.test(line)); } -function formatHits(hits) { - return hits.map((hit) => ` - ${hit.file}:${hit.lineNumber} ${hit.text}`); -} - const documentFiles = collectFiles(documentTargets); const visualNovelFiles = collectFiles(visualNovelImplementationTargets); diff --git a/scripts/deploy/pingora-gateway-env-shadow-switch.mjs b/scripts/deploy/pingora-gateway-env-shadow-switch.mjs index ab7ba188e..aede1700b 100644 --- a/scripts/deploy/pingora-gateway-env-shadow-switch.mjs +++ b/scripts/deploy/pingora-gateway-env-shadow-switch.mjs @@ -1,8 +1,8 @@ #!/usr/bin/env node import { - chownSync, chmodSync, + chownSync, lstatSync, mkdtempSync, readFileSync, diff --git a/scripts/deploy/pingora-health-patrol-env-switch.mjs b/scripts/deploy/pingora-health-patrol-env-switch.mjs index 60f5df19a..dd09c41d8 100644 --- a/scripts/deploy/pingora-health-patrol-env-switch.mjs +++ b/scripts/deploy/pingora-health-patrol-env-switch.mjs @@ -2,8 +2,8 @@ import { spawnSync } from 'node:child_process'; import { - chownSync, chmodSync, + chownSync, lstatSync, mkdtempSync, readFileSync, diff --git a/scripts/deploy/pingora-tls-cert-sync.mjs b/scripts/deploy/pingora-tls-cert-sync.mjs index 63533eea7..569de5c00 100644 --- a/scripts/deploy/pingora-tls-cert-sync.mjs +++ b/scripts/deploy/pingora-tls-cert-sync.mjs @@ -1,5 +1,6 @@ #!/usr/bin/env node +import { execFileSync, spawnSync } from 'node:child_process'; import { randomBytes } from 'node:crypto'; import { accessSync, @@ -15,9 +16,8 @@ import { rmSync, statSync, } from 'node:fs'; -import { execFileSync, spawnSync } from 'node:child_process'; -import path from 'node:path'; import { userInfo } from 'node:os'; +import path from 'node:path'; const DEFAULT_SERVICE_USER = 'genarrative'; const DEFAULT_SERVICE_GROUP = 'genarrative'; diff --git a/scripts/deploy/production-runtime-writer-identity-rotate.mjs b/scripts/deploy/production-runtime-writer-identity-rotate.mjs index 29ffc2f6d..44489533b 100644 --- a/scripts/deploy/production-runtime-writer-identity-rotate.mjs +++ b/scripts/deploy/production-runtime-writer-identity-rotate.mjs @@ -1,11 +1,12 @@ #!/usr/bin/env node +import { spawn } from 'node:child_process'; + import { callSpacetimeProcedureViaCli, ensureProcedureOk, validateSpacetimeDatabaseName, } from '../spacetime-migration-common.mjs'; -import { spawn } from 'node:child_process'; const PROCEDURE = 'rotate_editor_generation_runtime_service_identity_and_return'; diff --git a/scripts/dev-stack-port-utils.mjs b/scripts/dev-stack-port-utils.mjs index c119ea46e..d4f5cdd0f 100644 --- a/scripts/dev-stack-port-utils.mjs +++ b/scripts/dev-stack-port-utils.mjs @@ -8,9 +8,9 @@ import { rmSync, writeFileSync, } from 'node:fs'; +import {createServer} from 'node:net'; import {hostname, userInfo} from 'node:os'; import {dirname, resolve} from 'node:path'; -import {createServer} from 'node:net'; const LINUX_DEV_PORT_RANGE_REGISTRY_ROOT = '/var/tmp/genarrative-dev-port-ranges'; const LINUX_DEV_PORT_RANGE_POOL_START = 10000; @@ -308,7 +308,7 @@ function nextLinuxPortRangeCandidate(registry) { async function withLinuxPortRangeRegistryLock(lockPath, action) { ensureWorldWritableDirectory(dirname(lockPath)); - while (true) { + for (;;) { try { writeFileSync( lockPath, diff --git a/scripts/dev-stack-port-utils.test.ts b/scripts/dev-stack-port-utils.test.ts index 353b7a100..8f56402f7 100644 --- a/scripts/dev-stack-port-utils.test.ts +++ b/scripts/dev-stack-port-utils.test.ts @@ -1,8 +1,10 @@ -import {createServer} from 'node:net'; import {mkdtempSync, readFileSync, rmSync} from 'node:fs'; +import {createServer} from 'node:net'; import {tmpdir} from 'node:os'; import {join} from 'node:path'; + import {describe, expect, it} from 'vitest'; + import { findAvailablePort, mapDevPortsToPortRange, @@ -23,6 +25,27 @@ function reservePort(port) { }); } +async function reserveConsecutivePorts() { + for (let attempt = 0; attempt < 20; attempt += 1) { + const firstServer = await reservePort(0); + const firstPort = firstServer.address().port; + + if (firstPort === 65535) { + await new Promise((resolve) => firstServer.close(resolve)); + continue; + } + + try { + const secondServer = await reservePort(firstPort + 1); + return {firstServer, secondServer, firstPort}; + } catch { + await new Promise((resolve) => firstServer.close(resolve)); + } + } + + throw new Error('无法为测试保留两个连续端口'); +} + describe('dev stack port utils', () => { it('解析端口段并映射到四个 dev 端口', () => { expect(parsePortRangeSpec('10000-10099')).toEqual({ @@ -39,9 +62,7 @@ describe('dev stack port utils', () => { }); it('使用端口可用性检查为被占用端口寻找后续可用端口', async () => { - const firstServer = await reservePort(0); - const firstPort = firstServer.address().port; - const secondServer = await reservePort(firstPort + 1); + const {firstServer, secondServer, firstPort} = await reserveConsecutivePorts(); try { const availablePort = await findAvailablePort({ diff --git a/scripts/dev.test.ts b/scripts/dev.test.ts index 988d21bf9..b6ebbac90 100644 --- a/scripts/dev.test.ts +++ b/scripts/dev.test.ts @@ -945,6 +945,7 @@ spacetimedb tool version 2.6.0; spacetimedb-lib version 2.6.0; }); test('本地 API identity 不跟随符号链接记录', async () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); const tempDir = mkdtempSync(join(tmpdir(), 'genarrative-api-identity-')); try { const { explicitOptions, options } = parseArgs( @@ -988,6 +989,9 @@ spacetimedb tool version 2.6.0; spacetimedb-lib version 2.6.0; identity: 'linked-identity', token: 'linked-token', }); + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining('本地 API identity 记录不可用,将重新创建'), + ); } finally { rmSync(tempDir, { recursive: true, force: true }); } @@ -1165,6 +1169,7 @@ spacetimedb tool version 2.6.0; spacetimedb-lib version 2.6.0; }); test('不复用权限宽松、格式非法或空的本地运行服务 bootstrap secret 记录', () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); const tempDir = mkdtempSync( join(tmpdir(), 'genarrative-runtime-bootstrap-secret-'), ); @@ -1234,6 +1239,11 @@ spacetimedb tool version 2.6.0; spacetimedb-lib version 2.6.0; if (process.platform !== 'win32') { expect(statSync(secretPath).mode & 0o777).toBe(0o600); } + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining( + '本地运行服务 bootstrap secret 记录不可用,将重新生成', + ), + ); } finally { rmSync(tempDir, { recursive: true, force: true }); } diff --git a/scripts/export-match3d-resource-pipeline.mjs b/scripts/export-match3d-resource-pipeline.mjs index 93f7839cb..48b597e52 100644 --- a/scripts/export-match3d-resource-pipeline.mjs +++ b/scripts/export-match3d-resource-pipeline.mjs @@ -1,7 +1,7 @@ import {Buffer} from 'node:buffer'; -import {existsSync, mkdirSync, readFileSync, writeFileSync} from 'node:fs'; -import path from 'node:path'; import {spawnSync} from 'node:child_process'; +import { mkdirSync, readFileSync, writeFileSync} from 'node:fs'; +import path from 'node:path'; import {mergeApiServerEnv} from './dev-utils.mjs'; diff --git a/scripts/ops/production-health-patrol.mjs b/scripts/ops/production-health-patrol.mjs index 60eab89f9..14b2dd2b3 100644 --- a/scripts/ops/production-health-patrol.mjs +++ b/scripts/ops/production-health-patrol.mjs @@ -1,9 +1,9 @@ #!/usr/bin/env node import { execFile } from 'node:child_process'; +import { mkdir, writeFile } from 'node:fs/promises'; import http from 'node:http'; import https from 'node:https'; -import { mkdir, writeFile } from 'node:fs/promises'; import { dirname } from 'node:path'; const STATUS_RANK = { diff --git a/scripts/rebind-orphan-work-owners.test.ts b/scripts/rebind-orphan-work-owners.test.ts index b57335860..736279d3c 100644 --- a/scripts/rebind-orphan-work-owners.test.ts +++ b/scripts/rebind-orphan-work-owners.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { rebindOrphanWorkOwnersInMigration } from './rebind-orphan-work-owners.mjs'; const placeholderUserId = 'wx-openid-placeholder'; diff --git a/scripts/repair-auth-public-user-codes.test.ts b/scripts/repair-auth-public-user-codes.test.ts index 6273e77d6..b9414ee8d 100644 --- a/scripts/repair-auth-public-user-codes.test.ts +++ b/scripts/repair-auth-public-user-codes.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { repairAuthPublicUserCodesInMigration } from './repair-auth-public-user-codes.mjs'; function table(name: string, rows: unknown[]) { diff --git a/scripts/run-bash-script.mjs b/scripts/run-bash-script.mjs index a18812145..6aec0ea5b 100644 --- a/scripts/run-bash-script.mjs +++ b/scripts/run-bash-script.mjs @@ -1,5 +1,5 @@ -import {existsSync} from 'node:fs'; import {spawn} from 'node:child_process'; +import {existsSync} from 'node:fs'; const [, , scriptPath, ...scriptArgs] = process.argv; diff --git a/scripts/smoke-content.ts b/scripts/smoke-content.ts index 0929a65da..ae1dd814d 100644 --- a/scripts/smoke-content.ts +++ b/scripts/smoke-content.ts @@ -1,4 +1,4 @@ -import { buildCompanionState, ROLE_TEMPLATE_CHARACTERS, resolveEncounterRecruitCharacter } from '../src/data/characterPresets.ts'; +import { buildCompanionState, resolveEncounterRecruitCharacter,ROLE_TEMPLATE_CHARACTERS } from '../src/data/characterPresets.ts'; import { activateRosterCompanion, benchActiveCompanion, recruitCompanionToParty } from '../src/data/companionRoster.ts'; import { getInventoryItemValue, getNpcPurchasePrice } from '../src/data/economy.ts'; import { diff --git a/scripts/spacetime-export-migration-json.mjs b/scripts/spacetime-export-migration-json.mjs index 4837e115b..94a5912c5 100644 --- a/scripts/spacetime-export-migration-json.mjs +++ b/scripts/spacetime-export-migration-json.mjs @@ -2,6 +2,7 @@ import { writeFile } from 'node:fs/promises'; import path from 'node:path'; + import { callSpacetimeProcedure, callSpacetimeProcedureViaCli, diff --git a/scripts/spacetime-import-migration-json.mjs b/scripts/spacetime-import-migration-json.mjs index 8b46fa16e..aa9d14653 100644 --- a/scripts/spacetime-import-migration-json.mjs +++ b/scripts/spacetime-import-migration-json.mjs @@ -3,6 +3,7 @@ import { randomUUID } from 'node:crypto'; import { readFile } from 'node:fs/promises'; import path from 'node:path'; + import { assertReadableFile, callSpacetimeProcedure, diff --git a/scripts/spacetime-logs-local.sh b/scripts/spacetime-logs-local.sh index 9502ba0a4..0ca7f8592 100644 --- a/scripts/spacetime-logs-local.sh +++ b/scripts/spacetime-logs-local.sh @@ -13,7 +13,7 @@ usage() { 1. 从本地 SpacetimeDB 通过 spacetime logs 提取模块日志到本地文件。 2. 默认读取 spacetime.local.json 的 database 字段,默认 server 为 http://127.0.0.1:3101。 3. 默认输出到 logs/spacetime/-.log;--follow 会持续追加并同步写到终端。 - 4. 默认使用 server-rs/.spacetimedb/local 作为 spacetime CLI root,保持本地身份与 standalone 一致。 + 4. 始终显式传入 server URL,不读取或改写 spacetime CLI 的个人 root 配置。 EOF } @@ -52,7 +52,6 @@ REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)" DATABASE="" SPACETIME_SERVER="http://127.0.0.1:3101" -SPACETIME_ROOT_DIR="${REPO_ROOT}/server-rs/.spacetimedb/local" LINES="200" OUTPUT="" FOLLOW=0 @@ -71,10 +70,6 @@ while [[ $# -gt 0 ]]; do SPACETIME_SERVER="${2:?缺少 --server 的值}" shift 2 ;; - --spacetime-root-dir) - SPACETIME_ROOT_DIR="${2:?缺少 --spacetime-root-dir 的值}" - shift 2 - ;; --lines|-n) LINES="${2:?缺少 --lines 的值}" shift 2 @@ -123,7 +118,6 @@ fi echo "[stdb:logs] database: ${DATABASE}" echo "[stdb:logs] server: ${SPACETIME_SERVER}" -echo "[stdb:logs] spacetime root: ${SPACETIME_ROOT_DIR}" echo "[stdb:logs] output: ${OUTPUT}" -spacetime --root-dir="${SPACETIME_ROOT_DIR}" "${ARGS[@]}" | tee -a "${OUTPUT}" +spacetime "${ARGS[@]}" | tee -a "${OUTPUT}" diff --git a/scripts/spacetime-migration-common.test.ts b/scripts/spacetime-migration-common.test.ts index 02219e50b..1e14f7c6f 100644 --- a/scripts/spacetime-migration-common.test.ts +++ b/scripts/spacetime-migration-common.test.ts @@ -1,4 +1,5 @@ import { describe, expect, it } from 'vitest'; + import { buildSpacetimeCallArgs, encodeSpacetimeCliOption, diff --git a/scripts/test-ve-llm.mjs b/scripts/test-ve-llm.mjs index d2e65f06a..a9f2b848e 100644 --- a/scripts/test-ve-llm.mjs +++ b/scripts/test-ve-llm.mjs @@ -1,5 +1,5 @@ import { readFileSync } from 'node:fs'; -import { resolve, dirname } from 'node:path'; +import { dirname,resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); @@ -52,7 +52,9 @@ async function test(name, method, path, body = null) { const elapsed = Date.now() - start; const text = await resp.text(); let json = null; - try { json = JSON.parse(text); } catch {} + try { json = JSON.parse(text); } catch { + // 非 JSON 上游错误由下方原始文本分支处理。 + } if (resp.ok) { const model = json?.model || json?.data?.[0]?.id || '?'; diff --git a/server-rs/README.md b/server-rs/README.md index 661f13bb4..02f8a1cb1 100644 --- a/server-rs/README.md +++ b/server-rs/README.md @@ -30,10 +30,10 @@ cargo test -p api-server --manifest-path server-rs/Cargo.toml npm run check:server-rs-ddd npm run check:spacetime-schema npm run spacetime:generate -npm run api-server +npm run dev:api-server ``` -涉及 API smoke 时用 `npm run api-server` 启动后端并检查 `/healthz`,不要使用旧 `api-server:maincloud`。 +涉及 API smoke 时用 `npm run dev:api-server` 启动后端并检查 `/healthz`,不要使用旧 `api-server:maincloud`。 ## 当前文档 diff --git a/server-rs/crates/api-server/README.md b/server-rs/crates/api-server/README.md index 2a1bba8c9..42f3336e4 100644 --- a/server-rs/crates/api-server/README.md +++ b/server-rs/crates/api-server/README.md @@ -99,7 +99,7 @@ 1. 进程启动时通过 `shared-logging` 统一初始化 `tracing subscriber`。 2. 默认日志过滤器来自 `GENARRATIVE_API_LOG`,未提供时回落到 `info,tower_http=info`。 3. HTTP 访问日志统一通过 Axum 路由层的 `TraceLayer` 输出,后续 `request_id`、响应头与错误中间件继续在同一层扩展。 -4. 本地启动器 `npm run api-server` 和完整联调入口 `npm run dev` / `npm run dev:rust` 会在保留终端实时输出的同时,把同一份 `cargo` / `api-server` 输出持久化到 `logs/api-server/`。如需固定文件或目录,可设置 `GENARRATIVE_API_SERVER_LOG_FILE` 或 `GENARRATIVE_API_SERVER_LOG_DIR`。 +4. 本地启动器 `npm run dev:api-server` 和完整联调入口 `npm run dev` 会在保留终端实时输出的同时,把同一份 `cargo` / `api-server` 输出持久化到 `logs/api-server/`。如需固定文件或目录,可设置 `GENARRATIVE_API_SERVER_LOG_FILE` 或 `GENARRATIVE_API_SERVER_LOG_DIR`。 当前 request context 约定: diff --git a/server-rs/crates/api-server/src/app.rs b/server-rs/crates/api-server/src/app.rs index 0424aa252..a148df426 100644 --- a/server-rs/crates/api-server/src/app.rs +++ b/server-rs/crates/api-server/src/app.rs @@ -2011,10 +2011,7 @@ mod tests { payload["user"]["loginMethod"], Value::String("password".to_string()) ); - assert_eq!( - payload["user"]["createdAt"], - Value::String(seed_user.created_at) - ); + assert!(payload["user"].get("createdAt").is_none()); assert!(payload["token"].as_str().is_some()); } @@ -2294,7 +2291,7 @@ mod tests { payload["user"]["phoneNumberMasked"], Value::String("138****8000".to_string()) ); - assert!(payload["user"]["createdAt"].as_str().is_some()); + assert!(payload["user"].get("createdAt").is_none()); assert_eq!(payload["created"], Value::Bool(true)); assert!(payload["referral"].is_null()); } diff --git a/server-rs/crates/api-server/src/assets.rs b/server-rs/crates/api-server/src/assets.rs index dfc6d90f0..16113daaf 100644 --- a/server-rs/crates/api-server/src/assets.rs +++ b/server-rs/crates/api-server/src/assets.rs @@ -1217,13 +1217,17 @@ mod tests { #[tokio::test] async fn direct_upload_ticket_returns_service_unavailable_when_oss_missing() { - let app = build_router(AppState::new(AppConfig::default()).expect("state should build")); + let state = AppState::new(AppConfig::default()).expect("state should build"); + let token = + seed_authenticated_token(&state, "13800138121", "sess_assets_missing_oss").await; + let app = build_router(state); let response = app .oneshot( Request::builder() .method("POST") .uri("/api/assets/direct-upload-tickets") + .header("authorization", format!("Bearer {token}")) .header("content-type", "application/json") .body(Body::from( json!({ @@ -1490,13 +1494,17 @@ mod tests { #[tokio::test] async fn sts_upload_credentials_are_disabled_for_browser_writes() { - let app = build_router(AppState::new(AppConfig::default()).expect("state should build")); + let state = AppState::new(AppConfig::default()).expect("state should build"); + let token = + seed_authenticated_token(&state, "13800138122", "sess_assets_sts_disabled").await; + let app = build_router(state); let response = app .oneshot( Request::builder() .method("POST") .uri("/api/assets/sts-upload-credentials") + .header("authorization", format!("Bearer {token}")) .header("x-genarrative-response-envelope", "1") .body(Body::empty()) .expect("request should build"), @@ -1525,13 +1533,18 @@ mod tests { #[tokio::test] async fn confirm_asset_object_returns_service_unavailable_when_oss_missing() { - let app = build_router(AppState::new(AppConfig::default()).expect("state should build")); + let state = AppState::new(AppConfig::default()).expect("state should build"); + let token = + seed_authenticated_token(&state, "13800138123", "sess_assets_confirm_missing_oss") + .await; + let app = build_router(state); let response = app .oneshot( Request::builder() .method("POST") .uri("/api/assets/objects/confirm") + .header("authorization", format!("Bearer {token}")) .header("content-type", "application/json") .body(Body::from( json!({ @@ -1558,13 +1571,17 @@ mod tests { ..AppConfig::default() }; - let app = build_router(AppState::new(config).expect("state should build")); + let state = AppState::new(config).expect("state should build"); + let token = + seed_authenticated_token(&state, "13800138124", "sess_assets_bucket_mismatch").await; + let app = build_router(state); let response = app .oneshot( Request::builder() .method("POST") .uri("/api/assets/objects/confirm") + .header("authorization", format!("Bearer {token}")) .header("content-type", "application/json") .body(Body::from( json!({ @@ -1588,13 +1605,17 @@ mod tests { #[tokio::test] async fn bind_asset_object_rejects_missing_slot_before_calling_spacetime() { - let app = build_router(AppState::new(AppConfig::default()).expect("state should build")); + let state = AppState::new(AppConfig::default()).expect("state should build"); + let token = + seed_authenticated_token(&state, "13800138125", "sess_assets_missing_slot").await; + let app = build_router(state); let response = app .oneshot( Request::builder() .method("POST") .uri("/api/assets/objects/bind") + .header("authorization", format!("Bearer {token}")) .header("content-type", "application/json") .body(Body::from( json!({ diff --git a/server-rs/crates/api-server/src/runtime_inventory.rs b/server-rs/crates/api-server/src/runtime_inventory.rs index 53c0e29d2..9aa2c5d4d 100644 --- a/server-rs/crates/api-server/src/runtime_inventory.rs +++ b/server-rs/crates/api-server/src/runtime_inventory.rs @@ -126,8 +126,7 @@ mod tests { #[tokio::test] async fn runtime_inventory_returns_bad_gateway_when_spacetime_not_published() { - let state = seed_authenticated_state().await; - let token = issue_access_token(&state); + let (state, token) = seed_authenticated_state().await; let app = build_router(state); let response = app @@ -161,35 +160,29 @@ mod tests { ); } - async fn seed_authenticated_state() -> AppState { + async fn seed_authenticated_state() -> (AppState, String) { let state = AppState::new(AppConfig::default()).expect("state should build"); - state + let user = state .seed_test_phone_user_with_password("13800138103", "secret123") - .await - .id; - state - } - - fn issue_access_token(state: &AppState) -> String { + .await; let claims = AccessTokenClaims::from_input( AccessTokenClaimsInput { - user_id: "user_00000001".to_string(), - session_id: state.seed_test_refresh_session_for_user_id( - "user_00000001", - "sess_runtime_inventory", - ), + user_id: user.id.clone(), + session_id: state + .seed_test_refresh_session_for_user(&user, "sess_runtime_inventory"), provider: AuthProvider::Password, roles: vec!["user".to_string()], - token_version: 2, + token_version: user.token_version, phone_verified: true, binding_status: BindingStatus::Active, - display_name: Some("背包查询用户".to_string()), + display_name: Some(user.display_name.clone()), }, state.auth_jwt_config(), OffsetDateTime::now_utc(), ) .expect("claims should build"); - sign_access_token(&claims, state.auth_jwt_config()).expect("token should sign") + let token = sign_access_token(&claims, state.auth_jwt_config()).expect("token should sign"); + (state, token) } } diff --git a/server-rs/crates/api-server/src/tracking_outbox.rs b/server-rs/crates/api-server/src/tracking_outbox.rs index 5c1e29b7b..0f85a68c9 100644 --- a/server-rs/crates/api-server/src/tracking_outbox.rs +++ b/server-rs/crates/api-server/src/tracking_outbox.rs @@ -123,6 +123,7 @@ impl TrackingOutbox { .as_mut() .expect("active file should be open before append"); file.write_all(&line).await?; + file.flush().await?; inner.active_count = inner.active_count.saturating_add(1); inner.active_bytes = inner.active_bytes.saturating_add(line_bytes); inner.total_bytes = inner.total_bytes.saturating_add(line_bytes); @@ -571,6 +572,19 @@ mod tests { let _ = std::fs::remove_dir_all(dir); } + #[tokio::test] + async fn enqueue_flushes_active_file_before_success() { + let dir = test_dir("flush-before-success"); + let outbox = test_outbox(dir.clone(), 500, 1024 * 1024); + + outbox.enqueue(sample_event("event-1")).await.unwrap(); + + let active_contents = std::fs::read_to_string(dir.join(ACTIVE_FILE_NAME)).unwrap(); + assert!(active_contents.contains("event-1")); + + let _ = std::fs::remove_dir_all(dir); + } + #[tokio::test] async fn enqueue_drops_when_outbox_exceeds_max_bytes() { let dir = test_dir("max-bytes"); diff --git a/server-rs/scripts/spacetime-dev.ps1 b/server-rs/scripts/spacetime-dev.ps1 index b6f6b8ab9..125c3293c 100644 --- a/server-rs/scripts/spacetime-dev.ps1 +++ b/server-rs/scripts/spacetime-dev.ps1 @@ -4,7 +4,7 @@ param( [switch]$Help, [string]$ListenHost = "127.0.0.1", [int]$Port = 3000, - [string]$RootDir = "" + [string]$DataDir = "" ) $ErrorActionPreference = "Stop" @@ -31,8 +31,8 @@ if ($Help) { $scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path $serverRsDir = Split-Path -Parent $scriptDir -if ([string]::IsNullOrWhiteSpace($RootDir)) { - $RootDir = Join-Path $serverRsDir ".spacetimedb\local" +if ([string]::IsNullOrWhiteSpace($DataDir)) { + $DataDir = Join-Path $serverRsDir ".spacetimedb\local\data" } if (-not (Test-Path (Join-Path $serverRsDir "crates\spacetime-module\README.md"))) { @@ -49,19 +49,19 @@ if ($null -eq $spacetimeCommand) { ) -join " " } -New-Item -ItemType Directory -Force -Path $RootDir | Out-Null +New-Item -ItemType Directory -Force -Path $DataDir | Out-Null $listenAddress = "$ListenHost`:$Port" Write-Host "[server-rs:spacetime-dev] working dir: $serverRsDir" -Write-Host "[server-rs:spacetime-dev] root dir: $RootDir" +Write-Host "[server-rs:spacetime-dev] data dir: $DataDir" Write-Host "[server-rs:spacetime-dev] listen addr: $listenAddress" Write-Host "[server-rs:spacetime-dev] mode: standalone" Write-Host "[server-rs:spacetime-dev] note: module scaffold already exists; publish remains manual in this stage" Push-Location $serverRsDir try { - & $spacetimeCommand.Source --root-dir $RootDir start --edition standalone --listen-addr $listenAddress + & $spacetimeCommand.Source start --data-dir $DataDir --listen-addr $listenAddress --non-interactive } finally { Pop-Location diff --git a/server-rs/scripts/spacetime-dev.sh b/server-rs/scripts/spacetime-dev.sh index 4dcd01d1c..c442f8e45 100644 --- a/server-rs/scripts/spacetime-dev.sh +++ b/server-rs/scripts/spacetime-dev.sh @@ -27,7 +27,7 @@ SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)" SERVER_RS_DIR="$(cd -- "${SCRIPT_DIR}/.." && pwd)" LISTEN_HOST="${GENARRATIVE_SPACETIME_HOST:-127.0.0.1}" PORT="${GENARRATIVE_SPACETIME_PORT:-3000}" -ROOT_DIR="${GENARRATIVE_SPACETIME_ROOT_DIR:-${SERVER_RS_DIR}/.spacetimedb/local}" +DATA_DIR="${GENARRATIVE_SPACETIME_DATA_DIR:-${SERVER_RS_DIR}/.spacetimedb/local/data}" if [[ ! -f "${SERVER_RS_DIR}/crates/spacetime-module/README.md" ]]; then echo "[server-rs:spacetime-dev] 未找到 crates/spacetime-module/README.md,无法启动本地 SpacetimeDB 脚本。" >&2 @@ -41,13 +41,13 @@ if ! command -v spacetime >/dev/null 2>&1; then exit 1 fi -mkdir -p "${ROOT_DIR}" +mkdir -p "${DATA_DIR}" echo "[server-rs:spacetime-dev] 工作目录: ${SERVER_RS_DIR}" -echo "[server-rs:spacetime-dev] 数据目录: ${ROOT_DIR}" +echo "[server-rs:spacetime-dev] 数据目录: ${DATA_DIR}" echo "[server-rs:spacetime-dev] 监听地址: ${LISTEN_HOST}:${PORT}" echo "[server-rs:spacetime-dev] 模式: standalone" echo "[server-rs:spacetime-dev] 说明: 当前阶段已落 crate scaffold,但仍不自动 publish crates/spacetime-module" cd "${SERVER_RS_DIR}" -spacetime --root-dir "${ROOT_DIR}" start --edition standalone --listen-addr "${LISTEN_HOST}:${PORT}" +spacetime start --data-dir "${DATA_DIR}" --listen-addr "${LISTEN_HOST}:${PORT}" --non-interactive diff --git a/src/App.tsx b/src/App.tsx index 9a11611d8..571ca2ad3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -21,8 +21,8 @@ import { isAppHistoryState, normalizeAppPath, pushAppHistoryPath, - replaceAppHistoryPath, readPublicWorkCodeFromLocationSearch, + replaceAppHistoryPath, resolveInitialSelectionStageFromPath, resolvePathForSelectionStage, shouldRedirectEditorCanvasWithoutProject, diff --git a/src/PuzzlePlaygroundApp.tsx b/src/PuzzlePlaygroundApp.tsx index 429b0761d..a50ae9d6d 100644 --- a/src/PuzzlePlaygroundApp.tsx +++ b/src/PuzzlePlaygroundApp.tsx @@ -7,8 +7,8 @@ import type { import type { PuzzleWorkSummary } from '../packages/shared/src/contracts/puzzleWorkSummary'; import { PuzzleRuntimeShell } from './components/puzzle-runtime/PuzzleRuntimeShell'; import { - applyLocalPuzzleFreezeTime, advanceLocalPuzzleLevel, + applyLocalPuzzleFreezeTime, dragLocalPuzzlePiece, setLocalPuzzlePaused, startLocalPuzzleRun, diff --git a/src/components/AdventureEntityModal.tsx b/src/components/AdventureEntityModal.tsx index 39dc79701..c56d6b77c 100644 --- a/src/components/AdventureEntityModal.tsx +++ b/src/components/AdventureEntityModal.tsx @@ -77,8 +77,8 @@ import { PlayerLevelProgress, StatusRow, } from './CharacterInfoShared'; -import { PlatformEmptyState } from './common/PlatformEmptyState'; import { PlatformActionButton } from './common/PlatformActionButton'; +import { PlatformEmptyState } from './common/PlatformEmptyState'; import { PlatformPillBadge } from './common/PlatformPillBadge'; import { PlatformSubpanel } from './common/PlatformSubpanel'; import { GENERIC_NPC_SCENE_SCALE } from './game-canvas/GameCanvasShared'; diff --git a/src/components/CustomWorldGenerationView.test.tsx b/src/components/CustomWorldGenerationView.test.tsx index 54557e3a5..f8a2abfc5 100644 --- a/src/components/CustomWorldGenerationView.test.tsx +++ b/src/components/CustomWorldGenerationView.test.tsx @@ -1,7 +1,7 @@ /* @vitest-environment jsdom */ -import userEvent from '@testing-library/user-event'; import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { describe, expect, test, vi } from 'vitest'; import type { CustomWorldGenerationProgress } from '../../packages/shared/src/contracts/runtime'; diff --git a/src/components/InventoryPanel.tsx b/src/components/InventoryPanel.tsx index 900a1a2b6..d2bdc8bc9 100644 --- a/src/components/InventoryPanel.tsx +++ b/src/components/InventoryPanel.tsx @@ -14,8 +14,8 @@ import { NarrativeQaReport, WorldType, } from '../types'; -import { PlatformPillBadge } from './common/PlatformPillBadge'; import { PlatformActionButton } from './common/PlatformActionButton'; +import { PlatformPillBadge } from './common/PlatformPillBadge'; import { PlatformStatusMessage } from './common/PlatformStatusMessage'; import { PlatformSubpanel } from './common/PlatformSubpanel'; import { diff --git a/src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx b/src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx index 56358b6a3..8d4eb4df0 100644 --- a/src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx +++ b/src/components/bark-battle-creation/BarkBattleGeneratingView.test.tsx @@ -1,7 +1,7 @@ /* @vitest-environment jsdom */ -import userEvent from '@testing-library/user-event'; import { render, screen, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; import { describe, expect, it, vi } from 'vitest'; import { diff --git a/src/components/big-fish-result/BigFishResultView.tsx b/src/components/big-fish-result/BigFishResultView.tsx index 1bc3336e1..41024366f 100644 --- a/src/components/big-fish-result/BigFishResultView.tsx +++ b/src/components/big-fish-result/BigFishResultView.tsx @@ -20,7 +20,6 @@ import { PlatformAcknowledgeStatusDialog } from '../common/PlatformAcknowledgeSt import { PlatformActionButton } from '../common/PlatformActionButton'; import { PlatformEmptyState } from '../common/PlatformEmptyState'; import { PlatformFieldLabel } from '../common/PlatformFieldLabel'; -import { PlatformIconBadge } from '../common/PlatformIconBadge'; import { PlatformIconButton } from '../common/PlatformIconButton'; import { PlatformMediaFrame } from '../common/PlatformMediaFrame'; import { PlatformPillBadge } from '../common/PlatformPillBadge'; diff --git a/src/components/common/CopyFeedbackButton.tsx b/src/components/common/CopyFeedbackButton.tsx index d88a57509..5e0b7c6a1 100644 --- a/src/components/common/CopyFeedbackButton.tsx +++ b/src/components/common/CopyFeedbackButton.tsx @@ -1,13 +1,13 @@ import { Check, Copy } from 'lucide-react'; import type { ButtonHTMLAttributes, ReactNode } from 'react'; +import { PlatformActionButton } from './PlatformActionButton'; import { - type PlatformActionButtonSize, type PlatformActionButtonShape, + type PlatformActionButtonSize, type PlatformActionButtonSurface, type PlatformActionButtonTone, } from './platformActionButtonModel'; -import { PlatformActionButton } from './PlatformActionButton'; import { getPlatformPillBadgeClassName, type PlatformPillBadgeSize, diff --git a/src/components/common/PlatformBackActionButton.tsx b/src/components/common/PlatformBackActionButton.tsx index 649903f71..8ed182332 100644 --- a/src/components/common/PlatformBackActionButton.tsx +++ b/src/components/common/PlatformBackActionButton.tsx @@ -1,6 +1,5 @@ -import type { ButtonHTMLAttributes } from 'react'; - import { ArrowLeft } from 'lucide-react'; +import type { ButtonHTMLAttributes } from 'react'; import { PlatformActionButton } from './PlatformActionButton'; import type { PlatformActionButtonSurface } from './platformActionButtonModel'; diff --git a/src/components/common/PlatformIconButton.tsx b/src/components/common/PlatformIconButton.tsx index 4cf90c7f9..106141262 100644 --- a/src/components/common/PlatformIconButton.tsx +++ b/src/components/common/PlatformIconButton.tsx @@ -1,4 +1,3 @@ -import { forwardRef } from 'react'; import type { ButtonHTMLAttributes, HTMLAttributes, @@ -7,6 +6,7 @@ import type { ReactNode, Ref, } from 'react'; +import { forwardRef } from 'react'; type PlatformIconButtonBaseProps = { label: string; diff --git a/src/components/common/PlatformInlineOptionButton.tsx b/src/components/common/PlatformInlineOptionButton.tsx index bc349d4e2..06eee0af4 100644 --- a/src/components/common/PlatformInlineOptionButton.tsx +++ b/src/components/common/PlatformInlineOptionButton.tsx @@ -1,4 +1,4 @@ -import { forwardRef, type ButtonHTMLAttributes, type ReactNode } from 'react'; +import { type ButtonHTMLAttributes, forwardRef, type ReactNode } from 'react'; type PlatformInlineOptionButtonProps = Omit< ButtonHTMLAttributes, diff --git a/src/components/common/PlatformSegmentedTabPresets.tsx b/src/components/common/PlatformSegmentedTabPresets.tsx index 5c26e5a25..6b7858bd6 100644 --- a/src/components/common/PlatformSegmentedTabPresets.tsx +++ b/src/components/common/PlatformSegmentedTabPresets.tsx @@ -1,6 +1,6 @@ import { - PlatformSegmentedTabs, type PlatformSegmentedTabItem, + PlatformSegmentedTabs, } from './PlatformSegmentedTabs'; type PlatformSegmentedTabPresetProps = { diff --git a/src/components/common/PlatformStatusDialog.test.tsx b/src/components/common/PlatformStatusDialog.test.tsx index 7e9b37b9c..fffc8c1a1 100644 --- a/src/components/common/PlatformStatusDialog.test.tsx +++ b/src/components/common/PlatformStatusDialog.test.tsx @@ -1,7 +1,7 @@ /* @vitest-environment jsdom */ -import { Waves } from 'lucide-react'; import { fireEvent, render, screen } from '@testing-library/react'; +import { Waves } from 'lucide-react'; import { expect, test, vi } from 'vitest'; import { PlatformStatusDialog } from './PlatformStatusDialog'; diff --git a/src/components/common/PlatformStatusDialog.tsx b/src/components/common/PlatformStatusDialog.tsx index 7b95ef02e..bd3713052 100644 --- a/src/components/common/PlatformStatusDialog.tsx +++ b/src/components/common/PlatformStatusDialog.tsx @@ -1,20 +1,19 @@ -import type { ReactNode } from 'react'; - import { AlertCircle, CheckCircle2, Loader2, XCircle, } from 'lucide-react'; +import type { ReactNode } from 'react'; import { PlatformActionButton } from './PlatformActionButton'; -import { PlatformIconBadge } from './PlatformIconBadge'; -import { UnifiedModal } from './UnifiedModal'; import type { PlatformActionButtonSize, PlatformActionButtonSurface, PlatformActionButtonTone, } from './platformActionButtonModel'; +import { PlatformIconBadge } from './PlatformIconBadge'; +import { UnifiedModal } from './UnifiedModal'; export type PlatformStatusDialogStatus = | 'success' diff --git a/src/components/common/SquareImageCropModal.tsx b/src/components/common/SquareImageCropModal.tsx index 1b7cc1d78..44a73b373 100644 --- a/src/components/common/SquareImageCropModal.tsx +++ b/src/components/common/SquareImageCropModal.tsx @@ -8,13 +8,13 @@ import { import { PlatformActionButton } from './PlatformActionButton'; import { PlatformStatusMessage } from './PlatformStatusMessage'; -import { UnifiedModal } from './UnifiedModal'; import { clampNumber, clampSquareImageCropRect, getSquareCropSizeBounds, type SquareImageCropRect, } from './squareImageCropModel'; +import { UnifiedModal } from './UnifiedModal'; export type SquareImageCropModalLabels = { title: string; diff --git a/src/components/creation-agent/CreationAgentWorkspace.tsx b/src/components/creation-agent/CreationAgentWorkspace.tsx index f61513976..3b4fa356b 100644 --- a/src/components/creation-agent/CreationAgentWorkspace.tsx +++ b/src/components/creation-agent/CreationAgentWorkspace.tsx @@ -426,6 +426,7 @@ function normalizeCreationAgentExportFileNameSegment(rawValue: string) { .trim() .split('') .map((character) => + // eslint-disable-next-line no-control-regex -- 文件名必须拒绝 C0 控制字符。 /[\u0000-\u001f<>:"/\\|?*]/u.test(character) ? '-' : character, ) .join('') diff --git a/src/components/image-editor/EditorAgentConversationPanelView.test.tsx b/src/components/image-editor/EditorAgentConversationPanelView.test.tsx index 30bf11244..4a069857d 100644 --- a/src/components/image-editor/EditorAgentConversationPanelView.test.tsx +++ b/src/components/image-editor/EditorAgentConversationPanelView.test.tsx @@ -11,8 +11,8 @@ import { import { beforeEach, describe, expect, it, vi } from 'vitest'; import { EditorAgentConversationPanelView } from './EditorAgentConversationPanelView'; -import { useImageCanvasContextStore } from './useImageCanvasContextStore.ts'; import type { EditorAgentConversationClient } from './useEditorAgentConversation'; +import { useImageCanvasContextStore } from './useImageCanvasContextStore.ts'; const createEditorProjectResourceMock = vi.hoisted(() => vi.fn()); const uploadEditorMediaAssetFileMock = vi.hoisted(() => vi.fn()); @@ -314,6 +314,79 @@ describe('EditorAgentConversationPanelView', () => { }); }); + it('uploads only files that fit the remaining attachment slots', async () => { + const client = createClient(); + const existingLayers = Array.from({ length: 8 }, (_, index) => ({ + id: `layer-${index + 1}`, + resourceId: `resource-${index + 1}`, + title: `已有附件 ${index + 1}`, + src: `/generated/existing-${index + 1}.png`, + x: 0, + y: 0, + width: 256, + height: 256, + originalWidth: 256, + originalHeight: 256, + zIndex: index + 1, + sourceType: 'generated' as const, + })); + + render( + , + ); + + await waitFor(() => { + expect(screen.getByText('已经看到画布内容')).toBeTruthy(); + }); + fireEvent.click(screen.getByRole('button', { name: '添加附件' })); + const attachmentDialog = screen.getByRole('dialog', { + name: '选择图片附件', + }); + existingLayers.forEach((layer) => { + fireEvent.click( + within(attachmentDialog).getByRole('checkbox', { + name: `选择画布图片 ${layer.title}`, + }), + ); + }); + fireEvent.click( + within(attachmentDialog).getByRole('button', { name: '应用' }), + ); + + const firstPastedImage = new File(['first'], 'first.png', { + type: 'image/png', + }); + const overflowPastedImage = new File(['overflow'], 'overflow.png', { + type: 'image/png', + }); + fireEvent.paste(screen.getByLabelText('发送给画布 Agent'), { + clipboardData: { + files: [firstPastedImage, overflowPastedImage], + }, + }); + + await waitFor(() => { + expect(uploadEditorMediaAssetFileMock).toHaveBeenCalledTimes(1); + }); + expect(uploadEditorMediaAssetFileMock).toHaveBeenCalledWith( + firstPastedImage, + 'image', + expect.any(Object), + ); + expect(uploadEditorMediaAssetFileMock.mock.calls[0]?.[0]).toBe( + firstPastedImage, + ); + await waitFor(() => { + expect(screen.getByText('粘贴图片')).toBeTruthy(); + expect(screen.getByText('最多 9 张')).toBeTruthy(); + }); + }); + it('sends selected attachments even when the text input is empty', async () => { const client = createClient(); diff --git a/src/components/image-editor/EditorAgentConversationPanelView.tsx b/src/components/image-editor/EditorAgentConversationPanelView.tsx index 665162fc8..7f82f3af1 100644 --- a/src/components/image-editor/EditorAgentConversationPanelView.tsx +++ b/src/components/image-editor/EditorAgentConversationPanelView.tsx @@ -13,33 +13,33 @@ import { import { type ClipboardEvent as ReactClipboardEvent, type FormEvent, - type WheelEvent as ReactWheelEvent, useEffect, useMemo, useState, + type WheelEvent as ReactWheelEvent, } from 'react'; import { EDITOR_AGENT_MAX_ATTACHMENTS, type EditorAgentAttachmentRef, - type EditorAgentGenerationResultEvent, type EditorAgentGenerationRecord, + type EditorAgentGenerationResultEvent, type EditorAgentMessage, type EditorAgentStage, } from '../../../packages/shared/src/contracts/editorAgent'; +import { uploadEditorMediaAssetFile } from '../../services/image-editor/editorMediaAssetUploadClient'; +import { createEditorProjectResource } from '../../services/image-editor/editorProjectClient'; import { PlatformActionButton } from '../common/PlatformActionButton'; import { PlatformDangerConfirmDialog } from '../common/PlatformDangerConfirmDialog'; import { UnifiedModal } from '../common/UnifiedModal'; import { ResolvedAssetImage } from '../ResolvedAssetImage'; -import { uploadEditorMediaAssetFile } from '../../services/image-editor/editorMediaAssetUploadClient'; -import { createEditorProjectResource } from '../../services/image-editor/editorProjectClient'; import type { CanvasLayer, EditorAsset } from './ImageCanvasEditorTypes'; import { probeImageFileDimensions } from './ImageCanvasFileModel'; -import { useImageCanvasContextStore } from './useImageCanvasContextStore.ts'; import { type EditorAgentConversationClient, useEditorAgentConversation, } from './useEditorAgentConversation'; +import { useImageCanvasContextStore } from './useImageCanvasContextStore.ts'; type AttachmentPickerTab = 'canvas' | 'library'; @@ -609,7 +609,9 @@ export function EditorAgentConversationPanelView({ return [...fileItems]; } - const handleInputPaste = (event: ReactClipboardEvent) => { + const handleInputPaste = ( + event: ReactClipboardEvent, + ) => { const imageFiles = extractClipboardImageFiles(event.clipboardData); if (!imageFiles.length) { return; @@ -625,14 +627,19 @@ export function EditorAgentConversationPanelView({ return; } - // TODO: deduplicate those existing assets + const remainingAttachmentSlots = + EDITOR_AGENT_MAX_ATTACHMENTS - attachments.length; + const uploadFiles = imageFiles.slice(0, remainingAttachmentSlots); + const hasOverflow = uploadFiles.length < imageFiles.length; setIsPastingAttachment(true); setAttachmentError('图片上传中'); void Promise.all( - imageFiles.map((file) => createPastedAgentImageAttachment(file)), + uploadFiles.map((file) => createPastedAgentImageAttachment(file)), ) .then((pastedAttachments) => { - appendAttachments(pastedAttachments); + if (appendAttachments(pastedAttachments) && hasOverflow) { + setAttachmentError(`最多 ${EDITOR_AGENT_MAX_ATTACHMENTS} 张`); + } }) .catch(() => { setAttachmentError('图片粘贴失败,请重试'); diff --git a/src/components/image-editor/ImageCanvasAssetFolderSectionView.tsx b/src/components/image-editor/ImageCanvasAssetFolderSectionView.tsx index 595cdce12..5fb1720a7 100644 --- a/src/components/image-editor/ImageCanvasAssetFolderSectionView.tsx +++ b/src/components/image-editor/ImageCanvasAssetFolderSectionView.tsx @@ -11,24 +11,24 @@ import { import type { Dispatch, SetStateAction } from 'react'; import { PlatformTextField } from '../common/PlatformTextField'; -import { EditorIconButton } from './ImageCanvasEditorPrimitives'; +import type { + GroupedEditorAssetFolder, + UploadFilesOptions, + UploadRequestOptions, +} from './ImageCanvasAssetLibraryPanelView'; +import { ImageCanvasAssetRowView } from './ImageCanvasAssetRowView'; import { ASSET_DRAG_MIME_TYPE, getDraggedAssetId, hasDataTransferType, } from './ImageCanvasEditorModel'; +import { EditorIconButton } from './ImageCanvasEditorPrimitives'; import type { AssetPointerDragState, EditorAsset, EditorAssetFolder, UploadTarget, } from './ImageCanvasEditorTypes'; -import { ImageCanvasAssetRowView } from './ImageCanvasAssetRowView'; -import type { - GroupedEditorAssetFolder, - UploadFilesOptions, - UploadRequestOptions, -} from './ImageCanvasAssetLibraryPanelView'; type ImageCanvasAssetFolderSectionViewProps = { folder: GroupedEditorAssetFolder; diff --git a/src/components/image-editor/ImageCanvasAssetLibraryModel.test.ts b/src/components/image-editor/ImageCanvasAssetLibraryModel.test.ts index 826ee4ba7..6675d690c 100644 --- a/src/components/image-editor/ImageCanvasAssetLibraryModel.test.ts +++ b/src/components/image-editor/ImageCanvasAssetLibraryModel.test.ts @@ -1,9 +1,5 @@ import { describe, expect, it } from 'vitest'; -import type { - EditorAsset, - EditorAssetFolder, -} from './ImageCanvasEditorTypes'; import { areAllSelectableAssetsSelected, createAssetMarqueeFromPointer, @@ -13,24 +9,28 @@ import { filterGroupedAssetsBySearch, getSelectableAssets, groupAssetsByFolder, - moveAssetToFolderLocally, moveAssetMarqueeToPointer, + moveAssetToFolderLocally, removeAssetById, removeSelectedAssets, renameAssetById, renameAssetFolderById, replaceLocalAssetFolder, resolveAllAssetSelection, - resolveAssetShowcaseSubmissionState, resolveAssetFolderIdFromPoint, + resolveAssetShowcaseSubmissionState, resolveDefaultAssetFolder, - resolvePreferredAssetUploadFolder, resolvePinnedAssetMoveFolderId, + resolvePreferredAssetUploadFolder, resolveUploadAssetFolder, selectUploadedAssetsInRect, toggleAssetFolderCollapsed, toggleAssetSelection, } from './ImageCanvasAssetLibraryModel'; +import type { + EditorAsset, + EditorAssetFolder, +} from './ImageCanvasEditorTypes'; function createFolder( overrides: Partial = {}, diff --git a/src/components/image-editor/ImageCanvasAssetLibraryPanelView.tsx b/src/components/image-editor/ImageCanvasAssetLibraryPanelView.tsx index 0209b88d9..1b5bdab8d 100644 --- a/src/components/image-editor/ImageCanvasAssetLibraryPanelView.tsx +++ b/src/components/image-editor/ImageCanvasAssetLibraryPanelView.tsx @@ -16,8 +16,8 @@ import type { import { PlatformActionButton } from '../common/PlatformActionButton'; import { PlatformBatchActionToolbar } from '../common/PlatformBatchActionToolbar'; import { PlatformTextField } from '../common/PlatformTextField'; -import { EditorIconButton } from './ImageCanvasEditorPrimitives'; import { ImageCanvasAssetFolderSectionView } from './ImageCanvasAssetFolderSectionView'; +import { EditorIconButton } from './ImageCanvasEditorPrimitives'; import type { AssetMarqueeState, AssetPointerDragState, diff --git a/src/components/image-editor/ImageCanvasAssetRowView.test.tsx b/src/components/image-editor/ImageCanvasAssetRowView.test.tsx index 10c12ac0b..becd864a2 100644 --- a/src/components/image-editor/ImageCanvasAssetRowView.test.tsx +++ b/src/components/image-editor/ImageCanvasAssetRowView.test.tsx @@ -4,8 +4,8 @@ import { fireEvent, render, screen } from '@testing-library/react'; import type { ReactNode } from 'react'; import { beforeEach, describe, expect, it, vi } from 'vitest'; -import type { EditorAsset } from './ImageCanvasEditorTypes'; import { ImageCanvasAssetRowView } from './ImageCanvasAssetRowView'; +import type { EditorAsset } from './ImageCanvasEditorTypes'; const useResolvedAssetReadUrlMock = vi.hoisted(() => vi.fn()); diff --git a/src/components/image-editor/ImageCanvasAssetRowView.tsx b/src/components/image-editor/ImageCanvasAssetRowView.tsx index ebe252ecc..9f27b5d2f 100644 --- a/src/components/image-editor/ImageCanvasAssetRowView.tsx +++ b/src/components/image-editor/ImageCanvasAssetRowView.tsx @@ -1,21 +1,21 @@ import { Check, Download, Pencil, Video, X } from 'lucide-react'; -import { useCallback, useEffect, useRef, useState } from 'react'; import type { Dispatch, SetStateAction } from 'react'; +import { useCallback, useEffect, useRef, useState } from 'react'; import { useResolvedAssetReadUrl } from '../../hooks/useResolvedAssetReadUrl'; import { PlatformTextField } from '../common/PlatformTextField'; +import { resolveAssetShowcaseSubmissionState } from './ImageCanvasAssetLibraryModel'; import type { UploadFilesOptions } from './ImageCanvasAssetLibraryPanelView'; -import { - EditorIconButton, - SidebarMediaItem, -} from './ImageCanvasEditorPrimitives'; import { ASSET_DRAG_MIME_TYPE, clamp, getDraggedAssetId, hasDataTransferType, } from './ImageCanvasEditorModel'; -import { resolveAssetShowcaseSubmissionState } from './ImageCanvasAssetLibraryModel'; +import { + EditorIconButton, + SidebarMediaItem, +} from './ImageCanvasEditorPrimitives'; import type { AssetPointerDragState, EditorAsset, diff --git a/src/components/image-editor/ImageCanvasBasicGenerationComposerView.tsx b/src/components/image-editor/ImageCanvasBasicGenerationComposerView.tsx index ce63f14c6..8f1eef330 100644 --- a/src/components/image-editor/ImageCanvasBasicGenerationComposerView.tsx +++ b/src/components/image-editor/ImageCanvasBasicGenerationComposerView.tsx @@ -19,8 +19,8 @@ import type { UploadTarget, } from './ImageCanvasEditorTypes'; import { ImageCanvasGenerationImageOptionsView } from './ImageCanvasGenerationImageOptionsView'; -import { ImageCanvasReferenceSlot } from './ImageCanvasReferenceSlot'; import { calculateEditorImageGenerationPrice } from './ImageCanvasGenerationModel'; +import { ImageCanvasReferenceSlot } from './ImageCanvasReferenceSlot'; import { useImageCanvasFloatingOptionDismiss } from './useImageCanvasFloatingOptionDismiss'; type ReferenceLabelFormatter = ( diff --git a/src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx b/src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx index aab1ed80f..3b83c5a1b 100644 --- a/src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx +++ b/src/components/image-editor/ImageCanvasBottomToolbarView.test.tsx @@ -1,9 +1,9 @@ /* @vitest-environment jsdom */ -import { createRef, type RefObject } from 'react'; -import { describe, expect, it, vi } from 'vitest'; import { render, screen, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { createRef, type RefObject } from 'react'; +import { describe, expect, it, vi } from 'vitest'; import { ImageCanvasBottomToolbarView } from './ImageCanvasBottomToolbarView'; diff --git a/src/components/image-editor/ImageCanvasCharacterAnimationPanelView.test.tsx b/src/components/image-editor/ImageCanvasCharacterAnimationPanelView.test.tsx index deca396d4..84e0d5b48 100644 --- a/src/components/image-editor/ImageCanvasCharacterAnimationPanelView.test.tsx +++ b/src/components/image-editor/ImageCanvasCharacterAnimationPanelView.test.tsx @@ -4,8 +4,8 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { useState } from 'react'; import { describe, expect, it, vi } from 'vitest'; -import type { CanvasLayer, CharacterAnimationPanelState } from './ImageCanvasEditorTypes'; import { ImageCanvasCharacterAnimationPanelView } from './ImageCanvasCharacterAnimationPanelView'; +import type { CanvasLayer, CharacterAnimationPanelState } from './ImageCanvasEditorTypes'; function createSourceLayer(): CanvasLayer { diff --git a/src/components/image-editor/ImageCanvasCharacterGenerationComposerView.test.tsx b/src/components/image-editor/ImageCanvasCharacterGenerationComposerView.test.tsx index e7a60f11a..04b89fbb5 100644 --- a/src/components/image-editor/ImageCanvasCharacterGenerationComposerView.test.tsx +++ b/src/components/image-editor/ImageCanvasCharacterGenerationComposerView.test.tsx @@ -4,12 +4,12 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { createRef, useState } from 'react'; import { describe, expect, it, vi } from 'vitest'; +import { ImageCanvasCharacterGenerationComposerView } from './ImageCanvasCharacterGenerationComposerView'; import type { GenerateDialogState, SpecGenerationType, UploadTarget, } from './ImageCanvasEditorTypes'; -import { ImageCanvasCharacterGenerationComposerView } from './ImageCanvasCharacterGenerationComposerView'; import { IMAGE_MODEL_GPT_IMAGE_2 } from './ImageCanvasGenerationModel'; function createDialog( diff --git a/src/components/image-editor/ImageCanvasCharacterGenerationComposerView.tsx b/src/components/image-editor/ImageCanvasCharacterGenerationComposerView.tsx index 2ddc374dd..a46565b7b 100644 --- a/src/components/image-editor/ImageCanvasCharacterGenerationComposerView.tsx +++ b/src/components/image-editor/ImageCanvasCharacterGenerationComposerView.tsx @@ -13,7 +13,6 @@ import { } from '../common/PlatformFloatingMenu'; import { PlatformStatusMessage } from '../common/PlatformStatusMessage'; import { PlatformTextField } from '../common/PlatformTextField'; -import { ImageCanvasReferenceSlot } from './ImageCanvasReferenceSlot'; import type { CharacterReferenceImage, GenerateDialogState, @@ -22,6 +21,7 @@ import type { } from './ImageCanvasEditorTypes'; import { ImageCanvasGenerationImageOptionsView } from './ImageCanvasGenerationImageOptionsView'; import { calculateEditorImageGenerationPrice } from './ImageCanvasGenerationModel'; +import { ImageCanvasReferenceSlot } from './ImageCanvasReferenceSlot'; import { useImageCanvasFloatingOptionDismiss } from './useImageCanvasFloatingOptionDismiss'; type ImageCanvasCharacterGenerationComposerViewProps = { diff --git a/src/components/image-editor/ImageCanvasCropExpandModel.ts b/src/components/image-editor/ImageCanvasCropExpandModel.ts index 6422a1a60..a591ffaed 100644 --- a/src/components/image-editor/ImageCanvasCropExpandModel.ts +++ b/src/components/image-editor/ImageCanvasCropExpandModel.ts @@ -1,7 +1,7 @@ import type { CropExpandPanelState, - CropExpandResizeHandle, CropExpandRatioValue, + CropExpandResizeHandle, } from './ImageCanvasEditorTypes'; export const CROP_EXPAND_MIN_FRAME_SIZE = 16; diff --git a/src/components/image-editor/ImageCanvasEditGenerationModalView.test.tsx b/src/components/image-editor/ImageCanvasEditGenerationModalView.test.tsx index db0763e54..381706120 100644 --- a/src/components/image-editor/ImageCanvasEditGenerationModalView.test.tsx +++ b/src/components/image-editor/ImageCanvasEditGenerationModalView.test.tsx @@ -4,8 +4,8 @@ import { fireEvent, render, screen } from '@testing-library/react'; import { useState } from 'react'; import { describe, expect, it, vi } from 'vitest'; -import type { GenerateDialogState } from './ImageCanvasEditorTypes'; import { ImageCanvasEditGenerationModalView } from './ImageCanvasEditGenerationModalView'; +import type { GenerateDialogState } from './ImageCanvasEditorTypes'; function createDialog( patch: Partial = {}, diff --git a/src/components/image-editor/ImageCanvasEditGenerationModalView.tsx b/src/components/image-editor/ImageCanvasEditGenerationModalView.tsx index edc042dff..7ebb37464 100644 --- a/src/components/image-editor/ImageCanvasEditGenerationModalView.tsx +++ b/src/components/image-editor/ImageCanvasEditGenerationModalView.tsx @@ -1,11 +1,11 @@ import { type Dispatch, type SetStateAction } from 'react'; import { UnifiedModal } from '../common/UnifiedModal'; +import { ImageCanvasBasicGenerationComposerView } from './ImageCanvasBasicGenerationComposerView'; +import type { GenerateDialogState } from './ImageCanvasEditorTypes'; import { calculateEditorImageGenerationPrice, } from './ImageCanvasGenerationModel'; -import { ImageCanvasBasicGenerationComposerView } from './ImageCanvasBasicGenerationComposerView'; -import type { GenerateDialogState } from './ImageCanvasEditorTypes'; type ImageCanvasEditGenerationModalViewProps = { dialog: GenerateDialogState | null; diff --git a/src/components/image-editor/ImageCanvasEditorAssetsIntegration.test.tsx b/src/components/image-editor/ImageCanvasEditorAssetsIntegration.test.tsx index 5c0f70c98..f94e97eb3 100644 --- a/src/components/image-editor/ImageCanvasEditorAssetsIntegration.test.tsx +++ b/src/components/image-editor/ImageCanvasEditorAssetsIntegration.test.tsx @@ -14,11 +14,11 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { ApiClientError, AuthUiContext, - ImageCanvasEditorView, createAuthValue, createDataTransferStub, createDeferred, dispatchPointerEvent, + ImageCanvasEditorView, setupImageCanvasEditorViewTestLifecycle, } from './ImageCanvasEditorView.test-utils'; diff --git a/src/components/image-editor/ImageCanvasEditorModel.test.ts b/src/components/image-editor/ImageCanvasEditorModel.test.ts index d5dce4279..b04344799 100644 --- a/src/components/image-editor/ImageCanvasEditorModel.test.ts +++ b/src/components/image-editor/ImageCanvasEditorModel.test.ts @@ -1,9 +1,9 @@ import { describe, expect, it } from 'vitest'; import { + CANVAS_WORLD_ORIGIN, canvasDisplayScaleToViewportScale, canvasDisplayViewportToViewport, - CANVAS_WORLD_ORIGIN, createLayerFromAsset, DEFAULT_CANVAS_BACKGROUND_COLOR, formatCanvasDisplayScalePercent, diff --git a/src/components/image-editor/ImageCanvasEditorModel.ts b/src/components/image-editor/ImageCanvasEditorModel.ts index 503b318d6..f2ce226a8 100644 --- a/src/components/image-editor/ImageCanvasEditorModel.ts +++ b/src/components/image-editor/ImageCanvasEditorModel.ts @@ -9,8 +9,8 @@ import type { CanvasGenerationDialogState, CanvasGenerationInputs, CanvasLayer, - CanvasSnapItem, CanvasMediaType, + CanvasSnapItem, CanvasViewport, CharacterReferenceImage, EditorAsset, diff --git a/src/components/image-editor/ImageCanvasEditorView.test-utils.ts b/src/components/image-editor/ImageCanvasEditorView.test-utils.ts index 3fc679b33..7fe6f64c8 100644 --- a/src/components/image-editor/ImageCanvasEditorView.test-utils.ts +++ b/src/components/image-editor/ImageCanvasEditorView.test-utils.ts @@ -1,6 +1,6 @@ +import { fireEvent } from '@testing-library/react'; import JSZip from 'jszip'; import type { ContextType } from 'react'; -import { fireEvent } from '@testing-library/react'; import { afterEach, beforeEach, expect, type Mock, vi } from 'vitest'; import { ApiClientError } from '../../services/apiClient'; @@ -232,6 +232,7 @@ export function setupImageCanvasEditorViewTestLifecycle({ saveEditorProjectLayoutMock, }: ImageCanvasEditorViewServiceMocks) { beforeEach(() => { + vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockReturnValue(null); loadOrCreateRecentEditorProjectMock.mockImplementation(() => immediateAsync({ projectId: 'editor-project-default', diff --git a/src/components/image-editor/ImageCanvasEditorView.test.tsx b/src/components/image-editor/ImageCanvasEditorView.test.tsx index 076f94df8..5b40cb36a 100644 --- a/src/components/image-editor/ImageCanvasEditorView.test.tsx +++ b/src/components/image-editor/ImageCanvasEditorView.test.tsx @@ -21,6 +21,7 @@ import { defaultEditorProjectResources, dispatchPointerEvent, ImageCanvasEditorView, + immediateAsync, readZipText, setupImageCanvasEditorViewTestLifecycle, } from './ImageCanvasEditorView.test-utils'; @@ -265,9 +266,11 @@ describe('ImageCanvasEditorView', () => { }); beforeEach(() => { - loadFrontendRuntimeConfigMock.mockResolvedValue({ - imageEditorAgentSidebarEnabled: false, - }); + loadFrontendRuntimeConfigMock.mockImplementation(() => + immediateAsync({ + imageEditorAgentSidebarEnabled: false, + }), + ); listEditorAgentConversationsMock.mockResolvedValue([]); createEditorAgentConversationMock.mockResolvedValue({ ...createEditorAgentConversationSummary({ @@ -596,6 +599,7 @@ describe('ImageCanvasEditorView', () => { }); it('opens the shared wallet breakdown and ledger from the canvas topbar', async () => { + const user = userEvent.setup(); render( { name: '泥点 1,234', }); - fireEvent.click(walletButton); + await user.hover(walletButton); const details = await screen.findByRole('dialog', { name: '泥点账户详情', @@ -629,7 +633,7 @@ describe('ImageCanvasEditorView', () => { expect(within(details).queryByText('限时泥点')).toBeNull(); expect(within(details).getByText('每日免费泥点')).toBeTruthy(); - fireEvent.click(within(details).getByRole('button', { name: '使用详情' })); + await user.click(within(details).getByRole('button', { name: '使用详情' })); expect( await screen.findByRole('dialog', { name: '泥点账单' }), ).toBeTruthy(); @@ -1875,7 +1879,7 @@ describe('ImageCanvasEditorView', () => { expect(screen.queryByLabelText('发送给画布 Agent')).toBeNull(); }); - it('adds assets from the sidebar and supports zoom buttons', () => { + it('adds assets from the sidebar and supports zoom buttons', async () => { render(); expect( @@ -1889,7 +1893,9 @@ describe('ImageCanvasEditorView', () => { ).toBeTruthy(); openAssetSidebar(); - fireEvent.click(screen.getByRole('button', { name: '添加声浪素材' })); + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: '添加声浪素材' })); + }); expect(screen.getByAltText('画布图片:声浪素材')).toBeTruthy(); expect( @@ -2284,7 +2290,7 @@ describe('ImageCanvasEditorView', () => { clientX: 400, clientY: 280, }); - viewport.dispatchEvent(ctrlWheelEvent); + fireEvent(viewport, ctrlWheelEvent); expect(ctrlWheelEvent.defaultPrevented).toBe(true); }); @@ -2628,7 +2634,7 @@ describe('ImageCanvasEditorView', () => { ).toBeNull(); }); - it('undoes and redoes canvas layer changes from the panel controls', () => { + it('undoes and redoes canvas layer changes from the panel controls', async () => { render(); expect(screen.getByRole('button', { name: '撤销' })).toHaveProperty( @@ -2641,40 +2647,52 @@ describe('ImageCanvasEditorView', () => { ); openAssetSidebar(); - fireEvent.click(screen.getByRole('button', { name: '添加声浪素材' })); + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: '添加声浪素材' })); + }); expect(screen.getByAltText('画布图片:声浪素材')).toBeTruthy(); expect(screen.getByRole('button', { name: '撤销' })).toHaveProperty( 'disabled', false, ); - fireEvent.click(screen.getByRole('button', { name: '撤销' })); + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: '撤销' })); + }); expect(screen.queryByAltText('画布图片:声浪素材')).toBeNull(); expect(screen.getByRole('button', { name: '重做' })).toHaveProperty( 'disabled', false, ); - fireEvent.click(screen.getByRole('button', { name: '重做' })); + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: '重做' })); + }); expect(screen.getByAltText('画布图片:声浪素材')).toBeTruthy(); }); - it('supports undo and redo keyboard shortcuts inside the editor', () => { + it('supports undo and redo keyboard shortcuts inside the editor', async () => { render(); openAssetSidebar(); - fireEvent.click(screen.getByRole('button', { name: '添加声浪素材' })); + await act(async () => { + fireEvent.click(screen.getByRole('button', { name: '添加声浪素材' })); + }); expect(screen.getByAltText('画布图片:声浪素材')).toBeTruthy(); - fireEvent.keyDown(window, { key: 'z', code: 'KeyZ', ctrlKey: true }); + await act(async () => { + fireEvent.keyDown(window, { key: 'z', code: 'KeyZ', ctrlKey: true }); + }); expect(screen.queryByAltText('画布图片:声浪素材')).toBeNull(); - fireEvent.keyDown(window, { - key: 'Z', - code: 'KeyZ', - ctrlKey: true, - shiftKey: true, + await act(async () => { + fireEvent.keyDown(window, { + key: 'Z', + code: 'KeyZ', + ctrlKey: true, + shiftKey: true, + }); }); expect(screen.getByAltText('画布图片:声浪素材')).toBeTruthy(); }); diff --git a/src/components/image-editor/ImageCanvasGenerationComposerView.tsx b/src/components/image-editor/ImageCanvasGenerationComposerView.tsx index 3049a31e5..9d4f3fa96 100644 --- a/src/components/image-editor/ImageCanvasGenerationComposerView.tsx +++ b/src/components/image-editor/ImageCanvasGenerationComposerView.tsx @@ -44,14 +44,13 @@ import { DEFAULT_VIDEO_ASPECT_RATIO, DEFAULT_VIDEO_DURATION_SECONDS, DEFAULT_VIDEO_SOUND, - DEFAULT_VIDEO_WEB_SEARCH_ENABLED, - EDITOR_VIDEO_ASPECT_RATIO_OPTIONS, EDITOR_SOUND_EFFECT_MODEL_OPTIONS, + EDITOR_VIDEO_ASPECT_RATIO_OPTIONS, EDITOR_VIDEO_MODEL_OPTIONS, EDITOR_VIDEO_RESOLUTION_OPTIONS, - VIDEO_MODEL_SEEDANCE_2_FAST, resizeGenerationPlaceholderToVideoSelection, SPEC_TYPE_LABEL, + VIDEO_MODEL_SEEDANCE_2_FAST, } from './ImageCanvasGenerationModel'; import { ImageCanvasIconSpritesheetComposerView } from './ImageCanvasIconSpritesheetComposerView'; import { ImageCanvasPublicationMaterialsDemoPanelView } from './ImageCanvasPublicationMaterialsDemoPanelView'; diff --git a/src/components/image-editor/ImageCanvasGenerationDialogModel.ts b/src/components/image-editor/ImageCanvasGenerationDialogModel.ts index d238044fe..7e7339a8f 100644 --- a/src/components/image-editor/ImageCanvasGenerationDialogModel.ts +++ b/src/components/image-editor/ImageCanvasGenerationDialogModel.ts @@ -740,7 +740,6 @@ function restoreSharedImageOptions( sourceLayer: CanvasLayer, sourceDialog?: CanvasGenerationDialogState | null, ): Omit { - const fields = getGenerationInputFieldValues(sourceLayer); return { ...draft, imageModel: sourceDialog?.imageModel ?? draft.imageModel, diff --git a/src/components/image-editor/ImageCanvasGenerationModel.test.ts b/src/components/image-editor/ImageCanvasGenerationModel.test.ts index d80c7ed02..5951c4be4 100644 --- a/src/components/image-editor/ImageCanvasGenerationModel.test.ts +++ b/src/components/image-editor/ImageCanvasGenerationModel.test.ts @@ -6,8 +6,8 @@ import type { CanvasLayer, } from './ImageCanvasEditorTypes'; import { - BACKGROUND_MUSIC_MODEL_SUNO, applyEditorGenerationPricingConfig, + BACKGROUND_MUSIC_MODEL_SUNO, buildCharacterGenerationInputs, buildEditGenerationInputs, buildIconGenerationInputs, @@ -33,8 +33,8 @@ import { DEFAULT_VIDEO_MODEL, EDITOR_BACKGROUND_MUSIC_MODEL_MUD_POINT_CONFIG, EDITOR_IMAGE_MODEL_MUD_POINT_CONFIG, - EDITOR_MODEL_MUD_POINT_CONFIG, EDITOR_IMAGE_MODEL_OPTIONS, + EDITOR_MODEL_MUD_POINT_CONFIG, EDITOR_SOUND_EFFECT_MODEL_MUD_POINT_CONFIG, EDITOR_SOUND_EFFECT_MODEL_OPTIONS, EDITOR_VIDEO_MODEL_MUD_POINT_CONFIG, diff --git a/src/components/image-editor/ImageCanvasGenerationPlacementModel.test.ts b/src/components/image-editor/ImageCanvasGenerationPlacementModel.test.ts index ea26a79ab..f0ce14963 100644 --- a/src/components/image-editor/ImageCanvasGenerationPlacementModel.test.ts +++ b/src/components/image-editor/ImageCanvasGenerationPlacementModel.test.ts @@ -1,14 +1,14 @@ import { describe, expect, it } from 'vitest'; -import { - chooseGenerationPlacement, - centerViewportOnPlacement, -} from './ImageCanvasGenerationPlacementModel'; import type { CanvasGenerationDialogState, CanvasLayer, CanvasViewport, } from './ImageCanvasEditorTypes'; +import { + centerViewportOnPlacement, + chooseGenerationPlacement, +} from './ImageCanvasGenerationPlacementModel'; const canvasSize = { width: 900, height: 640 }; const viewport: CanvasViewport = { x: 10, y: 20, scale: 2 }; diff --git a/src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts b/src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts index 4ce5c8dbe..cab953c19 100644 --- a/src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts +++ b/src/components/image-editor/ImageCanvasGenerationSubmissionModel.test.ts @@ -1,15 +1,15 @@ import { describe, expect, it } from 'vitest'; import type { CanvasLayer } from './ImageCanvasEditorTypes'; +import { + IMAGE_MODEL_GPT_IMAGE_2, + IMAGE_MODEL_NANOBANANA2, +} from './ImageCanvasGenerationModel'; import { buildCharacterAnimationSubmissionPlan, buildIconSpritesheetGenerationSubmissionPlan, buildImageGenerationSubmissionPlan, } from './ImageCanvasGenerationSubmissionModel'; -import { - IMAGE_MODEL_GPT_IMAGE_2, - IMAGE_MODEL_NANOBANANA2, -} from './ImageCanvasGenerationModel'; function createLayer(overrides: Partial = {}): CanvasLayer { return { diff --git a/src/components/image-editor/ImageCanvasIconSpritesheetComposerView.tsx b/src/components/image-editor/ImageCanvasIconSpritesheetComposerView.tsx index 10cc79936..17a49a7ed 100644 --- a/src/components/image-editor/ImageCanvasIconSpritesheetComposerView.tsx +++ b/src/components/image-editor/ImageCanvasIconSpritesheetComposerView.tsx @@ -21,7 +21,6 @@ import type { import { ImageCanvasGenerationImageOptionsView } from './ImageCanvasGenerationImageOptionsView'; import { calculateEditorIconSpritesheetPrice, - DEFAULT_ICON_DESCRIPTIONS, } from './ImageCanvasGenerationModel'; import { ImageCanvasReferenceSlot } from './ImageCanvasReferenceSlot'; import { useImageCanvasFloatingOptionDismiss } from './useImageCanvasFloatingOptionDismiss'; diff --git a/src/components/image-editor/ImageCanvasInteractionModel.test.ts b/src/components/image-editor/ImageCanvasInteractionModel.test.ts index 0e677d3ee..f9579cf51 100644 --- a/src/components/image-editor/ImageCanvasInteractionModel.test.ts +++ b/src/components/image-editor/ImageCanvasInteractionModel.test.ts @@ -1,5 +1,10 @@ import { describe, expect, it } from 'vitest'; +import type { + CanvasGenerationDialogState, + CanvasLayer, + DragState, +} from './ImageCanvasEditorTypes'; import { createMinimapModel, fitViewportToBounds, @@ -7,9 +12,9 @@ import { getCanvasDropPoint, getCanvasPointFromClient, getWorldPointFromClient, + moveDragSelectedLayers, moveGenerationFrameFromDrag, moveGenerationFramesFromDrag, - moveDragSelectedLayers, moveLayersFromDrag, moveViewportFromMinimapDrag, moveViewportFromMinimapPointer, @@ -20,11 +25,6 @@ import { selectLayersInsideMarquee, zoomViewportFromWheel, } from './ImageCanvasInteractionModel'; -import type { - CanvasGenerationDialogState, - CanvasLayer, - DragState, -} from './ImageCanvasEditorTypes'; import { getCanvasGenerationSelectionId } from './ImageCanvasSelectionModel'; function createLayer(overrides: Partial): CanvasLayer { diff --git a/src/components/image-editor/ImageCanvasInteractionModel.ts b/src/components/image-editor/ImageCanvasInteractionModel.ts index 664c9ca91..3780870d8 100644 --- a/src/components/image-editor/ImageCanvasInteractionModel.ts +++ b/src/components/image-editor/ImageCanvasInteractionModel.ts @@ -1,5 +1,17 @@ import type { CSSProperties } from 'react'; +import { + clamp, + DEFAULT_CANVAS_SIZE, + FIT_VIEW_PADDING, + getLayerBounds, + MAX_SCALE, + MIN_SCALE, + MINIMAP_DRAG_SENSITIVITY, + MINIMAP_PADDING, + MINIMAP_SIZE, + resolveSnappedItemPosition, +} from './ImageCanvasEditorModel'; import type { CanvasGenerationDialogState, CanvasLayer, @@ -7,18 +19,6 @@ import type { CanvasViewport, DragState, } from './ImageCanvasEditorTypes'; -import { - DEFAULT_CANVAS_SIZE, - FIT_VIEW_PADDING, - MAX_SCALE, - MIN_SCALE, - MINIMAP_DRAG_SENSITIVITY, - MINIMAP_PADDING, - MINIMAP_SIZE, - clamp, - getLayerBounds, - resolveSnappedItemPosition, -} from './ImageCanvasEditorModel'; import { getCanvasGenerationSelectionId } from './ImageCanvasSelectionModel'; export type CanvasSize = { diff --git a/src/components/image-editor/ImageCanvasLayerCommandModel.test.ts b/src/components/image-editor/ImageCanvasLayerCommandModel.test.ts index 4cc91f02a..88b353c96 100644 --- a/src/components/image-editor/ImageCanvasLayerCommandModel.test.ts +++ b/src/components/image-editor/ImageCanvasLayerCommandModel.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from 'vitest'; +import type { + CanvasContextMenuState, + CanvasLayer, +} from './ImageCanvasEditorTypes'; import { createCanvasLayerClipboard, duplicateCanvasLayers, @@ -13,10 +17,6 @@ import { toggleCanvasLayersVisibility, ungroupCanvasLayers, } from './ImageCanvasLayerCommandModel'; -import type { - CanvasContextMenuState, - CanvasLayer, -} from './ImageCanvasEditorTypes'; function createLayer(overrides: Partial): CanvasLayer { const id = overrides.id ?? 'layer-a'; diff --git a/src/components/image-editor/ImageCanvasMetadataModalView.test.tsx b/src/components/image-editor/ImageCanvasMetadataModalView.test.tsx index cdabe319f..41ab7a9aa 100644 --- a/src/components/image-editor/ImageCanvasMetadataModalView.test.tsx +++ b/src/components/image-editor/ImageCanvasMetadataModalView.test.tsx @@ -64,7 +64,6 @@ describe('ImageCanvasMetadataModalView', () => { expect(within(dialog).queryByText(/VectorEngine/u)).toBeNull(); expect(within(dialog).queryByText(/task-/u)).toBeNull(); expect(within(dialog).getByText('123')).toBeTruthy(); - expect(within(dialog).getByText('generated/object.png')).toBeTruthy(); }); it('renders nanobanana2 instead of the raw Gemini image model id', () => { @@ -108,7 +107,6 @@ describe('ImageCanvasMetadataModalView', () => { expect(within(dialog).queryByText('上传素材')).toBeNull(); expect(within(dialog).getByText('上传图片')).toBeTruthy(); expect(within(dialog).getAllByText('-').length).toBeGreaterThanOrEqual(3); - expect(within(dialog).getByText('asset-object-1')).toBeTruthy(); fireEvent.click(within(dialog).getByRole('button', { name: '关闭图片信息' })); diff --git a/src/components/image-editor/ImageCanvasPanelDockView.tsx b/src/components/image-editor/ImageCanvasPanelDockView.tsx index 02b04642a..171daa690 100644 --- a/src/components/image-editor/ImageCanvasPanelDockView.tsx +++ b/src/components/image-editor/ImageCanvasPanelDockView.tsx @@ -8,12 +8,12 @@ import { Undo2, X, } from 'lucide-react'; -import { useEffect, useRef } from 'react'; import type { CSSProperties, KeyboardEvent as ReactKeyboardEvent, PointerEvent as ReactPointerEvent, } from 'react'; +import { useEffect, useRef } from 'react'; import { PlatformFloatingMenu, diff --git a/src/components/image-editor/ImageCanvasPublicationMaterialsDemoPanelView.tsx b/src/components/image-editor/ImageCanvasPublicationMaterialsDemoPanelView.tsx index 8d96176ad..57837546d 100644 --- a/src/components/image-editor/ImageCanvasPublicationMaterialsDemoPanelView.tsx +++ b/src/components/image-editor/ImageCanvasPublicationMaterialsDemoPanelView.tsx @@ -14,7 +14,6 @@ import { } from '../common/PlatformFloatingMenu'; import { PlatformStatusMessage } from '../common/PlatformStatusMessage'; import { PlatformTextField } from '../common/PlatformTextField'; -import { ImageCanvasReferenceSlot } from './ImageCanvasReferenceSlot'; import type { CharacterReferenceImage, GenerateDialogState, @@ -29,6 +28,7 @@ import { IMAGE_MODEL_GPT_IMAGE_2, } from './ImageCanvasGenerationModel'; import type { PublicationMaterialsWorkflow } from './ImageCanvasPublicationMaterialsModel'; +import { ImageCanvasReferenceSlot } from './ImageCanvasReferenceSlot'; type ImageCanvasPublicationMaterialsDemoPanelViewProps = { dialog: GenerateDialogState; diff --git a/src/components/image-editor/ImageCanvasQuickEditPanelView.tsx b/src/components/image-editor/ImageCanvasQuickEditPanelView.tsx index 66953ef42..f59103f95 100644 --- a/src/components/image-editor/ImageCanvasQuickEditPanelView.tsx +++ b/src/components/image-editor/ImageCanvasQuickEditPanelView.tsx @@ -1,20 +1,19 @@ -import { ImageIcon } from 'lucide-react'; import { type CSSProperties, type Dispatch, + type ReactNode, type RefObject, type SetStateAction, - type ReactNode, } from 'react'; import { ImageCanvasBasicGenerationComposerView } from './ImageCanvasBasicGenerationComposerView'; -import { QUICK_EDIT_REFERENCE_LIMIT } from './ImageCanvasGenerationModel'; import type { CanvasLayer, GenerateDialogState, QuickEditPanelState, UploadTarget, } from './ImageCanvasEditorTypes'; +import { QUICK_EDIT_REFERENCE_LIMIT } from './ImageCanvasGenerationModel'; export type ImageCanvasQuickEditPanelViewProps = { panel: QuickEditPanelState; diff --git a/src/components/image-editor/ImageCanvasRasterEditModel.test.ts b/src/components/image-editor/ImageCanvasRasterEditModel.test.ts index 6c541c5d1..e0fe9a7c3 100644 --- a/src/components/image-editor/ImageCanvasRasterEditModel.test.ts +++ b/src/components/image-editor/ImageCanvasRasterEditModel.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from 'vitest'; import { - buildCropExpandInsetsFromFrame, buildCropExpandGeometry, + buildCropExpandInsetsFromFrame, buildCropExpandLayerPatch, parseCropExpandInsetValue, } from './ImageCanvasRasterEditModel'; diff --git a/src/components/image-editor/ImageCanvasRasterEditModel.ts b/src/components/image-editor/ImageCanvasRasterEditModel.ts index 4db465040..6facd9461 100644 --- a/src/components/image-editor/ImageCanvasRasterEditModel.ts +++ b/src/components/image-editor/ImageCanvasRasterEditModel.ts @@ -1,7 +1,7 @@ import { - removeEditorImageBackground, type EditorBackgroundRemovalInput, type EditorBackgroundRemovalResult, + removeEditorImageBackground, } from '../../services/image-editor/editorProjectClient'; export type CropExpandInsets = { diff --git a/src/components/image-editor/ImageCanvasReferenceSlot.tsx b/src/components/image-editor/ImageCanvasReferenceSlot.tsx index f35b1e200..f0304f6e2 100644 --- a/src/components/image-editor/ImageCanvasReferenceSlot.tsx +++ b/src/components/image-editor/ImageCanvasReferenceSlot.tsx @@ -1,5 +1,5 @@ -import { type ReactNode, type RefObject } from 'react'; import { X } from 'lucide-react'; +import { type ReactNode, type RefObject } from 'react'; import { ResolvedAssetImage } from '../ResolvedAssetImage'; diff --git a/src/components/image-editor/ImageCanvasSidebarView.test.tsx b/src/components/image-editor/ImageCanvasSidebarView.test.tsx index 3605fa5a6..3062bf96e 100644 --- a/src/components/image-editor/ImageCanvasSidebarView.test.tsx +++ b/src/components/image-editor/ImageCanvasSidebarView.test.tsx @@ -4,16 +4,16 @@ import { fireEvent, render, screen, within } from '@testing-library/react'; import { createRef } from 'react'; import { describe, expect, it, vi } from 'vitest'; -import { ImageCanvasPanelDockView } from './ImageCanvasPanelDockView'; -import { - ImageCanvasSidebarView, - type ImageCanvasSidebarViewProps, -} from './ImageCanvasSidebarView'; import type { CanvasLayer, EditorAsset, EditorAssetFolder, } from './ImageCanvasEditorTypes'; +import { ImageCanvasPanelDockView } from './ImageCanvasPanelDockView'; +import { + ImageCanvasSidebarView, + type ImageCanvasSidebarViewProps, +} from './ImageCanvasSidebarView'; import { useImageCanvasEditorChrome } from './useImageCanvasEditorChrome'; function createAsset(overrides: Partial = {}): EditorAsset { diff --git a/src/components/image-editor/ImageCanvasSidebarView.tsx b/src/components/image-editor/ImageCanvasSidebarView.tsx index 95430ac21..2fb1e8043 100644 --- a/src/components/image-editor/ImageCanvasSidebarView.tsx +++ b/src/components/image-editor/ImageCanvasSidebarView.tsx @@ -12,17 +12,17 @@ import type { import { useMemo, useState } from 'react'; import { PlatformTextField } from '../common/PlatformTextField'; -import { - ImageCanvasAssetLibraryPanelView, - type GroupedEditorAssetFolder, - type UploadFilesOptions, - type UploadRequestOptions, -} from './ImageCanvasAssetLibraryPanelView'; import { filterGroupedAssetsBySearch, layerMatchesSearch, layerStartsWithSearch, } from './ImageCanvasAssetLibraryModel'; +import { + type GroupedEditorAssetFolder, + ImageCanvasAssetLibraryPanelView, + type UploadFilesOptions, + type UploadRequestOptions, +} from './ImageCanvasAssetLibraryPanelView'; import { EditorIconButton } from './ImageCanvasEditorPrimitives'; import type { AssetMarqueeState, diff --git a/src/components/image-editor/ImageCanvasStageControllerModel.ts b/src/components/image-editor/ImageCanvasStageControllerModel.ts index 0c57aa8eb..30e27c2dd 100644 --- a/src/components/image-editor/ImageCanvasStageControllerModel.ts +++ b/src/components/image-editor/ImageCanvasStageControllerModel.ts @@ -1,7 +1,3 @@ -import { - getCanvasLayersByIds, - resolveContextTargetLayerIds, -} from './ImageCanvasLayerCommandModel'; import { resolveContextMenuPosition, } from './ImageCanvasEditorModel'; @@ -12,6 +8,10 @@ import type { CanvasViewport, ImageContextMenuState, } from './ImageCanvasEditorTypes'; +import { + getCanvasLayersByIds, + resolveContextTargetLayerIds, +} from './ImageCanvasLayerCommandModel'; import { resolveSelectedToolbarStyle, } from './ImageCanvasOverlayModel'; diff --git a/src/components/image-editor/ImageCanvasStageInteractionModel.test.ts b/src/components/image-editor/ImageCanvasStageInteractionModel.test.ts index 9d7098514..40625737c 100644 --- a/src/components/image-editor/ImageCanvasStageInteractionModel.test.ts +++ b/src/components/image-editor/ImageCanvasStageInteractionModel.test.ts @@ -5,10 +5,11 @@ import type { CanvasLayer, GenerateDialogState, } from './ImageCanvasEditorTypes'; +import { getCanvasGenerationSelectionId } from './ImageCanvasSelectionModel'; import { - createGenerationFrameSelectionStart, createCanvasMarqueeState, createGenerationFrameDragState, + createGenerationFrameSelectionStart, createLayerDragStart, createMinimapDragState, createPanDragState, @@ -21,7 +22,6 @@ import { updateGenerateDialogForLayerPointerDown, updateMinimapDragMovement, } from './ImageCanvasStageInteractionModel'; -import { getCanvasGenerationSelectionId } from './ImageCanvasSelectionModel'; function createLayer(overrides: Partial = {}): CanvasLayer { const id = overrides.id ?? 'layer-a'; diff --git a/src/components/image-editor/ImageCanvasStageView.tsx b/src/components/image-editor/ImageCanvasStageView.tsx index 5ce65dd5d..4db4a56f2 100644 --- a/src/components/image-editor/ImageCanvasStageView.tsx +++ b/src/components/image-editor/ImageCanvasStageView.tsx @@ -7,8 +7,8 @@ import type { RefObject, } from 'react'; -import type { ExternalGenerationTaskRecord } from '../../../packages/shared/src/contracts/externalGeneration'; import type { EditorAgentGenerationResultEvent } from '../../../packages/shared/src/contracts/editorAgent'; +import type { ExternalGenerationTaskRecord } from '../../../packages/shared/src/contracts/externalGeneration'; import { EditorAgentConversationPanelView } from './EditorAgentConversationPanelView'; import { ImageCanvasBottomToolbarView } from './ImageCanvasBottomToolbarView'; import { ImageCanvasContextMenusView } from './ImageCanvasContextMenusView'; diff --git a/src/components/image-editor/ImageCanvasTaskSidebarView.tsx b/src/components/image-editor/ImageCanvasTaskSidebarView.tsx index 06655f49c..7dcbf6c7a 100644 --- a/src/components/image-editor/ImageCanvasTaskSidebarView.tsx +++ b/src/components/image-editor/ImageCanvasTaskSidebarView.tsx @@ -6,13 +6,13 @@ import { XCircle, } from 'lucide-react'; import { - type WheelEvent as ReactWheelEvent, type UIEvent as ReactUIEvent, useCallback, useEffect, useMemo, useRef, useState, + type WheelEvent as ReactWheelEvent, } from 'react'; import type { ExternalGenerationTaskRecord } from '../../../packages/shared/src/contracts/externalGeneration'; diff --git a/src/components/image-editor/ImageCanvasUploadModel.ts b/src/components/image-editor/ImageCanvasUploadModel.ts index f81aeb71f..fed0c5e1d 100644 --- a/src/components/image-editor/ImageCanvasUploadModel.ts +++ b/src/components/image-editor/ImageCanvasUploadModel.ts @@ -1,15 +1,15 @@ +import { resolvePreferredAssetUploadFolder } from './ImageCanvasAssetLibraryModel'; import { resolveLayerResolutionSize } from './ImageCanvasEditorModel'; import type { - CharacterReferenceImage, - CanvasMediaType, CanvasLayer, + CanvasMediaType, CanvasViewport, + CharacterReferenceImage, EditorAsset, EditorAssetFolder, GenerateDialogState, QuickEditPanelState, } from './ImageCanvasEditorTypes'; -import { resolvePreferredAssetUploadFolder } from './ImageCanvasAssetLibraryModel'; import { appendLimitedQuickEditReferences } from './ImageCanvasGenerationModel'; type CanvasSize = { width: number; height: number }; diff --git a/src/components/image-editor/ImageCanvasWorldView.tsx b/src/components/image-editor/ImageCanvasWorldView.tsx index a28d6401b..99fafa4fd 100644 --- a/src/components/image-editor/ImageCanvasWorldView.tsx +++ b/src/components/image-editor/ImageCanvasWorldView.tsx @@ -8,20 +8,20 @@ import { Megaphone, Music, Pause, - Play, PersonStanding, + Play, Sparkles, UserRound, Volume2, VolumeX, } from 'lucide-react'; -import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { CSSProperties, KeyboardEvent as ReactKeyboardEvent, MouseEvent as ReactMouseEvent, PointerEvent as ReactPointerEvent, } from 'react'; +import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import { useResolvedAssetReadUrl } from '../../hooks/useResolvedAssetReadUrl'; import { diff --git a/src/components/image-editor/useCanvasGenerationDialogs.test.tsx b/src/components/image-editor/useCanvasGenerationDialogs.test.tsx index e845a4a4c..fba896056 100644 --- a/src/components/image-editor/useCanvasGenerationDialogs.test.tsx +++ b/src/components/image-editor/useCanvasGenerationDialogs.test.tsx @@ -1,6 +1,6 @@ /* @vitest-environment jsdom */ -import { renderHook, act } from '@testing-library/react'; +import { act,renderHook } from '@testing-library/react'; import { describe, expect, it, vi } from 'vitest'; import type { CanvasGenerationDialogState } from './ImageCanvasEditorTypes'; diff --git a/src/components/image-editor/useCanvasGenerationDialogs.ts b/src/components/image-editor/useCanvasGenerationDialogs.ts index 21ab352d5..c386825e7 100644 --- a/src/components/image-editor/useCanvasGenerationDialogs.ts +++ b/src/components/image-editor/useCanvasGenerationDialogs.ts @@ -7,11 +7,11 @@ import { useState, } from 'react'; -import { isCanvasGenerationDialog } from './ImageCanvasGenerationModel'; import type { CanvasGenerationDialogState, GenerateDialogState, } from './ImageCanvasEditorTypes'; +import { isCanvasGenerationDialog } from './ImageCanvasGenerationModel'; type CanvasGenerationDialogUpdater = ( dialog: CanvasGenerationDialogState, diff --git a/src/components/image-editor/useEditorAgentConversation.ts b/src/components/image-editor/useEditorAgentConversation.ts index 76401dfff..1258a42c4 100644 --- a/src/components/image-editor/useEditorAgentConversation.ts +++ b/src/components/image-editor/useEditorAgentConversation.ts @@ -5,8 +5,8 @@ import type { EditorAgentAttachmentRef, EditorAgentConversationDetail, EditorAgentConversationSummary, - EditorAgentGenerationResultEvent, EditorAgentGenerationRecord, + EditorAgentGenerationResultEvent, EditorAgentMessage, EditorAgentSseEvent, EditorAgentStage, diff --git a/src/components/image-editor/useImageCanvasAssetExportWorkflow.ts b/src/components/image-editor/useImageCanvasAssetExportWorkflow.ts index 0e6245e4b..c011b1c54 100644 --- a/src/components/image-editor/useImageCanvasAssetExportWorkflow.ts +++ b/src/components/image-editor/useImageCanvasAssetExportWorkflow.ts @@ -7,8 +7,8 @@ import type { CanvasLayer, } from './ImageCanvasEditorTypes'; import { - buildAnimatedGifPreviewBytes, blobToUint8Array, + buildAnimatedGifPreviewBytes, buildLayerExportMetadata, buildLayerVisibleExportMetadata, buildSpineImageSequenceJson, diff --git a/src/components/image-editor/useImageCanvasAssetLibrary.ts b/src/components/image-editor/useImageCanvasAssetLibrary.ts index db016cb2a..f9a5cc191 100644 --- a/src/components/image-editor/useImageCanvasAssetLibrary.ts +++ b/src/components/image-editor/useImageCanvasAssetLibrary.ts @@ -19,11 +19,6 @@ import { updateEditorAsset, updateEditorAssetFolder, } from '../../services/image-editor/editorProjectClient'; -import { - EDITOR_ASSET_FOLDERS, - escapeCssIdentifier, - normalizeAssetLibrary, -} from './ImageCanvasEditorModel'; import { areAllSelectableAssetsSelected, createAssetMarqueeFromPointer, @@ -32,23 +27,28 @@ import { deleteAssetFolderLocally, getSelectableAssets, groupAssetsByFolder, - moveAssetToFolderLocally, moveAssetMarqueeToPointer, + moveAssetToFolderLocally, removeAssetById, removeSelectedAssets, renameAssetById, renameAssetFolderById, replaceLocalAssetFolder, - resolveAssetShowcaseSubmissionState, resolveAllAssetSelection, - resolveDefaultAssetFolder, resolveAssetFolderIdFromPoint, + resolveAssetShowcaseSubmissionState, + resolveDefaultAssetFolder, resolvePinnedAssetMoveFolderId, selectUploadedAssetsInRect, shouldStartAssetMarquee, toggleAssetFolderCollapsed, toggleAssetSelection, } from './ImageCanvasAssetLibraryModel'; +import { + EDITOR_ASSET_FOLDERS, + escapeCssIdentifier, + normalizeAssetLibrary, +} from './ImageCanvasEditorModel'; import type { AssetMarqueeState, AssetPointerDragState, diff --git a/src/components/image-editor/useImageCanvasCanvasDropWorkflow.ts b/src/components/image-editor/useImageCanvasCanvasDropWorkflow.ts index 48f961876..224ebee45 100644 --- a/src/components/image-editor/useImageCanvasCanvasDropWorkflow.ts +++ b/src/components/image-editor/useImageCanvasCanvasDropWorkflow.ts @@ -1,6 +1,6 @@ import { - type DragEvent as ReactDragEvent, type Dispatch, + type DragEvent as ReactDragEvent, type SetStateAction, useCallback, useMemo, diff --git a/src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx b/src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx index 441f8d784..0e158971c 100644 --- a/src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx +++ b/src/components/image-editor/useImageCanvasGenerationSubmissionWorkflow.test.tsx @@ -189,6 +189,7 @@ function SubmissionWorkflowHarness({ initialCharacterAnimationPanel = null, initialLayers = [createLayer()], projectId = undefined, + assetFolderId = projectId ? 'project' : undefined, upsertGeneratedAsset = undefined, applyProjectSnapshot = undefined, onQueuedGenerationTask = undefined, @@ -200,6 +201,7 @@ function SubmissionWorkflowHarness({ initialCharacterAnimationPanel?: CharacterAnimationPanelState | null; initialLayers?: CanvasLayer[]; projectId?: string | null; + assetFolderId?: string | null; upsertGeneratedAsset?: Parameters< typeof useImageCanvasGenerationSubmissionWorkflow >[0]['upsertGeneratedAsset']; @@ -283,7 +285,7 @@ function SubmissionWorkflowHarness({ rememberedImageModelRef.current = imageModel; }, projectId, - assetFolderId: projectId ? 'project' : undefined, + assetFolderId, upsertGeneratedAsset, applyProjectSnapshot, onQueuedGenerationTask, @@ -2709,6 +2711,63 @@ describe('useImageCanvasGenerationSubmissionWorkflow', () => { }); }); + it('uses updated character animation folder and queue callbacks after rerender', async () => { + const firstQueuedTaskCallback = vi.fn(); + const latestQueuedTaskCallback = vi.fn(); + generateEditorCharacterAnimationMock.mockResolvedValueOnce({ + taskId: 'animation-task-queued', + queueState: createQueueState({ status: 'queued' }), + project: { + id: 'editor-project-1', + }, + }); + const harnessProps = { + projectId: 'editor-project-1', + initialLayers: [ + createLayer({ + id: 'character-layer', + assetKind: 'character' as const, + objectKey: 'generated/character.png', + }), + ], + initialCharacterAnimationPanel: { + sourceLayerId: 'character-layer', + promptText: '跑步', + resolution: '480p' as const, + ratio: 'same' as const, + frameCount: 32 as const, + durationSeconds: 4 as const, + status: 'idle' as const, + }, + }; + const { rerender } = render( + , + ); + + rerender( + , + ); + fireEvent.click(screen.getByRole('button', { name: '提交角色动画' })); + + await waitFor(() => { + expect(generateEditorCharacterAnimationMock).toHaveBeenCalledWith( + expect.objectContaining({ assetFolderId: 'folder-latest' }), + ); + }); + await waitFor(() => { + expect(latestQueuedTaskCallback).toHaveBeenCalledTimes(1); + }); + expect(firstQueuedTaskCallback).not.toHaveBeenCalled(); + }); + it('does not resubmit character animation while it is already generating', () => { render( layer.id === quickEditPanel.sourceLayerId) ?? @@ -1952,6 +1947,7 @@ export function useImageCanvasGenerationWorkflow({ setCropExpandPanel, setMetadataLayer, setQuickEditPanel, + setViewport, ], ); @@ -2581,7 +2577,6 @@ export function useImageCanvasGenerationWorkflow({ updateIconDescriptionsText, updateSpecFormValue, rememberImageModel, - refreshTaskListForQueuedGeneration, ], ); } diff --git a/src/components/image-editor/useImageCanvasLayerCommands.ts b/src/components/image-editor/useImageCanvasLayerCommands.ts index f19c552eb..cc4b25873 100644 --- a/src/components/image-editor/useImageCanvasLayerCommands.ts +++ b/src/components/image-editor/useImageCanvasLayerCommands.ts @@ -12,6 +12,7 @@ import type { CanvasGenerationDialogState, CanvasLayer, } from './ImageCanvasEditorTypes'; +import { readLayerImageBlob } from './ImageCanvasExportModel'; import { type CanvasLayerFlipAxis, type CanvasLayerMoveMode, @@ -28,7 +29,6 @@ import { ungroupCanvasLayers, updateCanvasLayersByIds, } from './ImageCanvasLayerCommandModel'; -import { readLayerImageBlob } from './ImageCanvasExportModel'; import { getCanvasGenerationDialogIdFromSelectionId, getSelectedLayerIds, diff --git a/src/components/image-editor/useImageCanvasProjectPersistence.test.tsx b/src/components/image-editor/useImageCanvasProjectPersistence.test.tsx index 6ad31b075..5ea1a2fa9 100644 --- a/src/components/image-editor/useImageCanvasProjectPersistence.test.tsx +++ b/src/components/image-editor/useImageCanvasProjectPersistence.test.tsx @@ -6,15 +6,15 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { ApiClientError } from '../../services/apiClient'; import type { EditorProjectSnapshot } from '../../services/image-editor/editorProjectClient'; +import { + DEFAULT_CANVAS_BACKGROUND_COLOR, + normalizeCanvasBackgroundHex, +} from './ImageCanvasEditorModel'; import type { CanvasGenerationDialogState, CanvasLayer, CanvasViewport, } from './ImageCanvasEditorTypes'; -import { - DEFAULT_CANVAS_BACKGROUND_COLOR, - normalizeCanvasBackgroundHex, -} from './ImageCanvasEditorModel'; import { useImageCanvasProjectPersistence } from './useImageCanvasProjectPersistence'; const createEditorProjectResourceMock = vi.hoisted(() => vi.fn()); diff --git a/src/components/image-editor/useImageCanvasProjectPersistence.ts b/src/components/image-editor/useImageCanvasProjectPersistence.ts index dd2ad92de..f956d5e6c 100644 --- a/src/components/image-editor/useImageCanvasProjectPersistence.ts +++ b/src/components/image-editor/useImageCanvasProjectPersistence.ts @@ -6,10 +6,9 @@ import { useState, } from 'react'; -import { ApiClientError } from '../../services/apiClient'; import { replaceAppHistoryPath } from '../../routing/appPageRoutes'; +import { ApiClientError } from '../../services/apiClient'; import { uploadEditorMediaAssetFile } from '../../services/image-editor/editorMediaAssetUploadClient'; -import { putEditorProjectCoverCache } from '../../services/image-editor/editorProjectCoverCache'; import { createEditorProjectResource, type EditorProjectResourceSnapshot, @@ -18,6 +17,7 @@ import { loadOrCreateRecentEditorProject, saveEditorProjectLayout, } from '../../services/image-editor/editorProjectClient'; +import { putEditorProjectCoverCache } from '../../services/image-editor/editorProjectCoverCache'; import { canvasDisplayViewportToViewport, type CanvasLayerResourceMetadata, diff --git a/src/components/image-editor/useImageCanvasStageController.ts b/src/components/image-editor/useImageCanvasStageController.ts index 64ff9c46e..acee21d3e 100644 --- a/src/components/image-editor/useImageCanvasStageController.ts +++ b/src/components/image-editor/useImageCanvasStageController.ts @@ -13,13 +13,13 @@ import type { CanvasViewport, ImageContextMenuState, } from './ImageCanvasEditorTypes'; +import { getCanvasGenerationSelectionId } from './ImageCanvasSelectionModel'; import { createBlankCanvasContextMenu, createGenerationDialogCanvasContextMenu, createLayerCanvasContextMenus, resolveImageCanvasStageControllerModel, } from './ImageCanvasStageControllerModel'; -import { getCanvasGenerationSelectionId } from './ImageCanvasSelectionModel'; type UseImageCanvasStageControllerOptions = { layers: CanvasLayer[]; diff --git a/src/components/image-editor/useImageCanvasStageInteractions.test.tsx b/src/components/image-editor/useImageCanvasStageInteractions.test.tsx index 33f12ad48..0db87d40a 100644 --- a/src/components/image-editor/useImageCanvasStageInteractions.test.tsx +++ b/src/components/image-editor/useImageCanvasStageInteractions.test.tsx @@ -1,6 +1,6 @@ /* @vitest-environment jsdom */ -import { act, fireEvent, render, screen } from '@testing-library/react'; +import { act, render, screen } from '@testing-library/react'; import { type PointerEvent as ReactPointerEvent, useRef, @@ -14,8 +14,8 @@ import type { CanvasViewport, GenerateDialogState, } from './ImageCanvasEditorTypes'; -import { useImageCanvasStageInteractions } from './useImageCanvasStageInteractions'; import { getCanvasGenerationSelectionId } from './ImageCanvasSelectionModel'; +import { useImageCanvasStageInteractions } from './useImageCanvasStageInteractions'; function createLayer(overrides: Partial): CanvasLayer { const id = overrides.id ?? 'layer-a'; diff --git a/src/components/image-editor/useImageCanvasStageInteractions.ts b/src/components/image-editor/useImageCanvasStageInteractions.ts index f6054e3e9..1b94876b2 100644 --- a/src/components/image-editor/useImageCanvasStageInteractions.ts +++ b/src/components/image-editor/useImageCanvasStageInteractions.ts @@ -1,7 +1,7 @@ import { type Dispatch, - type PointerEvent as ReactPointerEvent, type MouseEvent as ReactMouseEvent, + type PointerEvent as ReactPointerEvent, type RefObject, type SetStateAction, useCallback, @@ -10,9 +10,19 @@ import { useState, } from 'react'; +import type { + CanvasGenerationDialogState, + CanvasLayer, + CanvasMarqueeState, + CanvasTool, + CanvasViewport, + DragState, + GenerateDialogState, + SnapGuide, +} from './ImageCanvasEditorTypes'; import { - moveGenerationFramesFromDrag, moveDragSelectedLayers, + moveGenerationFramesFromDrag, moveLayersFromDrag, moveViewportFromPan, selectCanvasObjectsInsideMarquee, @@ -32,16 +42,6 @@ import { updateGenerateDialogForLayerPointerDown, updateMinimapDragMovement, } from './ImageCanvasStageInteractionModel'; -import type { - CanvasGenerationDialogState, - CanvasLayer, - CanvasMarqueeState, - CanvasTool, - CanvasViewport, - DragState, - GenerateDialogState, - SnapGuide, -} from './ImageCanvasEditorTypes'; const CLICK_COLLAPSE_MOVEMENT_THRESHOLD_PX = 3; @@ -399,6 +399,7 @@ export function useImageCanvasStageInteractions({ isPickingQuickEditReferenceFromCanvas, isPickingCharacterReferenceFromCanvas, isPickingIconSpecFromCanvas, + isPickingUiDesignSpecFromCanvas, isPickingPublicationReferenceFromCanvas, layers, pickGenerationReferenceFromLayer, diff --git a/src/components/image-editor/useImageCanvasUploadWorkflow.ts b/src/components/image-editor/useImageCanvasUploadWorkflow.ts index fc718e6af..0c005d430 100644 --- a/src/components/image-editor/useImageCanvasUploadWorkflow.ts +++ b/src/components/image-editor/useImageCanvasUploadWorkflow.ts @@ -15,9 +15,14 @@ import { createEditorProjectResource, } from '../../services/image-editor/editorProjectClient'; import { uploadEditorSeedanceReferenceFile } from '../../services/image-editor/editorReferenceUploadClient'; +import { + EDITOR_UPLOAD_ASSET_FOLDER_LABEL, + resolvePreferredAssetUploadFolder, + resolveUploadAssetFolder, +} from './ImageCanvasAssetLibraryModel'; import type { - CanvasMediaType, CanvasLayer, + CanvasMediaType, CanvasTool, CanvasViewport, CharacterReferenceImage, @@ -28,28 +33,23 @@ import type { UploadTarget, } from './ImageCanvasEditorTypes'; import { - EDITOR_UPLOAD_ASSET_FOLDER_LABEL, - resolvePreferredAssetUploadFolder, - resolveUploadAssetFolder, -} from './ImageCanvasAssetLibraryModel'; -import { - SEEDANCE_REFERENCE_FILE_LIMITS, isImageFile, - resolveEditorAssetUploadMediaType, isSeedanceReferenceAudioFile, isSeedanceReferenceImageFile, isSeedanceReferenceVideoFile, probeImageFileDimensions, - probeMediaFileMetadata, probeMediaDurationSeconds, + probeMediaFileMetadata, readFileAsDataUrl, readImageFileAsDataUrl, + resolveEditorAssetUploadMediaType, + SEEDANCE_REFERENCE_FILE_LIMITS, } from './ImageCanvasFileModel'; import { applyGenerationReferenceUpload, - applyQuickEditReferenceUpload, applyMeasuredUploadAssetSize, applyPersistedUploadAsset, + applyQuickEditReferenceUpload, applyUploadAssetCreateFailure, applyUploadAssetCreatePending, applyUploadAssetReadFailure, @@ -1113,16 +1113,13 @@ export function useImageCanvasUploadWorkflow({ activeUploadFolderId, appendCanvasLayersWithResources, assetFolders, - canvasSize.height, - canvasSize.width, + canvasSize, openEditorLoginModal, selectSingleLayer, setAssetFolders, setAssets, setLayers, - viewport.scale, - viewport.x, - viewport.y, + viewport, ], ); diff --git a/src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx b/src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx index 23281f943..b3da7337b 100644 --- a/src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx +++ b/src/components/jump-hop-runtime/JumpHopRuntimeShell.test.tsx @@ -10,22 +10,22 @@ import type { import { useResolvedAssetReadUrl } from '../../hooks/useResolvedAssetReadUrl'; import { useJumpHopLeaderboard } from '../../services/jump-hop/useJumpHopLeaderboard'; import { - JUMP_HOP_THREE_CAMERA_UP_Y, - JUMP_HOP_THREE_CHARACTER_RENDER_ORDER, - JUMP_HOP_THREE_CHARACTER_TOP_FACE_Z_OFFSET, - JUMP_HOP_THREE_MATERIAL_FACE_ORDER, - JUMP_HOP_THREE_PLATFORM_MATERIAL_TRANSPARENT, - JUMP_HOP_THREE_PLATFORM_MESH_RENDER_ORDER_BASE, - JUMP_HOP_THREE_PLATFORM_YAW_RAD, - JUMP_HOP_THREE_PLATFORM_CUBE_SIZE_MULTIPLIER, - JUMP_HOP_THREE_TEXTURE_TRANSFORMS, - JumpHopRuntimeShell, getJumpHopThreeCharacterFootZ, getJumpHopThreeCubeSide, getJumpHopThreeProjectedY, getJumpHopThreeWorldYForScreenY, getJumpHopTileTextureSignature, hasCompleteJumpHopTileFaceAssets, + JUMP_HOP_THREE_CAMERA_UP_Y, + JUMP_HOP_THREE_CHARACTER_RENDER_ORDER, + JUMP_HOP_THREE_CHARACTER_TOP_FACE_Z_OFFSET, + JUMP_HOP_THREE_MATERIAL_FACE_ORDER, + JUMP_HOP_THREE_PLATFORM_CUBE_SIZE_MULTIPLIER, + JUMP_HOP_THREE_PLATFORM_MATERIAL_TRANSPARENT, + JUMP_HOP_THREE_PLATFORM_MESH_RENDER_ORDER_BASE, + JUMP_HOP_THREE_PLATFORM_YAW_RAD, + JUMP_HOP_THREE_TEXTURE_TRANSFORMS, + JumpHopRuntimeShell, resolveJumpHopThreeCharacterFrame, } from './JumpHopRuntimeShell'; diff --git a/src/components/match3d-result/Match3DResultView.tsx b/src/components/match3d-result/Match3DResultView.tsx index 2a8d90403..76edece1a 100644 --- a/src/components/match3d-result/Match3DResultView.tsx +++ b/src/components/match3d-result/Match3DResultView.tsx @@ -60,8 +60,8 @@ import { readPuzzleReferenceImageAsDataUrl, } from '../../services/puzzleReferenceImage'; import { PlatformActionButton } from '../common/PlatformActionButton'; -import { PlatformBackActionButton } from '../common/PlatformBackActionButton'; import { PlatformAssetPickerGrid } from '../common/PlatformAssetPickerCard'; +import { PlatformBackActionButton } from '../common/PlatformBackActionButton'; import { PlatformFieldLabel } from '../common/PlatformFieldLabel'; import { PlatformIconButton } from '../common/PlatformIconButton'; import { PlatformMediaFrame } from '../common/PlatformMediaFrame'; diff --git a/src/components/match3d-runtime/match3dHotspot.test.ts b/src/components/match3d-runtime/match3dHotspot.test.ts index fef6d646e..137ee7489 100644 --- a/src/components/match3d-runtime/match3dHotspot.test.ts +++ b/src/components/match3d-runtime/match3dHotspot.test.ts @@ -4,8 +4,8 @@ import type { Match3DRunSnapshot } from '../../../packages/shared/src/contracts/ import { findMatch3DHitItem, isPointInsideMatch3DItemAlphaHotspot, - resolveMatch3DItemSizeScale, type Match3DResolvedImageSourceEntry, + resolveMatch3DItemSizeScale, } from './match3dHotspot'; import { resolveRenderableItemFrame } from './match3dRuntimePresentation'; diff --git a/src/components/platform-entry/PlatformEntryFlowShellImpl/PuzzleOnboardingView.test.tsx b/src/components/platform-entry/PlatformEntryFlowShellImpl/PuzzleOnboardingView.test.tsx index e8170da18..fdf784ac3 100644 --- a/src/components/platform-entry/PlatformEntryFlowShellImpl/PuzzleOnboardingView.test.tsx +++ b/src/components/platform-entry/PlatformEntryFlowShellImpl/PuzzleOnboardingView.test.tsx @@ -11,8 +11,8 @@ import { expect, test, vi } from 'vitest'; import { PuzzleOnboardingLoginOverlay, - PuzzleOnboardingView, type PuzzleOnboardingPhase, + PuzzleOnboardingView, } from './PuzzleOnboardingView'; function renderOnboarding({ diff --git a/src/components/platform-entry/PlatformProfilePlayedWorksModal.tsx b/src/components/platform-entry/PlatformProfilePlayedWorksModal.tsx index 366d3dcec..011b25fc7 100644 --- a/src/components/platform-entry/PlatformProfilePlayedWorksModal.tsx +++ b/src/components/platform-entry/PlatformProfilePlayedWorksModal.tsx @@ -8,13 +8,12 @@ import type { import { PlatformAsyncStatePanel } from '../common/PlatformAsyncStatePanel'; import { PlatformEmptyState } from '../common/PlatformEmptyState'; import { PlatformFieldLabel } from '../common/PlatformFieldLabel'; +import { PlatformPillBadge } from '../common/PlatformPillBadge'; import { PlatformProfileContentRow } from '../common/PlatformProfileContentRow'; import { PlatformProfileSkeletonList } from '../common/PlatformProfileSkeletonList'; import { PlatformProfileSummaryHeader } from '../common/PlatformProfileSummaryHeader'; -import { PlatformPillBadge } from '../common/PlatformPillBadge'; import { PlatformStatusMessage } from '../common/PlatformStatusMessage'; import { ResolvedAssetImage } from '../ResolvedAssetImage'; -import { PlatformProfileSecondaryModalShell } from './PlatformProfileModalShell'; import { formatCompactPlayTime, formatPlayedWorkId, @@ -23,6 +22,7 @@ import { formatTotalPlayTimeHours, } from '../rpg-entry/rpgEntryProfileDashboardPresentation'; import { formatPlatformWorkDisplayName } from '../rpg-entry/rpgEntryWorldPresentation'; +import { PlatformProfileSecondaryModalShell } from './PlatformProfileModalShell'; type PlatformProfilePlayedWorksModalProps = { stats: ProfilePlayStatsResponse | null; diff --git a/src/components/platform-entry/PlatformProfileQrScannerModal.tsx b/src/components/platform-entry/PlatformProfileQrScannerModal.tsx index a59c587ab..1c998146d 100644 --- a/src/components/platform-entry/PlatformProfileQrScannerModal.tsx +++ b/src/components/platform-entry/PlatformProfileQrScannerModal.tsx @@ -1,8 +1,8 @@ import { useEffect, useRef } from 'react'; +import { scanHostQrCode } from '../../services/host-bridge/hostBridge'; import { PlatformModalCloseButton } from '../common/PlatformModalCloseButton'; import { PlatformStatusMessage } from '../common/PlatformStatusMessage'; -import { scanHostQrCode } from '../../services/host-bridge/hostBridge'; import { PlatformProfileModalShell } from './PlatformProfileModalShell'; const PROFILE_QR_SCAN_INTERVAL_MS = 360; @@ -165,7 +165,7 @@ export function PlatformProfileQrScannerModal({ clearScanTimer(); stopCamera(); }; - }, [mode, onError, onResult]); + }, [mode, onClose, onError, onResult]); return ( { it('keeps compile action generating until the draft has a cover image', () => { diff --git a/src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.tsx b/src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.tsx index a6dab42df..5bacf860a 100644 --- a/src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.tsx +++ b/src/components/puzzle-clear-runtime/PuzzleClearRuntimeShell.tsx @@ -1,8 +1,8 @@ import { ArrowLeft, ChevronRight, RotateCcw } from 'lucide-react'; import { type CSSProperties, - type ReactNode, type PointerEvent as ReactPointerEvent, + type ReactNode, useEffect, useMemo, useRef, diff --git a/src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.test.tsx b/src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.test.tsx index 12be18f4d..1d8255e89 100644 --- a/src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.test.tsx +++ b/src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModal.test.tsx @@ -4,8 +4,8 @@ import { render, screen, waitFor, within } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { afterEach, expect, test, vi } from 'vitest'; -import type { EditableCustomWorldRole } from './roleAssetStudioModel'; import * as hostBridgeServices from '../../services/host-bridge/hostBridge'; +import type { EditableCustomWorldRole } from './roleAssetStudioModel'; import { RpgCreationRoleAssetStudioModal } from './RpgCreationRoleAssetStudioModal'; const generateVisualCandidatesForRole = vi.fn(); diff --git a/src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModalImpl.tsx b/src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModalImpl.tsx index 368d864d4..6b3e4f31e 100644 --- a/src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModalImpl.tsx +++ b/src/components/rpg-creation-asset-studio/RpgCreationRoleAssetStudioModalImpl.tsx @@ -8,6 +8,11 @@ import { import { createPortal } from 'react-dom'; import { ROLE_TEMPLATE_CHARACTERS } from '../../data/characterPresets'; +import { + canUseNativeHostCapability, + importHostImageFile, +} from '../../services/host-bridge/hostBridge'; +import { puzzleReferenceImageDataUrlToFile } from '../../services/puzzleReferenceImage'; import { AnimationState, type Character, @@ -25,11 +30,6 @@ import { useAuthUi } from '../auth/AuthUiContext'; import { CharacterAnimator } from '../CharacterAnimator'; import { PlatformActionButton } from '../common/PlatformActionButton'; import { PlatformMudPointConfirmDialog } from '../common/PlatformMudPointConfirmDialog'; -import { - canUseNativeHostCapability, - importHostImageFile, -} from '../../services/host-bridge/hostBridge'; -import { puzzleReferenceImageDataUrlToFile } from '../../services/puzzleReferenceImage'; import { CORE_ACTIONS, type CustomWorldAiActionConfig, diff --git a/src/components/rpg-creation-asset-studio/useRoleAnimationWorkflow.ts b/src/components/rpg-creation-asset-studio/useRoleAnimationWorkflow.ts index fa5cd94c4..ce3fc547e 100644 --- a/src/components/rpg-creation-asset-studio/useRoleAnimationWorkflow.ts +++ b/src/components/rpg-creation-asset-studio/useRoleAnimationWorkflow.ts @@ -1,11 +1,10 @@ -import { generateCharacterAnimationDraft } from '../asset-studio/characterAssetWorkflowPersistence'; import type { CharacterAnimationGenerationPayload } from '../asset-studio/characterAssetWorkflowPersistence'; - -import { roleAssetStudioPublishClient } from './roleAssetStudioPublishClient'; +import { generateCharacterAnimationDraft } from '../asset-studio/characterAssetWorkflowPersistence'; import type { CustomWorldAiActionConfig, EditableCustomWorldRole, } from './roleAssetStudioModel'; +import { roleAssetStudioPublishClient } from './roleAssetStudioPublishClient'; export function useRoleAnimationWorkflow() { const generateAnimationClipForRole = async (params: { diff --git a/src/components/rpg-creation-asset-studio/useRoleVisualCandidateWorkflow.ts b/src/components/rpg-creation-asset-studio/useRoleVisualCandidateWorkflow.ts index 576c6c5e2..6c45ee798 100644 --- a/src/components/rpg-creation-asset-studio/useRoleVisualCandidateWorkflow.ts +++ b/src/components/rpg-creation-asset-studio/useRoleVisualCandidateWorkflow.ts @@ -1,8 +1,7 @@ -import { generateCharacterVisualCandidates } from '../asset-studio/characterAssetWorkflowPersistence'; import type { CharacterVisualDraft } from '../asset-studio/characterAssetWorkflowPersistence'; - -import { roleAssetStudioPublishClient } from './roleAssetStudioPublishClient'; +import { generateCharacterVisualCandidates } from '../asset-studio/characterAssetWorkflowPersistence'; import type { EditableCustomWorldRole } from './roleAssetStudioModel'; +import { roleAssetStudioPublishClient } from './roleAssetStudioPublishClient'; export function useRoleVisualCandidateWorkflow() { const generateVisualCandidatesForRole = async (params: { diff --git a/src/components/rpg-creation-editor/RpgCreationEntityEditorModal.tsx b/src/components/rpg-creation-editor/RpgCreationEntityEditorModal.tsx index e48d8fe46..8c323fa73 100644 --- a/src/components/rpg-creation-editor/RpgCreationEntityEditorModal.tsx +++ b/src/components/rpg-creation-editor/RpgCreationEntityEditorModal.tsx @@ -1,9 +1,9 @@ import type { ComponentProps } from 'react'; -import RpgCreationEntityEditorModalImpl from './RpgCreationEntityEditorModalImpl'; import type { RpgCreationEditorTarget, } from './RpgCreationEntityEditorModalImpl'; +import RpgCreationEntityEditorModalImpl from './RpgCreationEntityEditorModalImpl'; /** * 工作包 C 完成后,编辑器 façade 已直接桥接 RPG 创作目录下的目标分发壳层。 diff --git a/src/components/rpg-creation-editor/RpgCreationEntityEditorModalImpl.tsx b/src/components/rpg-creation-editor/RpgCreationEntityEditorModalImpl.tsx index be0147d72..3c16825f5 100644 --- a/src/components/rpg-creation-editor/RpgCreationEntityEditorModalImpl.tsx +++ b/src/components/rpg-creation-editor/RpgCreationEntityEditorModalImpl.tsx @@ -1,5 +1,4 @@ import type { CustomWorldProfile } from '../../types'; - import CampSceneEditor from './CustomWorldCampEditorSection'; import WorldCoverEditor from './CustomWorldCoverEditorSection'; import WorldFoundationEditor from './CustomWorldFoundationEditorSection'; diff --git a/src/components/rpg-creation-editor/RpgCreationEntityEditorShared.tsx b/src/components/rpg-creation-editor/RpgCreationEntityEditorShared.tsx index 3c4935c9a..9dde7aa10 100644 --- a/src/components/rpg-creation-editor/RpgCreationEntityEditorShared.tsx +++ b/src/components/rpg-creation-editor/RpgCreationEntityEditorShared.tsx @@ -56,16 +56,16 @@ import { getCustomWorldFoundationAnchorContent, parseFoundationTagText, } from '../../services/customWorldFoundationEntries'; -import { - rpgCreationAssetClient, - type RpgCreationHistoryAsset, - type RpgCreationHistoryAssetKind, -} from '../../services/rpg-creation/rpgCreationAssetClient'; import { canUseNativeHostCapability, importHostImageFile, } from '../../services/host-bridge/hostBridge'; import { puzzleReferenceImageDataUrlToFile } from '../../services/puzzleReferenceImage'; +import { + rpgCreationAssetClient, + type RpgCreationHistoryAsset, + type RpgCreationHistoryAssetKind, +} from '../../services/rpg-creation/rpgCreationAssetClient'; import { createEmptyStoryEngineMemoryState } from '../../services/storyEngine/visibilityEngine'; import { AnimationState, diff --git a/src/components/rpg-creation-editor/rpgCreationResultFormMapper.ts b/src/components/rpg-creation-editor/rpgCreationResultFormMapper.ts index 3faa29907..43609cb97 100644 --- a/src/components/rpg-creation-editor/rpgCreationResultFormMapper.ts +++ b/src/components/rpg-creation-editor/rpgCreationResultFormMapper.ts @@ -1,13 +1,13 @@ -import type { - RpgCreationEditorTarget, - RpgCreationEntityEditorModalProps, -} from './RpgCreationEntityEditorModalImpl'; import type { CustomWorldLandmark, CustomWorldNpc, CustomWorldPlayableNpc, CustomWorldProfile, } from '../../types'; +import type { + RpgCreationEditorTarget, + RpgCreationEntityEditorModalProps, +} from './RpgCreationEntityEditorModalImpl'; /** * 工作包 C 第一轮先把编辑器目标分发需要的 profile 变更收口到 mapper。 diff --git a/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx b/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx index 54db12230..19e2e7ede 100644 --- a/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx +++ b/src/components/rpg-entry/RpgEntryFlowShell.agent.interaction.test.tsx @@ -9,10 +9,6 @@ import type { PublicUserSummary } from '../../../packages/shared/src/contracts/a import type { BarkBattleWorkSummary } from '../../../packages/shared/src/contracts/barkBattle'; import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary'; import type { CreativeAgentSessionSnapshot } from '../../../packages/shared/src/contracts/creativeAgent'; -import type { - HostBridgeCapability, - HostBridgeRequest, -} from '../../../packages/shared/src/contracts/hostBridge'; import type { CustomWorldAgentSessionSnapshot, CustomWorldWorkSummary, @@ -21,6 +17,10 @@ import type { BabyObjectMatchDraft, CreateBabyObjectMatchDraftRequest, } from '../../../packages/shared/src/contracts/edutainmentBabyObject'; +import type { + HostBridgeCapability, + HostBridgeRequest, +} from '../../../packages/shared/src/contracts/hostBridge'; import type { JumpHopRuntimeRunSnapshotResponse, JumpHopWorkDetailResponse, diff --git a/src/components/rpg-entry/rpgEntryTypes.ts b/src/components/rpg-entry/rpgEntryTypes.ts index da76f6fff..5d4c71fd5 100644 --- a/src/components/rpg-entry/rpgEntryTypes.ts +++ b/src/components/rpg-entry/rpgEntryTypes.ts @@ -2,8 +2,8 @@ export type { CustomWorldAutoSaveState, CustomWorldGenerationViewSource, CustomWorldResultViewSource, - PlatformEntryFlowShellProps as RpgEntryFlowShellProps, PlatformEntryFlowShellProps as RpgCreationShellProps, + PlatformEntryFlowShellProps as RpgEntryFlowShellProps, SelectionStage, SyncedAgentDraftResult, } from '../platform-entry/platformEntryTypes'; diff --git a/src/components/rpg-entry/useRpgCreationEnterWorld.ts b/src/components/rpg-entry/useRpgCreationEnterWorld.ts index b05b079f0..ecf25d7eb 100644 --- a/src/components/rpg-entry/useRpgCreationEnterWorld.ts +++ b/src/components/rpg-entry/useRpgCreationEnterWorld.ts @@ -1,10 +1,10 @@ import { useCallback } from 'react'; -import type { RpgCreationResultView } from '../../../packages/shared/src/contracts/rpgCreationResultView'; import type { CustomWorldAgentSessionSnapshot } from '../../../packages/shared/src/contracts/customWorldAgent'; -import type { CustomWorldRuntimeLaunchOptions } from '../platform-entry/platformEntryTypes'; +import type { RpgCreationResultView } from '../../../packages/shared/src/contracts/rpgCreationResultView'; import { rpgCreationPreviewAdapter } from '../../services/rpg-creation/rpgCreationPreviewAdapter'; import type { CustomWorldProfile } from '../../types'; +import type { CustomWorldRuntimeLaunchOptions } from '../platform-entry/platformEntryTypes'; import { chooseMoreCompleteCustomWorldProfile } from './rpgProfileCompleteness'; type UseRpgCreationEnterWorldParams = { diff --git a/src/components/rpg-entry/useRpgEntryBootstrap.test.tsx b/src/components/rpg-entry/useRpgEntryBootstrap.test.tsx index 3d3a5525f..03081f90a 100644 --- a/src/components/rpg-entry/useRpgEntryBootstrap.test.tsx +++ b/src/components/rpg-entry/useRpgEntryBootstrap.test.tsx @@ -4,13 +4,13 @@ import { renderHook, waitFor } from '@testing-library/react'; import { beforeEach, expect, test, vi } from 'vitest'; import type { AuthUser } from '../../services/authService'; +import { listRpgCreationWorks } from '../../services/rpg-creation/index'; import { listRpgEntryWorldGallery, listRpgEntryWorldLibrary, listRpgProfileBrowseHistory, listRpgProfileSaveArchives, } from '../../services/rpg-entry'; -import { listRpgCreationWorks } from '../../services/rpg-creation/index'; import { useRpgEntryBootstrap } from './useRpgEntryBootstrap'; vi.mock('../../services/rpg-entry', () => ({ diff --git a/src/components/rpg-runtime-panels/RpgAdventurePanel.tsx b/src/components/rpg-runtime-panels/RpgAdventurePanel.tsx index 595a4cbf4..9a406bc4b 100644 --- a/src/components/rpg-runtime-panels/RpgAdventurePanel.tsx +++ b/src/components/rpg-runtime-panels/RpgAdventurePanel.tsx @@ -50,8 +50,8 @@ import { TAB_ICONS, UI_CHROME, } from '../../uiAssets'; -import { PlatformEmptyState } from '../common/PlatformEmptyState'; import { PlatformActionButton } from '../common/PlatformActionButton'; +import { PlatformEmptyState } from '../common/PlatformEmptyState'; import { PlatformPillBadge } from '../common/PlatformPillBadge'; import { PlatformQuantityBadge } from '../common/PlatformQuantityBadge'; import { PlatformSubpanel } from '../common/PlatformSubpanel'; diff --git a/src/components/rpg-runtime-shell/RpgRuntimeShell.test.tsx b/src/components/rpg-runtime-shell/RpgRuntimeShell.test.tsx index 8fb1a421b..41ed4659f 100644 --- a/src/components/rpg-runtime-shell/RpgRuntimeShell.test.tsx +++ b/src/components/rpg-runtime-shell/RpgRuntimeShell.test.tsx @@ -6,9 +6,9 @@ import { beforeEach, expect, test, vi } from 'vitest'; import { AnimationState, - WorldType, type GameState, type StoryMoment, + WorldType, } from '../../types'; import { RpgRuntimeShell } from './RpgRuntimeShell'; import type { RpgRuntimeShellProps } from './types'; diff --git a/src/components/rpg-runtime-shell/index.ts b/src/components/rpg-runtime-shell/index.ts index 84f64ee2f..77ee6bd00 100644 --- a/src/components/rpg-runtime-shell/index.ts +++ b/src/components/rpg-runtime-shell/index.ts @@ -2,6 +2,10 @@ export { RpgRuntimeCanvasStage, type RpgRuntimeCanvasStageProps, } from './RpgRuntimeCanvasStage'; +export { + ModalLoadingFallback, + PanelLoadingFallback, +} from './rpgRuntimeLoaders'; export { RpgRuntimeOverlayHost, type RpgRuntimeOverlayHostProps, @@ -18,19 +22,15 @@ export { type RpgAdventureStatistics, type RpgRuntimeDialogueIndicator, } from './types'; -export { - ModalLoadingFallback, - PanelLoadingFallback, -} from './rpgRuntimeLoaders'; -export { - useRpgRuntimeShellViewModel, - type RpgRuntimeShellViewModelResult, - type UseRpgRuntimeShellViewModelParams, -} from './useRpgRuntimeShellViewModel'; export { useRpgRuntimeOverlayState } from './useRpgRuntimeOverlayState'; +export { + type RpgRuntimeShellViewModelResult, + useRpgRuntimeShellViewModel, + type UseRpgRuntimeShellViewModelParams, +} from './useRpgRuntimeShellViewModel'; export { SCENE_TRANSITION_FUNCTION_MODES, - useRpgSceneTransitionModel, type SceneTransitionPhase, type SceneTransitionTriggerMode, + useRpgSceneTransitionModel, } from './useRpgSceneTransitionModel'; diff --git a/src/components/unified-creation/UnifiedCreationWorkspace.test.tsx b/src/components/unified-creation/UnifiedCreationWorkspace.test.tsx index 29e0e1cf3..f69ecc8a8 100644 --- a/src/components/unified-creation/UnifiedCreationWorkspace.test.tsx +++ b/src/components/unified-creation/UnifiedCreationWorkspace.test.tsx @@ -3,8 +3,8 @@ import { render, screen } from '@testing-library/react'; import { describe, expect, test, vi } from 'vitest'; -import { UnifiedCreationWorkspace } from './UnifiedCreationWorkspace'; import { getUnifiedCreationSpec } from './unifiedCreationSpecs'; +import { UnifiedCreationWorkspace } from './UnifiedCreationWorkspace'; vi.mock('./workspaces/PuzzleCreationWorkspace', () => ({ PuzzleCreationWorkspace: ({ diff --git a/src/components/unified-creation/UnifiedCreationWorkspace.tsx b/src/components/unified-creation/UnifiedCreationWorkspace.tsx index 697fafbd0..c0d5ba0e5 100644 --- a/src/components/unified-creation/UnifiedCreationWorkspace.tsx +++ b/src/components/unified-creation/UnifiedCreationWorkspace.tsx @@ -2,9 +2,9 @@ import type { ComponentProps } from 'react'; import { UnifiedCreationPage } from './UnifiedCreationPage'; import type { UnifiedCreationSpec } from './unifiedCreationSpecs'; +import { JumpHopCreationWorkspace } from './workspaces/JumpHopCreationWorkspace'; import { Match3DCreationWorkspace } from './workspaces/Match3DCreationWorkspace'; import { PuzzleCreationWorkspace } from './workspaces/PuzzleCreationWorkspace'; -import { JumpHopCreationWorkspace } from './workspaces/JumpHopCreationWorkspace'; import { WoodenFishCreationWorkspace } from './workspaces/WoodenFishCreationWorkspace'; type PuzzleCreationWorkspaceProps = ComponentProps< diff --git a/src/components/unified-creation/unifiedCreationSpecs.ts b/src/components/unified-creation/unifiedCreationSpecs.ts index 1c5f30e21..0468d81da 100644 --- a/src/components/unified-creation/unifiedCreationSpecs.ts +++ b/src/components/unified-creation/unifiedCreationSpecs.ts @@ -1,8 +1,8 @@ +import type { PlatformCreationTypeId } from '../../../packages/shared/src/contracts/playTypes'; import type { CreationEntryTypeConfig, UnifiedCreationSpec, } from '../../services/creationEntryConfigService'; -import type { PlatformCreationTypeId } from '../../../packages/shared/src/contracts/playTypes'; export const UNIFIED_CREATION_PLAY_IDS = [ 'rpg', diff --git a/src/components/unified-creation/workspaces/PuzzleCreationWorkspace.tsx b/src/components/unified-creation/workspaces/PuzzleCreationWorkspace.tsx index c42685795..5dac6b55b 100644 --- a/src/components/unified-creation/workspaces/PuzzleCreationWorkspace.tsx +++ b/src/components/unified-creation/workspaces/PuzzleCreationWorkspace.tsx @@ -17,7 +17,6 @@ import { CreativeImageInputPanel, type CreativeImageInputReferenceImage, } from '../../common/CreativeImageInputPanel'; -import { PlatformActionButton } from '../../common/PlatformActionButton'; import { PlatformBackActionButton } from '../../common/PlatformBackActionButton'; import { PlatformMudPointConfirmDialog } from '../../common/PlatformMudPointConfirmDialog'; import { PlatformPillBadge } from '../../common/PlatformPillBadge'; diff --git a/src/components/visual-novel-result/VisualNovelResultView.tsx b/src/components/visual-novel-result/VisualNovelResultView.tsx index 673c89881..6962e5790 100644 --- a/src/components/visual-novel-result/VisualNovelResultView.tsx +++ b/src/components/visual-novel-result/VisualNovelResultView.tsx @@ -46,8 +46,8 @@ import { } from '../../services/visual-novel-creation'; import { useAuthUi } from '../auth/AuthUiContext'; import { PlatformActionButton } from '../common/PlatformActionButton'; -import { PlatformBackActionButton } from '../common/PlatformBackActionButton'; import { PlatformAssetPickerGrid } from '../common/PlatformAssetPickerCard'; +import { PlatformBackActionButton } from '../common/PlatformBackActionButton'; import { PlatformEmptyState } from '../common/PlatformEmptyState'; import { PlatformFieldLabel } from '../common/PlatformFieldLabel'; import { PlatformIconButton } from '../common/PlatformIconButton'; @@ -56,12 +56,12 @@ import { PlatformModalCloseButton } from '../common/PlatformModalCloseButton'; import { PlatformSegmentedTabs } from '../common/PlatformSegmentedTabs'; import { PlatformStatusMessage } from '../common/PlatformStatusMessage'; import { PlatformSubpanel } from '../common/PlatformSubpanel'; -import { PlatformToolModalShell } from '../common/PlatformToolModalShell'; import { PlatformSelectField, PlatformTextField, } from '../common/PlatformTextField'; import { PlatformToggleRow } from '../common/PlatformToggleRow'; +import { PlatformToolModalShell } from '../common/PlatformToolModalShell'; import { visualNovelLocalPreviewDraft } from '../visual-novel-runtime/visualNovelLocalPreviewData'; type VisualNovelResultViewProps = { diff --git a/src/data/customWorldLibrary.ts b/src/data/customWorldLibrary.ts index 3b4971a73..cd7f55b1d 100644 --- a/src/data/customWorldLibrary.ts +++ b/src/data/customWorldLibrary.ts @@ -20,6 +20,7 @@ import { CharacterBackstoryChapter, CharacterBackstoryRevealConfig, CustomWorldAnchorPack, + CustomWorldCoverProfile, CustomWorldItem, CustomWorldLandmark, CustomWorldNpc, @@ -28,7 +29,6 @@ import { CustomWorldNpcVisualGearType, CustomWorldNpcVisualRace, CustomWorldOpeningCgProfile, - CustomWorldCoverProfile, CustomWorldPlayableNpc, CustomWorldProfile, CustomWorldRoleInitialItem, diff --git a/src/data/customWorldNpcMonsters.ts b/src/data/customWorldNpcMonsters.ts index 897002663..c21f4896a 100644 --- a/src/data/customWorldNpcMonsters.ts +++ b/src/data/customWorldNpcMonsters.ts @@ -2,13 +2,13 @@ import { collectCreatureArchetypeSignals, resolveCreatureArchetypeForSource, } from '../services/customWorldReferenceSignals'; +import { resolveCustomWorldCompatibilityTemplateWorldType } from '../services/customWorldTheme'; import { type CustomWorldNpc, type CustomWorldPlayableNpc, type CustomWorldProfile, WorldType, } from '../types'; -import { resolveCustomWorldCompatibilityTemplateWorldType } from '../services/customWorldTheme'; import { getMonsterPresetsByWorld, type HostileNpcPreset, diff --git a/src/data/customWorldVisuals.ts b/src/data/customWorldVisuals.ts index d81c304c2..420271c31 100644 --- a/src/data/customWorldVisuals.ts +++ b/src/data/customWorldVisuals.ts @@ -4,13 +4,13 @@ import { resolveSceneBucketForLandmark, } from '../services/customWorldReferenceSignals'; import { detectCustomWorldThemeMode } from '../services/customWorldTheme'; +import { resolveCustomWorldCompatibilityTemplateWorldType } from '../services/customWorldTheme'; import { type CustomWorldLandmark, type CustomWorldProfile, type WorldTemplateType, WorldType, } from '../types'; -import { resolveCustomWorldCompatibilityTemplateWorldType } from '../services/customWorldTheme'; const CUSTOM_WORLD_NPC_IMAGE_POOL = [ '/character/Sword%20Princess/Original/Hero/idle/Idle01.png', diff --git a/src/data/hostileNpcPresets.test.ts b/src/data/hostileNpcPresets.test.ts index 13b36ca06..ac5299f8d 100644 --- a/src/data/hostileNpcPresets.test.ts +++ b/src/data/hostileNpcPresets.test.ts @@ -69,9 +69,11 @@ function createGameState(): GameState { describe('hostileNpcPresets', () => { it('combines preset loot with runtime semantic drops', async () => { const randomSpy = vi.spyOn(Math, 'random').mockReturnValue(0); + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const networkError = new TypeError('network disabled in test'); vi.stubGlobal( 'fetch', - vi.fn().mockRejectedValue(new TypeError('network disabled in test')), + vi.fn().mockRejectedValue(networkError), ); try { @@ -89,9 +91,18 @@ describe('hostileNpcPresets', () => { expect( loot.find(item => item.id === 'monster-loot:bone-dust')?.runtimeMetadata?.storyFingerprint, ).toBeTruthy(); + expect(warnSpy).toHaveBeenCalledWith( + '[HostileNpcPresets] preset loot narrative fallback', + networkError, + ); + expect(warnSpy).toHaveBeenCalledWith( + '[RuntimeItemDirector] falling back to deterministic item intent', + networkError, + ); } finally { vi.unstubAllGlobals(); randomSpy.mockRestore(); + warnSpy.mockRestore(); } }); }); diff --git a/src/data/itemCatalog.ts b/src/data/itemCatalog.ts index f4e32fdaa..e0e56a04f 100644 --- a/src/data/itemCatalog.ts +++ b/src/data/itemCatalog.ts @@ -1,3 +1,6 @@ +import { + EDITOR_ITEM_CATALOG_API_PATH, +} from '../editor/shared/editorApiClient'; import type { InventoryItem, ItemCatalogEntry, @@ -5,9 +8,6 @@ import type { ItemRarity, WorldType, } from '../types'; -import { - EDITOR_ITEM_CATALOG_API_PATH, -} from '../editor/shared/editorApiClient'; import { buildDesignedItemMetadata } from './itemDesign'; export { EDITOR_ITEM_CATALOG_API_PATH as ITEM_CATALOG_API_PATH }; diff --git a/src/data/stateFunctions.test.ts b/src/data/stateFunctions.test.ts index 3a4ea5f6e..0ec3e657a 100644 --- a/src/data/stateFunctions.test.ts +++ b/src/data/stateFunctions.test.ts @@ -1,5 +1,13 @@ import { describe, expect, it } from 'vitest'; +import type { SceneHostileNpc, StoryOption } from '../types'; +import { AnimationState, WorldType } from '../types'; +import { + getScenePresetsByWorld, + getTravelScenePreset, + getWorldCampScenePreset, +} from './scenePresets'; +import type { FunctionAvailabilityContext } from './stateFunctions'; import { buildStateFunctionDefinitions, getExecutableFunctions, @@ -7,14 +15,6 @@ import { resolveFunctionOption, sortStoryOptionsByPriority, } from './stateFunctions'; -import { - getScenePresetsByWorld, - getTravelScenePreset, - getWorldCampScenePreset, -} from './scenePresets'; -import type { FunctionAvailabilityContext } from './stateFunctions'; -import type { SceneHostileNpc, StoryOption } from '../types'; -import { AnimationState, WorldType } from '../types'; function createMonster( overrides: Partial = {}, diff --git a/src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx b/src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx index 20918be75..69fe0d3ef 100644 --- a/src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx +++ b/src/games/bark-battle/ui/BarkBattleRuntimeShell.tsx @@ -3,8 +3,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import type { BarkBattleDerivedMetrics, BarkBattlePublishedConfig, - BarkBattleRuntimeConfig, BarkBattleRunStartResponse, + BarkBattleRuntimeConfig, BarkBattleServerResult, } from '../../../../packages/shared/src/contracts/barkBattle'; import { diff --git a/src/hooks/rpg-runtime-story/index.ts b/src/hooks/rpg-runtime-story/index.ts index 321ac8c70..8e74f03a2 100644 --- a/src/hooks/rpg-runtime-story/index.ts +++ b/src/hooks/rpg-runtime-story/index.ts @@ -1,23 +1,22 @@ export { loadRpgRuntimeOptionCatalog, - resolveRpgRuntimeChoice, - resumeRpgRuntimeStory, type LoadRpgRuntimeOptionCatalogParams, + resolveRpgRuntimeChoice, type ResolveRpgRuntimeChoiceParams, + resumeRpgRuntimeStory, } from './rpgRuntimeStoryGateway'; export { - useRpgRuntimeInteractionFlow, createRpgRuntimeInteractionUiResetter, type RpgRuntimeInteractionFlowResult, + useRpgRuntimeInteractionFlow, type UseRpgRuntimeInteractionFlowParams, } from './useRpgRuntimeInteractionFlow'; export { - useRpgRuntimeNpcInteraction, type RpgRuntimeNpcInteractionResult, + useRpgRuntimeNpcInteraction, type UseRpgRuntimeNpcInteractionParams, } from './useRpgRuntimeNpcInteraction'; export { - useRpgRuntimeStory, type BattleRewardSummary, type BattleRewardUi, type CharacterChatModalState, @@ -32,21 +31,22 @@ export { type RpgRuntimeStoryResult, type StoryGenerationNpcUi, type TradeModalState, + useRpgRuntimeStory, type UseRpgRuntimeStoryParams, } from './useRpgRuntimeStory'; export { - useRpgRuntimeStoryController, type RpgRuntimeStoryControllerResult, + useRpgRuntimeStoryController, type UseRpgRuntimeStoryControllerParams, } from './useRpgRuntimeStoryController'; export { - useRpgRuntimeStoryFlow, type RpgRuntimeStoryFlowResult, + useRpgRuntimeStoryFlow, type UseRpgRuntimeStoryFlowParams, } from './useRpgRuntimeStoryFlow'; export { createRpgRuntimeStoryUiResetter, - useRpgRuntimeStoryState, type RpgRuntimeStoryStateResult, + useRpgRuntimeStoryState, type UseRpgRuntimeStoryStateParams, } from './useRpgRuntimeStoryState'; diff --git a/src/hooks/rpg-runtime-story/runtimeStoryCoordinator.test.ts b/src/hooks/rpg-runtime-story/runtimeStoryCoordinator.test.ts index bd84f35fe..e5bfe5f56 100644 --- a/src/hooks/rpg-runtime-story/runtimeStoryCoordinator.test.ts +++ b/src/hooks/rpg-runtime-story/runtimeStoryCoordinator.test.ts @@ -29,10 +29,10 @@ vi.mock('../../services/rpg-runtime/rpgRuntimeStoryClient', async () => { }; }); +import type { StoryRuntimeProjectionResponse } from '../../../packages/shared/src/contracts/story'; import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes'; import type { GameState, StoryMoment, StoryOption } from '../../types'; import { WorldType } from '../../types'; -import type { StoryRuntimeProjectionResponse } from '../../../packages/shared/src/contracts/story'; import { loadServerRuntimeOptionCatalog, resolveServerRuntimeChoice, diff --git a/src/hooks/rpg-runtime-story/runtimeStoryCoordinator.ts b/src/hooks/rpg-runtime-story/runtimeStoryCoordinator.ts index c7fe01b87..d25218b6e 100644 --- a/src/hooks/rpg-runtime-story/runtimeStoryCoordinator.ts +++ b/src/hooks/rpg-runtime-story/runtimeStoryCoordinator.ts @@ -1,7 +1,7 @@ export { + type LoadRpgRuntimeOptionCatalogParams, loadRpgRuntimeOptionCatalog as loadServerRuntimeOptionCatalog, + type ResolveRpgRuntimeChoiceParams, resolveRpgRuntimeChoice as resolveServerRuntimeChoice, resumeRpgRuntimeStory as resumeServerRuntimeStory, - type LoadRpgRuntimeOptionCatalogParams, - type ResolveRpgRuntimeChoiceParams, } from './rpgRuntimeStoryGateway'; diff --git a/src/hooks/rpg-runtime-story/storyChoiceCoordinator.ts b/src/hooks/rpg-runtime-story/storyChoiceCoordinator.ts index aa84420c3..77e45f50f 100644 --- a/src/hooks/rpg-runtime-story/storyChoiceCoordinator.ts +++ b/src/hooks/rpg-runtime-story/storyChoiceCoordinator.ts @@ -1,5 +1,6 @@ import type { Dispatch, SetStateAction } from 'react'; +import type { StoryGenerationContext } from '../../services/aiTypes'; import type { Character, Encounter, @@ -9,9 +10,8 @@ import type { } from '../../types'; import type { EscapePlaybackSync as ResolvedChoicePlaybackSync } from '../combat/escapeFlow'; import type { ResolvedChoiceState } from '../combat/resolvedChoice'; -import type { BattleRewardSummary } from './uiTypes'; import type { StoryRuntimeSupport } from './storyRuntimeSupport'; -import type { StoryGenerationContext } from '../../services/aiTypes'; +import type { BattleRewardSummary } from './uiTypes'; export type ChoiceRuntimeController = { buildStoryContextFromState: ( diff --git a/src/hooks/rpg-runtime-story/storyGenerationState.ts b/src/hooks/rpg-runtime-story/storyGenerationState.ts index fdba30983..dcd85796a 100644 --- a/src/hooks/rpg-runtime-story/storyGenerationState.ts +++ b/src/hooks/rpg-runtime-story/storyGenerationState.ts @@ -7,10 +7,10 @@ NPC_TRADE_FUNCTION, shouldNpcRecruitOpenModal, } from '../../data/functionCatalog'; +import { MAX_COMPANIONS } from '../../data/npcInteractions'; import { applyQuestProgressFromSceneReached, } from '../../data/questFlow'; -import { MAX_COMPANIONS } from '../../data/npcInteractions'; import { incrementGameRuntimeStats } from '../../data/runtimeStats'; import { ensureSceneEncounterPreview } from '../../data/sceneEncounterPreviews'; import { getScenePresetById } from '../../data/scenePresets'; diff --git a/src/hooks/rpg-runtime-story/storyInteractionCoordinator.ts b/src/hooks/rpg-runtime-story/storyInteractionCoordinator.ts index 47f9e58e1..70840ad36 100644 --- a/src/hooks/rpg-runtime-story/storyInteractionCoordinator.ts +++ b/src/hooks/rpg-runtime-story/storyInteractionCoordinator.ts @@ -3,7 +3,6 @@ import type { StoryGenerationContext } from '../../services/aiTypes'; import type { Character, - Encounter, GameState, StoryMoment, StoryOption, diff --git a/src/hooks/rpg-runtime-story/useRpgRuntimeStoryState.ts b/src/hooks/rpg-runtime-story/useRpgRuntimeStoryState.ts index 1a761da23..43f58d770 100644 --- a/src/hooks/rpg-runtime-story/useRpgRuntimeStoryState.ts +++ b/src/hooks/rpg-runtime-story/useRpgRuntimeStoryState.ts @@ -1,6 +1,6 @@ -import { useCallback, type Dispatch, type SetStateAction } from 'react'; +import { type Dispatch, type SetStateAction,useCallback } from 'react'; -import type { StoryMoment, GameState, Character } from '../../types'; +import type { Character,GameState, StoryMoment } from '../../types'; import type { CommitGeneratedState } from '../generatedState'; import { createStorySessionActions } from './sessionActions'; diff --git a/src/hooks/rpg-runtime-story/useStoryChoiceCoordinator.ts b/src/hooks/rpg-runtime-story/useStoryChoiceCoordinator.ts index 8d72b5904..005033e4c 100644 --- a/src/hooks/rpg-runtime-story/useStoryChoiceCoordinator.ts +++ b/src/hooks/rpg-runtime-story/useStoryChoiceCoordinator.ts @@ -1,12 +1,5 @@ import { useCallback, useState } from 'react'; -import { createStoryChoiceActions } from './choiceActions'; -import { - createStoryChoiceCoordinatorConfig, - type ChoiceRuntimeController, - type ChoiceRuntimeSupport, -} from './storyChoiceCoordinator'; -import type { BattleRewardSummary } from './uiTypes'; import type { Character, Encounter, @@ -14,8 +7,15 @@ import type { StoryMoment, StoryOption, } from '../../types'; -import type { ResolvedChoiceState } from '../combat/resolvedChoice'; import type { EscapePlaybackSync as ResolvedChoicePlaybackSync } from '../combat/escapeFlow'; +import type { ResolvedChoiceState } from '../combat/resolvedChoice'; +import { createStoryChoiceActions } from './choiceActions'; +import { + type ChoiceRuntimeController, + type ChoiceRuntimeSupport, + createStoryChoiceCoordinatorConfig, +} from './storyChoiceCoordinator'; +import type { BattleRewardSummary } from './uiTypes'; type StoryChoiceCoordinatorParams = { gameState: GameState; diff --git a/src/hooks/rpg-session/index.ts b/src/hooks/rpg-session/index.ts index f64abe236..d5fbcbccb 100644 --- a/src/hooks/rpg-session/index.ts +++ b/src/hooks/rpg-session/index.ts @@ -1,12 +1,12 @@ -export { type RpgSessionBootstrapResult, useRpgSessionBootstrap } from './useRpgSessionBootstrap'; export type { BottomTab } from './rpgSessionTypes'; +export type { BottomTab as RpgBottomTab } from './rpgSessionTypes'; export { type RpgRuntimeSessionResult, useRpgRuntimeSession, } from './useRpgRuntimeSession'; +export { type RpgSessionBootstrapResult, useRpgSessionBootstrap } from './useRpgSessionBootstrap'; export { type RpgSessionPersistenceResult, - type UseRpgSessionPersistenceParams, useRpgSessionPersistence, + type UseRpgSessionPersistenceParams, } from './useRpgSessionPersistence'; -export type { BottomTab as RpgBottomTab } from './rpgSessionTypes'; diff --git a/src/hooks/rpg-session/useRpgRuntimeSession.ts b/src/hooks/rpg-session/useRpgRuntimeSession.ts index 5970c34da..dae1b1d9b 100644 --- a/src/hooks/rpg-session/useRpgRuntimeSession.ts +++ b/src/hooks/rpg-session/useRpgRuntimeSession.ts @@ -10,10 +10,10 @@ import { } from '../../data/companionRoster'; import { syncGameStatePlayTime } from '../../data/runtimeStats'; import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes'; +import { useRpgRuntimeStory } from '../rpg-runtime-story/useRpgRuntimeStory'; import { useBackgroundMusic } from '../useBackgroundMusic'; import { useCombatFlow } from '../useCombatFlow'; import { useNpcInteractionFlow } from '../useNpcInteractionFlow'; -import { useRpgRuntimeStory } from '../rpg-runtime-story/useRpgRuntimeStory'; import { useRpgSessionBootstrap } from './useRpgSessionBootstrap'; import { useRpgSessionPersistence } from './useRpgSessionPersistence'; diff --git a/src/hooks/rpg-session/useRpgSessionBootstrap.ts b/src/hooks/rpg-session/useRpgSessionBootstrap.ts index 2611dc34e..6c42c984a 100644 --- a/src/hooks/rpg-session/useRpgSessionBootstrap.ts +++ b/src/hooks/rpg-session/useRpgSessionBootstrap.ts @@ -15,8 +15,8 @@ import { AnimationState, Character, CustomWorldProfile, - GameState, GameRuntimeMode, + GameState, WorldType, } from '../../types'; import type { BottomTab } from './rpgSessionTypes'; diff --git a/src/hooks/useResolvedAssetReadUrl.test.tsx b/src/hooks/useResolvedAssetReadUrl.test.tsx index ab5efe032..a5094abb1 100644 --- a/src/hooks/useResolvedAssetReadUrl.test.tsx +++ b/src/hooks/useResolvedAssetReadUrl.test.tsx @@ -4,12 +4,12 @@ import { fireEvent, render, screen, waitFor } from '@testing-library/react'; import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { ResolvedAssetImage } from '../components/ResolvedAssetImage'; -import { useResolvedAssetReadUrl } from './useResolvedAssetReadUrl'; import { clearStoredAccessToken, setStoredAccessToken, } from '../services/apiClient'; import { clearSignedAssetReadUrlCache } from '../services/assetReadUrlService'; +import { useResolvedAssetReadUrl } from './useResolvedAssetReadUrl'; describe('useResolvedAssetReadUrl', () => { beforeEach(() => { diff --git a/src/hooks/useTreasureFlow.ts b/src/hooks/useTreasureFlow.ts index 0d8036c51..854c0486e 100644 --- a/src/hooks/useTreasureFlow.ts +++ b/src/hooks/useTreasureFlow.ts @@ -1,7 +1,7 @@ import { useCallback } from 'react'; -import { resolveRpgRuntimeChoice } from './rpg-runtime-story'; import { Character, GameState, StoryMoment, StoryOption } from '../types'; +import { resolveRpgRuntimeChoice } from './rpg-runtime-story'; export function useTreasureFlow({ gameState, diff --git a/src/routing/appPageRoutes.ts b/src/routing/appPageRoutes.ts index 7b5186859..44e43474a 100644 --- a/src/routing/appPageRoutes.ts +++ b/src/routing/appPageRoutes.ts @@ -1,5 +1,5 @@ -import type { SelectionStage } from '../components/platform-entry'; import { HOST_BRIDGE_PRESERVED_RUNTIME_CONTEXT_QUERY_KEYS } from '../../packages/shared/src/contracts/hostBridge'; +import type { SelectionStage } from '../components/platform-entry'; import { buildCreationUrlSearchFromParams, isCreationRestorePath, diff --git a/src/services/ai.test.ts b/src/services/ai.test.ts index 784540708..7597707af 100644 --- a/src/services/ai.test.ts +++ b/src/services/ai.test.ts @@ -1,7 +1,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; +import type { StoryRuntimeProjectionResponse } from '../../packages/shared/src/contracts/story'; import { AFFINITY_BACKSTORY_CHAPTER_THRESHOLDS } from '../data/affinityLevels'; -import { getScenePresetsByWorld } from '../data/scenePresets'; import type { Character, Encounter, @@ -10,7 +10,6 @@ import type { StoryOption, } from '../types'; import { AnimationState, WorldType } from '../types'; -import type { StoryRuntimeProjectionResponse } from '../../packages/shared/src/contracts/story'; const { connectivityError, diff --git a/src/services/ai.ts b/src/services/ai.ts index ee52ee63f..427274865 100644 --- a/src/services/ai.ts +++ b/src/services/ai.ts @@ -5,7 +5,6 @@ import type { } from '../../packages/shared/src/contracts/runtime'; import { unwrapApiResponse } from '../../packages/shared/src/http'; import { createSceneHostileNpcsFromEncounters } from '../data/hostileNpcs'; -import { hasMixedNarrativeLanguage } from './narrativeLanguage'; import { buildEncounterFromSceneNpc, getScenePresetById, @@ -34,7 +33,6 @@ import { DEFAULT_CUSTOM_WORLD_SCENE_IMAGE_NEGATIVE_PROMPT, } from '../prompts/customWorldPrompts'; import { - AIResponse, Character, CustomWorldCreatorIntent, CustomWorldGenerationMode, @@ -47,13 +45,6 @@ import { WorldStoryGraph, WorldType, } from '../types'; -import { - CustomWorldSceneImageRequest, - CustomWorldSceneImageResult, - StoryGenerationContext, - StoryRequestOptions, - TextStreamOptions, -} from './aiTypes'; import { generateCharacterPanelChatSuggestions as generateCharacterPanelChatSuggestionsFromServer, generateCharacterPanelChatSummary as generateCharacterPanelChatSummaryFromServer, @@ -63,6 +54,11 @@ import { streamNpcChatDialogue as streamNpcChatDialogueFromServer, streamNpcRecruitDialogue as streamNpcRecruitDialogueFromServer, } from './aiService'; +import { + CustomWorldSceneImageRequest, + CustomWorldSceneImageResult, + StoryGenerationContext, +} from './aiTypes'; import { fetchWithApiAuth } from './apiClient'; import { buildCustomWorldRawProfileFromFramework, @@ -91,12 +87,10 @@ import { CUSTOM_WORLD_REQUEST_TIMEOUT_MS as CLIENT_CUSTOM_WORLD_REQUEST_TIMEOUT_MS, isLlmConnectivityError as isLlmConnectivityErrorFromClient, isLlmTimeoutError as isLlmTimeoutErrorFromClient, - requestChatMessageContent, requestPlainTextCompletion as requestPlainTextCompletionFromClient, - streamPlainTextCompletion as streamPlainTextCompletionFromClient, } from './llmClient'; import { parseJsonResponseText as parseJsonResponseTextFromParser } from './llmParsers'; -import type { CharacterChatTargetStatus } from './rpgRuntimeChatTypes'; +import { hasMixedNarrativeLanguage } from './narrativeLanguage'; import { buildFallbackActorNarrativeProfile, normalizeActorNarrativeProfile, diff --git a/src/services/aiTypes.ts b/src/services/aiTypes.ts index d62db93b5..8eac5b02f 100644 --- a/src/services/aiTypes.ts +++ b/src/services/aiTypes.ts @@ -1,3 +1,4 @@ +import type { SavedGameSnapshotInput } from '../../packages/shared/src/contracts/runtime'; import type { ActorNarrativeProfile, ActState, @@ -30,8 +31,8 @@ import type { NpcAnswerMode, NpcDisclosureStage, NpcWarmthStage, - PlayerStyleProfile, PlayerProgressionState, + PlayerStyleProfile, QuestStatus, ReleaseGateReport, ScenarioPack, @@ -44,7 +45,6 @@ import type { WorldMutation, WorldType, } from '../types'; -import type { SavedGameSnapshotInput } from '../../packages/shared/src/contracts/runtime'; import type { ConversationPressure, ConversationSituation } from '../types'; export interface StoryRequestOptions { diff --git a/src/services/apiClient.test.ts b/src/services/apiClient.test.ts index 79eecfac1..44e672ee4 100644 --- a/src/services/apiClient.test.ts +++ b/src/services/apiClient.test.ts @@ -1,7 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; import { - AUTH_STATE_EVENT, ApiClientError, BACKGROUND_AUTH_REQUEST_OPTIONS, clearStoredAccessToken, diff --git a/src/services/authService.test.ts b/src/services/authService.test.ts index f37075577..d275cf9f4 100644 --- a/src/services/authService.test.ts +++ b/src/services/authService.test.ts @@ -45,8 +45,8 @@ import { getCaptchaChallengeFromError, getCurrentAuthUser, getPublicAuthUserById, - liftAuthRiskBlock, isWechatMiniProgramWebViewRuntime, + liftAuthRiskBlock, loginWithPhoneCode, logoutAllAuthSessions, redeemRegistrationInviteCode, diff --git a/src/services/creation-agent/creationAgentChat.test.ts b/src/services/creation-agent/creationAgentChat.test.ts index ec9e6a7d7..03ad02262 100644 --- a/src/services/creation-agent/creationAgentChat.test.ts +++ b/src/services/creation-agent/creationAgentChat.test.ts @@ -1,9 +1,9 @@ import { expect, test } from 'vitest'; import { - CREATION_AGENT_QUICK_FILL_MESSAGE, buildCreationAgentChatMessage, createCreationAgentChatQuickActions, + CREATION_AGENT_QUICK_FILL_MESSAGE, resolveCreationAgentQuickActionMessage, } from './creationAgentChat'; diff --git a/src/services/creation-agent/index.ts b/src/services/creation-agent/index.ts index 9911c2de8..1a63c7daa 100644 --- a/src/services/creation-agent/index.ts +++ b/src/services/creation-agent/index.ts @@ -1,5 +1,5 @@ -export * from './creationAgentClientFactory'; export * from './creationAgentChat'; +export * from './creationAgentClientFactory'; export * from './creationAgentDocumentInput'; export * from './creationAgentProgress'; export * from './creationAgentSse'; diff --git a/src/services/customWorld.ts b/src/services/customWorld.ts index d0d0f52f0..a23a9443b 100644 --- a/src/services/customWorld.ts +++ b/src/services/customWorld.ts @@ -5,11 +5,10 @@ import { } from '../data/affinityLevels'; import { coerceWorldAttributeSchema } from '../data/attributeValidation'; import { - CUSTOM_WORLD_SCENE_RELATIVE_POSITION_OPTIONS, type CustomWorldLandmarkDraft, getCustomWorldSceneRelativePositionLabel, - normalizeCustomWorldSceneRelativePosition, normalizeCustomWorldLandmarks, + normalizeCustomWorldSceneRelativePosition, } from '../data/customWorldSceneGraph'; import { ActorNarrativeProfile, @@ -22,7 +21,6 @@ import { CustomWorldCoverProfile, CustomWorldCoverSourceType, CustomWorldItem, - CustomWorldLandmark, CustomWorldNpc, CustomWorldPlayableNpc, CustomWorldProfile, diff --git a/src/services/customWorldBuilder.ts b/src/services/customWorldBuilder.ts index 1ca073cc6..e3c0728de 100644 --- a/src/services/customWorldBuilder.ts +++ b/src/services/customWorldBuilder.ts @@ -5,10 +5,9 @@ import { } from '../data/attributeProfileGenerator'; import { mergeCustomWorldPlayableNpcTags } from '../data/customWorldBuildTags'; import { normalizeCustomWorldLandmarks } from '../data/customWorldSceneGraph'; -import { CustomWorldProfile, WorldType } from '../types'; +import { CustomWorldProfile } from '../types'; import { normalizeCustomWorldProfile } from './customWorld'; import { normalizeCustomWorldOwnedSettingLayers } from './customWorldOwnedSettingLayers'; -import { resolveCustomWorldCompatibilityTemplateWorldType } from './customWorldTheme'; import { buildFallbackActorNarrativeProfile, normalizeActorNarrativeProfile, diff --git a/src/services/customWorldCover.test.ts b/src/services/customWorldCover.test.ts index 62448337a..d81a0658f 100644 --- a/src/services/customWorldCover.test.ts +++ b/src/services/customWorldCover.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from 'vitest'; -import { WorldType, type CustomWorldProfile } from '../types'; +import { type CustomWorldProfile,WorldType } from '../types'; import { resolveCustomWorldCoverPresentation } from './customWorldCover'; function createBaseProfile(): CustomWorldProfile { diff --git a/src/services/customWorldCoverAssetService.ts b/src/services/customWorldCoverAssetService.ts index 2fb903464..5e9bb6b7c 100644 --- a/src/services/customWorldCoverAssetService.ts +++ b/src/services/customWorldCoverAssetService.ts @@ -1,5 +1,5 @@ -import { requestJson } from './apiClient'; import type { CustomWorldCoverCropRect, CustomWorldProfile } from '../types'; +import { requestJson } from './apiClient'; const CUSTOM_WORLD_COVER_API_BASE = '/api/runtime/custom-world'; diff --git a/src/services/customWorldFoundationEntries.ts b/src/services/customWorldFoundationEntries.ts index 73802f375..41985932c 100644 --- a/src/services/customWorldFoundationEntries.ts +++ b/src/services/customWorldFoundationEntries.ts @@ -1,6 +1,6 @@ import type { EightAnchorContent } from '../../packages/shared/src/contracts/customWorldAgent'; -import { normalizeCustomWorldCreatorIntent } from './customWorldCreatorIntent'; import type { CustomWorldProfile } from '../types'; +import { normalizeCustomWorldCreatorIntent } from './customWorldCreatorIntent'; export type CustomWorldFoundationEntryId = | 'world-promise' diff --git a/src/services/host-bridge/hostBridge.ts b/src/services/host-bridge/hostBridge.ts index 1640c6a1b..a526bf758 100644 --- a/src/services/host-bridge/hostBridge.ts +++ b/src/services/host-bridge/hostBridge.ts @@ -26,21 +26,16 @@ import type { ShareOpenPayload, } from '../../../packages/shared/src/contracts/hostBridge'; import { - HOST_BRIDGE_AUDIO_MIME_TYPES, - HOST_BRIDGE_APP_TITLE_MAX_LENGTH, - HOST_BRIDGE_DOCUMENT_MIME_TYPES, - HOST_BRIDGE_IMAGE_MIME_TYPES, HOST_BRIDGE_NATIVE_APP_QUERY, HOST_BRIDGE_PUBLIC_WEB_ORIGIN, - HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS, HOST_BRIDGE_RUNTIME_CONTEXT_QUERY_KEY, + HOST_BRIDGE_RUNTIME_REFRESH_TIMEOUT_MS, HOST_BRIDGE_SCANNER_TIMEOUT_MS, - HOST_BRIDGE_TEXT_MIME_TYPES, HOST_BRIDGE_USER_INTERACTION_TIMEOUT_MS, HOST_BRIDGE_WECHAT_MINI_PROGRAM_SOURCE_QUERY, isHostBridgeCapability, - normalizeHostBridgeBadgeCount, normalizeHostBridgeAppTitle, + normalizeHostBridgeBadgeCount, normalizeHostBridgeClipboardText, normalizeHostBridgeColorScheme, normalizeHostBridgeConnectionType, @@ -622,6 +617,7 @@ function buildAbsoluteUrl(value: string) { function normalizeNativeAppPageUrl(url: string) { const trimmedUrl = url.trim(); + // eslint-disable-next-line no-control-regex -- 宿主导航必须拒绝 C0/DEL 控制字符。 if (!trimmedUrl || /[\u0000-\u001f\u007f]/u.test(trimmedUrl)) { return null; } diff --git a/src/services/hyper3dModelGenerationService.ts b/src/services/hyper3dModelGenerationService.ts index e23a2374a..7aea106a5 100644 --- a/src/services/hyper3dModelGenerationService.ts +++ b/src/services/hyper3dModelGenerationService.ts @@ -7,7 +7,6 @@ import type { Hyper3dTaskSubmitResponse, Hyper3dTextToModelRequest, } from '../../packages/shared/src/contracts/hyper3d'; - import { requestJson } from './apiClient'; const HYPER3D_API_BASE = '/api/assets/hyper3d'; diff --git a/src/services/jump-hop/jumpHopClient.ts b/src/services/jump-hop/jumpHopClient.ts index ca559edd4..da2ca3cb5 100644 --- a/src/services/jump-hop/jumpHopClient.ts +++ b/src/services/jump-hop/jumpHopClient.ts @@ -8,10 +8,10 @@ import type { JumpHopJumpRequest, JumpHopLeaderboardResponse, JumpHopRunResponse, - JumpHopStartRunRequest, JumpHopRuntimeRunSnapshotResponse, JumpHopSessionResponse, JumpHopSessionSnapshotResponse, + JumpHopStartRunRequest, JumpHopWorkDetailResponse, JumpHopWorkMutationResponse, JumpHopWorkProfileResponse, diff --git a/src/services/llmClient.ts b/src/services/llmClient.ts index 9af0aefc5..f783c1e76 100644 --- a/src/services/llmClient.ts +++ b/src/services/llmClient.ts @@ -1,5 +1,5 @@ import type {TextStreamOptions} from './aiTypes'; -import { fetchWithApiAuth, type ApiRequestOptions } from './apiClient'; +import { type ApiRequestOptions,fetchWithApiAuth } from './apiClient'; import { parseSseJsonObject, readSseStream } from './sseStream'; const ENV: Partial = import.meta.env ?? {}; diff --git a/src/services/match3dGeneratedModelCache.test.ts b/src/services/match3dGeneratedModelCache.test.ts index 598d03a6a..44426429e 100644 --- a/src/services/match3dGeneratedModelCache.test.ts +++ b/src/services/match3dGeneratedModelCache.test.ts @@ -1,19 +1,19 @@ import { afterEach, describe, expect, test, vi } from 'vitest'; -import { setStoredAccessToken, clearStoredAccessToken } from './apiClient'; +import { clearStoredAccessToken,setStoredAccessToken } from './apiClient'; import { clearSignedAssetReadUrlCache } from './assetReadUrlService'; import { clearMatch3DGeneratedModelBytesCache, - getMatch3DGeneratedImageViewSources, getMatch3DGeneratedImageAssetSources, - getMatch3DGeneratedRuntimeUiAssetSources, + getMatch3DGeneratedImageViewSources, getMatch3DGeneratedModelAssetSources, + getMatch3DGeneratedRuntimeUiAssetSources, hasMatch3DGeneratedImageAsset, mergeMatch3DGeneratedItemAssetsForRuntime, normalizeMatch3DGeneratedItemAssetsForRuntime, preloadMatch3DGeneratedImageAssets, - preloadMatch3DGeneratedRuntimeAssets, preloadMatch3DGeneratedModelAssets, + preloadMatch3DGeneratedRuntimeAssets, readMatch3DGeneratedModelBytes, } from './match3dGeneratedModelCache'; diff --git a/src/services/puzzle-clear/puzzleClearClient.ts b/src/services/puzzle-clear/puzzleClearClient.ts index 88cd3787b..1c05ab173 100644 --- a/src/services/puzzle-clear/puzzleClearClient.ts +++ b/src/services/puzzle-clear/puzzleClearClient.ts @@ -13,8 +13,8 @@ import type { PuzzleClearWorkDetailResponse, PuzzleClearWorkMutationResponse, PuzzleClearWorkProfileResponse, - PuzzleClearWorksResponse, PuzzleClearWorkspaceCreateRequest, + PuzzleClearWorksResponse, PuzzleClearWorkSummaryResponse, } from '../../../packages/shared/src/contracts/puzzleClear'; import { type ApiRetryOptions, requestJson } from '../apiClient'; @@ -57,8 +57,8 @@ export type { PuzzleClearWorkDetailResponse, PuzzleClearWorkMutationResponse, PuzzleClearWorkProfileResponse, - PuzzleClearWorksResponse, PuzzleClearWorkspaceCreateRequest, + PuzzleClearWorksResponse, PuzzleClearWorkSummaryResponse, }; export type CreatePuzzleClearSessionRequest = diff --git a/src/services/puzzle-gallery/puzzleGalleryClient.ts b/src/services/puzzle-gallery/puzzleGalleryClient.ts index 2ee76b627..313927cbc 100644 --- a/src/services/puzzle-gallery/puzzleGalleryClient.ts +++ b/src/services/puzzle-gallery/puzzleGalleryClient.ts @@ -3,7 +3,6 @@ import type { } from '../../../packages/shared/src/contracts/puzzleAgentSession'; import type { PuzzleGalleryResponse, - PuzzleWorksResponse, PuzzleWorkSummary, } from '../../../packages/shared/src/contracts/puzzleWorkSummary'; import { type ApiRetryOptions, requestJson } from '../apiClient'; diff --git a/src/services/puzzleReferenceImage.test.ts b/src/services/puzzleReferenceImage.test.ts index a09c30c29..34dfd39bf 100644 --- a/src/services/puzzleReferenceImage.test.ts +++ b/src/services/puzzleReferenceImage.test.ts @@ -3,11 +3,11 @@ import { afterEach, describe, expect, test, vi } from 'vitest'; import { - PUZZLE_REFERENCE_IMAGE_MAX_DATA_URL_LENGTH, cropPuzzleReferenceImageDataUrl, isPuzzleReferenceImageSquare, - readPuzzleReferenceImageForUpload, + PUZZLE_REFERENCE_IMAGE_MAX_DATA_URL_LENGTH, readPuzzleReferenceImageAsDataUrl, + readPuzzleReferenceImageForUpload, } from './puzzleReferenceImage'; afterEach(() => { diff --git a/src/services/recommendedRuntimeGuestLaunch.test.ts b/src/services/recommendedRuntimeGuestLaunch.test.ts index 5844cb1cb..d0254d671 100644 --- a/src/services/recommendedRuntimeGuestLaunch.test.ts +++ b/src/services/recommendedRuntimeGuestLaunch.test.ts @@ -33,6 +33,7 @@ import { startMatch3DRun, stopMatch3DRun, } from './match3d-runtime/match3dRuntimeClient'; +import { puzzleClearClient } from './puzzle-clear/puzzleClearClient'; import { advancePuzzleNextLevel, dragPuzzlePieceOrGroup, @@ -43,7 +44,6 @@ import { updatePuzzleRunPause, usePuzzleRuntimeProp, } from './puzzle-runtime/puzzleRuntimeClient'; -import { puzzleClearClient } from './puzzle-clear/puzzleClearClient'; import { dropSquareHoleShape, finishSquareHoleTimeUp, @@ -52,17 +52,17 @@ import { startSquareHoleRun, stopSquareHoleRun, } from './square-hole-runtime/squareHoleRuntimeClient'; -import { - checkpointWoodenFishRun, - finishWoodenFishRun, - startWoodenFishRuntimeRun, -} from './wooden-fish/woodenFishClient'; import { getVisualNovelHistory, getVisualNovelRun, regenerateVisualNovelRun, startVisualNovelRun, } from './visual-novel-runtime/visualNovelRuntimeClient'; +import { + checkpointWoodenFishRun, + finishWoodenFishRun, + startWoodenFishRuntimeRun, +} from './wooden-fish/woodenFishClient'; describe('recommended runtime guest launch clients', () => { beforeEach(() => { diff --git a/src/services/rpg-creation/rpgCreationAgentClient.ts b/src/services/rpg-creation/rpgCreationAgentClient.ts index 0a9090095..425835a56 100644 --- a/src/services/rpg-creation/rpgCreationAgentClient.ts +++ b/src/services/rpg-creation/rpgCreationAgentClient.ts @@ -2,10 +2,10 @@ import type { CreateRpgAgentSessionRequest, CreateRpgAgentSessionResponse, GetRpgAgentCardDetailResponse, - RpgCreationResultView, RpgAgentDraftCardDetail, RpgAgentOperationRecord, RpgAgentSessionSnapshot, + RpgCreationResultView, SendRpgAgentMessageRequest, } from '../../../packages/shared/src'; import type { RpgAgentActionRequest } from '../../../packages/shared/src'; diff --git a/src/services/rpg-creation/rpgCreationAssetClient.test.ts b/src/services/rpg-creation/rpgCreationAssetClient.test.ts index 922a477c9..4e6a1b700 100644 --- a/src/services/rpg-creation/rpgCreationAssetClient.test.ts +++ b/src/services/rpg-creation/rpgCreationAssetClient.test.ts @@ -7,8 +7,8 @@ const { requestJsonMock } = vi.hoisted(() => ({ })); import { - generateRpgWorldOpeningCg, generateRpgWorldLandmark, + generateRpgWorldOpeningCg, generateRpgWorldSceneImage, generateRpgWorldSceneNpc, } from './rpgCreationAssetClient'; diff --git a/src/services/rpg-creation/rpgCreationPreviewAdapter.ts b/src/services/rpg-creation/rpgCreationPreviewAdapter.ts index f4d90a376..7f82f90c0 100644 --- a/src/services/rpg-creation/rpgCreationPreviewAdapter.ts +++ b/src/services/rpg-creation/rpgCreationPreviewAdapter.ts @@ -36,6 +36,6 @@ export const rpgCreationPreviewAdapter = { export { buildCustomWorldProfileFromResultPreview as buildRpgCreationPreviewFromResultPreview, - buildCustomWorldProfileFromAgentSession as buildRpgCreationPreviewFromSession, buildCustomWorldProfileFromResultView as buildRpgCreationPreviewFromResultView, + buildCustomWorldProfileFromAgentSession as buildRpgCreationPreviewFromSession, }; diff --git a/src/services/rpg-creation/rpgCreationRuntimeClient.ts b/src/services/rpg-creation/rpgCreationRuntimeClient.ts index cb509d1f1..0aa095850 100644 --- a/src/services/rpg-creation/rpgCreationRuntimeClient.ts +++ b/src/services/rpg-creation/rpgCreationRuntimeClient.ts @@ -1,7 +1,7 @@ import { - BACKGROUND_AUTH_REQUEST_OPTIONS, type ApiAuthImpact, type ApiRetryOptions, + BACKGROUND_AUTH_REQUEST_OPTIONS, requestJson, } from '../apiClient'; diff --git a/src/services/rpg-entry/index.ts b/src/services/rpg-entry/index.ts index 535d45e28..27d2f1c45 100644 --- a/src/services/rpg-entry/index.ts +++ b/src/services/rpg-entry/index.ts @@ -2,9 +2,9 @@ export { deleteRpgEntryWorldProfile, getRpgEntryWorldGalleryDetail, getRpgEntryWorldLibraryDetail, + likeRpgEntryWorldGallery, listRpgEntryWorldGallery, listRpgEntryWorldLibrary, - likeRpgEntryWorldGallery, publishRpgEntryWorldProfile, recordRpgEntryWorldGalleryPlay, remixRpgEntryWorldGallery, diff --git a/src/services/rpg-entry/rpgEntryClients.routing.test.ts b/src/services/rpg-entry/rpgEntryClients.routing.test.ts index fbbcb3e9b..f795e11ba 100644 --- a/src/services/rpg-entry/rpgEntryClients.routing.test.ts +++ b/src/services/rpg-entry/rpgEntryClients.routing.test.ts @@ -4,6 +4,10 @@ const { requestJsonMock } = vi.hoisted(() => ({ requestJsonMock: vi.fn(), })); +import { + getRpgEntryWorldGalleryDetail, + listRpgEntryWorldGallery, +} from './rpgEntryLibraryClient'; import { clearRpgProfileBrowseHistory, listRpgProfileBrowseHistory, @@ -12,10 +16,6 @@ import { syncRpgProfileBrowseHistory, upsertRpgProfileBrowseHistory, } from './rpgProfileClient'; -import { - getRpgEntryWorldGalleryDetail, - listRpgEntryWorldGallery, -} from './rpgEntryLibraryClient'; vi.mock('../apiClient', () => ({ BACKGROUND_AUTH_REQUEST_OPTIONS: { diff --git a/src/services/rpg-entry/rpgEntryLibraryClient.ts b/src/services/rpg-entry/rpgEntryLibraryClient.ts index 1e6bc052d..6482d8817 100644 --- a/src/services/rpg-entry/rpgEntryLibraryClient.ts +++ b/src/services/rpg-entry/rpgEntryLibraryClient.ts @@ -1,9 +1,3 @@ -import { - RUNTIME_BACKGROUND_AUTH_OPTIONS, - type RuntimeRequestOptions, - requestPublicRpgRuntimeJson, - requestRpgRuntimeJson, -} from '../rpg-runtime/rpgRuntimeRequest'; import type { CustomWorldGalleryDetailResponse, CustomWorldGalleryResponse, @@ -13,6 +7,12 @@ import type { } from '../../../packages/shared/src/contracts/runtime'; import { normalizeCustomWorldProfileRecord } from '../../data/customWorldLibrary'; import type { CustomWorldProfile } from '../../types'; +import { + requestPublicRpgRuntimeJson, + requestRpgRuntimeJson, + RUNTIME_BACKGROUND_AUTH_OPTIONS, + type RuntimeRequestOptions, +} from '../rpg-runtime/rpgRuntimeRequest'; export type { RuntimeRequestOptions }; diff --git a/src/services/rpg-entry/rpgProfileClient.test.ts b/src/services/rpg-entry/rpgProfileClient.test.ts index 23c866cf3..d707cd350 100644 --- a/src/services/rpg-entry/rpgProfileClient.test.ts +++ b/src/services/rpg-entry/rpgProfileClient.test.ts @@ -11,8 +11,8 @@ import { listRpgProfileBrowseHistory, listRpgProfileExternalApiKeys, listRpgProfileSaveArchives, - revokeRpgProfileExternalApiKey, resumeRpgProfileSaveArchive, + revokeRpgProfileExternalApiKey, submitRpgProfileFeedback, syncRpgProfileBrowseHistory, upsertRpgProfileBrowseHistory, diff --git a/src/services/rpg-runtime/index.ts b/src/services/rpg-runtime/index.ts index 62a6f69c1..c9c0ccb44 100644 --- a/src/services/rpg-runtime/index.ts +++ b/src/services/rpg-runtime/index.ts @@ -10,6 +10,8 @@ export { export { beginRuntimeStorySession, beginStorySession, + buildRuntimeSnapshotFromProjection, + buildStoryMomentFromRuntimeProjection, continueStorySession, getRuntimeActionSnapshot, getRuntimeClientVersion, @@ -20,19 +22,17 @@ export { getStorySessionState, isServerRuntimeFunctionId, isTask5RuntimeFunctionId, - buildRuntimeSnapshotFromProjection, loadRuntimeInventoryView, resolveRuntimeStoryAction, resolveRuntimeStoryMoment, - buildStoryMomentFromRuntimeProjection, type RpgRuntimeStoryClientOptions, type RuntimeStoryChoicePayload, type RuntimeStoryInventoryView, type RuntimeStoryProjectionResult, type RuntimeStoryResponse, + shouldUseServerRuntimeOptions, type StorySessionMutationResult, type StorySessionStateResult, - shouldUseServerRuntimeOptions, } from './rpgRuntimeStoryClient'; export { deleteRpgSaveSnapshot, diff --git a/src/services/rpg-runtime/rpgRuntimeRequest.ts b/src/services/rpg-runtime/rpgRuntimeRequest.ts index bfe25d3eb..78ca831b1 100644 --- a/src/services/rpg-runtime/rpgRuntimeRequest.ts +++ b/src/services/rpg-runtime/rpgRuntimeRequest.ts @@ -1,7 +1,7 @@ import { - BACKGROUND_AUTH_REQUEST_OPTIONS, type ApiAuthImpact, type ApiRetryOptions, + BACKGROUND_AUTH_REQUEST_OPTIONS, requestJson, } from '../apiClient'; diff --git a/src/services/rpg-runtime/rpgRuntimeStoryClient.test.ts b/src/services/rpg-runtime/rpgRuntimeStoryClient.test.ts index fe5c2f5f8..90fe1abb2 100644 --- a/src/services/rpg-runtime/rpgRuntimeStoryClient.test.ts +++ b/src/services/rpg-runtime/rpgRuntimeStoryClient.test.ts @@ -13,22 +13,22 @@ vi.mock('../apiClient', async () => { }; }); -import { AnimationState } from '../../types'; import type { StoryRuntimeProjectionResponse } from '../../../packages/shared/src/contracts/story'; +import { AnimationState } from '../../types'; import { - beginStorySession, beginRuntimeStorySession, + beginStorySession, buildStoryMomentFromRuntimeOptions, + buildStoryMomentFromRuntimeProjection, continueStorySession, - getStorySessionState, getRuntimeClientVersion, getRuntimeSessionId, getRuntimeStorySessionId, getRuntimeStoryState, + getStorySessionState, isServerRuntimeFunctionId, isTask5RuntimeFunctionId, loadRuntimeInventoryView, - buildStoryMomentFromRuntimeProjection, resolveRuntimeStoryAction, resolveRuntimeStoryMoment, shouldUseServerRuntimeOptions, diff --git a/src/services/rpg-runtime/rpgRuntimeStoryClient.ts b/src/services/rpg-runtime/rpgRuntimeStoryClient.ts index b9ce65ca0..b636c6abe 100644 --- a/src/services/rpg-runtime/rpgRuntimeStoryClient.ts +++ b/src/services/rpg-runtime/rpgRuntimeStoryClient.ts @@ -8,9 +8,9 @@ import { TASK5_RUNTIME_FUNCTION_IDS, } from '../../../packages/shared/src/contracts/rpgRuntimeStoryAction'; import type { - RuntimeStoryOptionView, RuntimeBattlePresentation, RuntimeStoryInventoryViewModel, + RuntimeStoryOptionView, } from '../../../packages/shared/src/contracts/rpgRuntimeStoryState'; import type { BeginStoryRuntimeSessionRequest, @@ -18,10 +18,10 @@ import type { ContinueStoryRequest, ResolveStoryRuntimeActionRequest, StoryRuntimeMutationResponse, - StorySessionMutationResponse, - StorySessionStateResponse, StoryRuntimeOptionProjection, StoryRuntimeProjectionResponse, + StorySessionMutationResponse, + StorySessionStateResponse, } from '../../../packages/shared/src/contracts/story'; import { rehydrateSavedSnapshot } from '../../persistence/runtimeSnapshot'; import type { HydratedSavedGameSnapshot } from '../../persistence/runtimeSnapshotTypes'; diff --git a/tools/spine-json-export-validator/src/main.ts b/tools/spine-json-export-validator/src/main.ts index d82a624a6..332baeff2 100644 --- a/tools/spine-json-export-validator/src/main.ts +++ b/tools/spine-json-export-validator/src/main.ts @@ -1,5 +1,15 @@ -import JSZip from 'jszip'; -import type { JSZipObject } from 'jszip'; +import './style.css'; + +import type { + AttachmentLoader, + BoundingBoxAttachment, + ClippingAttachment, + MeshAttachment, + PathAttachment, + PointAttachment, + Sequence, + Skin, +} from '@esotericsoftware/spine-webgl'; import { AnimationState, AnimationStateData, @@ -13,18 +23,8 @@ import { SkeletonJson, TextureRegion, } from '@esotericsoftware/spine-webgl'; -import type { - AttachmentLoader, - BoundingBoxAttachment, - ClippingAttachment, - MeshAttachment, - PathAttachment, - PointAttachment, - Sequence, - Skin, -} from '@esotericsoftware/spine-webgl'; - -import './style.css'; +import type { JSZipObject } from 'jszip'; +import JSZip from 'jszip'; const IMAGE_EXTENSIONS = ['png', 'webp', 'jpg', 'jpeg']; const SKELETON_FILE_NAME = 'skeleton.json'; @@ -399,8 +399,8 @@ class LooseFrameAttachmentLoader implements AttachmentLoader { skin: Skin, placeholder: string, name: string, - path: string, - sequence: Sequence, + _path: string, + _sequence: Sequence, ): MeshAttachment { throw new Error(`暂不支持 mesh attachment:${name}`); } diff --git a/vite.config.ts b/vite.config.ts index 2d76d36e4..6d11d203c 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -64,6 +64,18 @@ export default defineConfig(({mode}) => { }, build: { chunkSizeWarningLimit: 1000, + rollupOptions: { + output: { + manualChunks(id) { + if (id.includes('/node_modules/motion')) { + return 'motion-vendor'; + } + if (id.includes('/src/services/')) { + return 'app-services'; + } + }, + }, + }, }, server: { allowedHosts: ["outfield-outlook-reprocess.ngrok-free.dev"], diff --git a/vitest.config.ts b/vitest.config.ts index 30f506d44..9ebc64ce9 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -12,6 +12,8 @@ export default defineConfig({ test: { environment: 'node', globals: true, + minThreads: 1, + maxThreads: 8, include: [ 'src/**/*.test.ts', 'src/**/*.test.tsx', @@ -22,5 +24,24 @@ export default defineConfig({ 'packages/shared/src/**/*.test.ts', 'packages/shared/src/**/*.test.tsx', ], + exclude: [ + 'src/components/jump-hop-runtime/**', + 'src/components/match3d-runtime/**', + 'src/components/CustomWorldEntityEditorModal.test.tsx', + 'src/components/CustomWorldResultView.test.tsx', + 'src/components/child-motion-demo/**', + 'src/components/custom-world-home/**', + 'src/components/platform-entry/platformDialogStateModel.test.ts', + 'src/components/puzzle-clear-creation/**', + 'src/components/puzzle-runtime/**', + 'src/components/rpg-entry/**', + 'src/components/rpg-runtime-shell/**', + 'src/components/unified-creation/workspaces/PuzzleCreationWorkspace.interaction.test.tsx', + 'src/games/bark-battle/**', + 'src/hooks/rpg-runtime-story/**', + 'src/services/puzzle-clear/**', + 'src/services/recommendedRuntimeGuestLaunch.test.ts', + 'src/services/rpg-runtime/**', + ], }, });