88 KiB
88 KiB
Findings & Decisions
Requirements
- User asked: "了解整个项目" — inspect and understand the whole project, then explain it clearly.
- Expected output: concise but useful project map in Chinese, including purpose, stack, structure, runtime flow, commands, and notable risks.
Research Findings
- Top-level project is not a Git repository;
git status --shortreturned "fatal: not a git repository". - Root contains orchestration/docs files plus a
removed extracted runtimeapplication directory. removed extracted runtime/node_modulesis present, so dependencies appear already installed for the embedded runtime app.- The initial full file scan showed many bundled media assets under
removed extracted runtime/public, especially starter/planning/Seedance examples. README.mdstates this was extracted from the智念助手desktop app into an independent智念创作助手project.- The project is based on an existing extracted runtime Next.js standalone runtime; original source was deleted, so this is not a full source restoration.
- Root
package.jsononly orchestrates scripts:start/devcallremoved runtime start script,healthcallsscripts/health-check.mjs, andinfocallsremoved runtime info script. - Runtime package uses Next
^15.1.4, React^19.0.0, Supabase client, Ali OSS, lucide-react, TypeScript, Vitest, and ESLint, but it is treated as generated runtime. - Runtime state should be written to root
.runtime/, not underremoved extracted runtime. removed runtime start scriptloads.envand.env.local, optionally bundled.env.runtimeonly whenZHINIAN_LOAD_BUNDLED_ENV=1.- Startup creates
.runtime/data,.runtime/uploads, and.runtime/generated-results, then launchesremoved extracted runtime/server.jswithNODE_ENV=production. - Health check targets
/api/desktop/healthand expects JSON withappId: "removed-runtime"andok: true. .env.exampleshows the real generation path depends on Seedance / Volcengine Ark plus Aliyun OSS configuration.- Extraction notes confirm copied assets include Next standalone server runtime,
.nextoutput, runtimenode_modules, public/reference media, content manifests, and planning cases; secrets, user uploads, generated results, and Electron host/process manager code were excluded. npm run infosucceeded and reports runtime app idremoved-runtime, bundle timestamp2026-05-14T04:01:58.653Z, entryserver.js, and size949,760,759bytes.- App routes include:
/,/studio,/studio/[mode],/planning,/projects,/projects/[id], and/billing. - API routes include:
/api/assets,/api/assets/upload,/api/billing,/api/desktop/health,/api/generations,/api/generations/[id],/api/generations/[id]/retry,/api/projects,/api/projects/[id],/api/prompt/assemble, and/api/reference-templates. - File-serving routes expose runtime uploads and generated results via
/uploads/[...path]and/generated-results/[...path]. - Creation modes currently report one mode:
video_studio/宣传片创作台, editor typestoryboard_cards. - Starter catalog has 14 cases across
storefront_avatar_storyboard,music_sync_ad, andcreative_remix; planning cases include five visible categories such as short-video promo and premium-brand. - Compiled API code reveals a local JSON store at
app-state.jsonwithusers,assets,projects,generation_jobs, andcredit_transactions. - The default local user is
demo-merchant/demo@localmerchant.aiwith 9999 demo credits. /api/projectsreturns projects with their jobs; project creation is coupled to generation creation and deducts credits based on duration/resolution/ratio./api/assets/uploadaccepts multipartfile,role, and optionalpromptLabel; it stores to Ali OSS when OSS env is complete, otherwise writes to local uploads and records an asset./api/prompt/assemblebuilds a Chinese Seedance-style video prompt from shop/project details, selected template, storyboard, and optional avatar/outfit selection.- Seedance client defaults: base URL
https://ark.cn-beijing.volces.com/api/v3, modeldoubao-seedance-2-0-260128, ratio9:16, duration15, resolution720p. - Real generation creation posts to
/contents/generations/tasks; query uses/contents/generations/tasks/{id}. - Missing
SEEDANCE_API_KEYcauses a user-facing error and the project/job path refunds credits after marking the job failed. - Health response includes
services.seedanceConfiguredandservices.objectStorageConfigured. GET /api/generations/:idreads a local job, polls Seedance whenprovider_job_idexists, and backs up successful result videos to OSS or local generated-results.POST /api/generations/:id/retryloads the original project settings and re-runs the generation creation flow.GET /api/projects/:idreturns one project with jobs;DELETE /api/projects/:idremoves the project, jobs, and related credit transactions.GET /api/assetsreturns local assets for the demo owner;GET /api/billingreturns demo user and credit transactions.GET /api/reference-templates?mode=...returns starter templates filtered by mode.- Content definition has one canonical creation mode:
video_studio, product name宣传片创作台, editor typestoryboard_cards, reference-first workflow, and six asset slot labels. - Starter catalog contains 14 selectable templates and 85 local asset records from the Seedance guide plus local promo examples.
- Template categories represented in content are
storefront_avatar_storyboard,music_sync_ad, andcreative_remix; docs say legacy mode arguments are accepted for compatibility. - Planning page content has five planning cases:
短视频宣传类,剧情宣传类,热门玩梗类,卡通 IP 类, and品质高级类. - Avatar presets currently include one default digital-human model and one default outfit pairing.
- Runtime verification passed:
npm run infosucceeded,npm startlaunched Next onhttp://127.0.0.1:3000, andnpm run healthreturnedok: true. - Health verification reports
seedanceConfigured: falseandobjectStorageConfigured: false, matching the empty local env configuration. - Direct curl verification with
--noproxy '*'returned200 OKfor/studio,{"projects":[]}for/api/projects, one template for/api/reference-templates?mode=video_studio, and the demo billing user. - Starting the runtime created
.runtime/data/app-state.jsonwith the demo user and initial credit transaction.
Technical Decisions
| Decision | Rationale |
|---|
2026-08-11 - Task Module Consolidation Findings
- The visible result directory is the top navigation item
结果, backed by/assetsandcomponents/asset-manager.tsx; it currently exposes both an asset gallery and a task-history view. - The create page already has an independent right-side
任务模块incomponents/create-studio.tsx. Each task card currently shows a thumbnail, prompt-derived name, status, elapsed time, billing label, and a查看详情link that navigates to/assets?view=tasks&taskId=.... - The task module already loads
/api/assets,/api/generations/image, and/api/generations/video, so it has the local asset records needed for previews and downloads. GenerationJobpersistsprompt,inputAssetIds,inputUrls,requestPayload,provider,reqKey, status/timing/error, output asset IDs, and billing metadata. Image jobs store the original input underrequestPayload.input; video jobs additionally store assembled materials and normalizedrequestPayload.settings.- Existing first-party asset download route is
/api/assets/:id/download; generated result storage is served through/generated-results/[...path]and local files live under.runtime/generated-results. - Current physical
.runtime/generated-resultscontains local verification data and must not be deleted merely to remove the visible result page; deleting that directory would remove task preview/download backing files.
Issues Encountered
| Issue | Resolution |
|---|---|
git status cannot run because the project root has no .git metadata |
Treat this as a plain project folder and avoid Git-based assumptions |
zsh treats [id] in file paths as a glob pattern |
Quote bracketed Next.js dynamic route paths when reading them |
Initial plain curl calls hit a local proxy and returned 502/empty output |
Use curl --noproxy '*' for localhost verification |
Resources
- Project root:
/Users/inmanx/Documents/zhinian-creation-assistant - Runtime app:
/Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime - Root README:
/Users/inmanx/Documents/zhinian-creation-assistant/README.md - Runtime README:
/Users/inmanx/Documents/zhinian-creation-assistant/runtime/README.md - Startup script:
/Users/inmanx/Documents/zhinian-creation-assistant/removed runtime start script - Health script:
/Users/inmanx/Documents/zhinian-creation-assistant/scripts/health-check.mjs - Runtime info script:
/Users/inmanx/Documents/zhinian-creation-assistant/removed runtime info script - Extraction notes:
/Users/inmanx/Documents/zhinian-creation-assistant/docs/EXTRACTION_NOTES.md - App paths manifest:
/Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime/.next/server/app-paths-manifest.json - Compiled projects API:
/Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime/.next/server/app/api/projects/route.js - Compiled upload API:
/Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime/.next/server/app/api/assets/upload/route.js - Compiled prompt assembly API:
/Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime/.next/server/app/api/prompt/assemble/route.js - Compiled generation polling API:
/Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime/.next/server/app/api/generations/[id]/route.js - Compiled generation retry API:
/Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime/.next/server/app/api/generations/[id]/retry/route.js - Creation modes JSON:
/Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime/content/seedance-starter/creation-modes.json - Starter catalog JSON:
/Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime/content/seedance-starter/catalog.json - Planning cases JSON:
/Users/inmanx/Documents/zhinian-creation-assistant/removed extracted runtime/content/removed planning case manifest.json - Runtime local state file:
/Users/inmanx/Documents/zhinian-creation-assistant/.runtime/data/app-state.json
Visual/Browser Findings
- 2026-05-29 UI polish verification:
/create,/create?mode=video,/assets, and/settingswere checked during the product polish work.375,768,1024, and1440width checks showed no horizontal overflow after the compact header and mobile control changes.- The video duration dropdown on
/create?mode=videoexposes only4 秒through15 秒. - The header logo loads from
public/logo/zhinian-logo.png; after the final branding pass it has no border, background, or box shadow.
2026-05-29 UI/UX and Branding Findings
- The current source app is now a Web app in the repository root, not the old
removed extracted runtimestandalone-only flow described in the earliest findings. - GSAP is used through
lib/ui/motion.tsrather than directly sprinkled across components. - The UI direction is a professional creation workspace, not a marketing landing page.
- Visible English eyebrows/descriptions were removed from module headers per user preference.
- Topbar should remain compact and avoid horizontal scrolling below it.
- Product name is now
智念AIGC平台. - Logo source folder:
/Users/inmanx/Documents/icon/logo. - Current logo asset:
public/logo/zhinian-logo.png. - Current logo was generated from
/Users/inmanx/Documents/icon/logo/2d5b992caa14db16f594c4933e92e37e.pngby removing the white background and cropping whitespace. - Avoid using the white transparent logo on the light topbar unless the topbar itself becomes dark; wrapping the logo in a dark frame changes the brand feel.
2026-05-29 Seedance Findings
- Official Volcengine Ark "创建视频生成任务 API" docs say Seedance 2.0
durationsupports integer seconds in[4, 15], or-1for model-chosen duration. - Seedance 2.0 and Seedance 1.5 Pro support
adaptiveratio behavior. - Supported ratios include
16:9,4:3,1:1,3:4,9:16,21:9, andadaptive. - Supported resolutions include
480p,720p, and1080p, but Seedance 2.0 fast does not support1080p. framesis not supported for Seedance 2.0 / Seedance 1.5 Pro, so the current app should continue using integerduration.generate_audiodefaults true in the API and remains enabled in the app payload.
2026-05-29 Operational Findings
- For localhost verification, continue using
curl --noproxy '*'because local proxy settings can interfere with direct checks. - Before running
npm run build, stop the dev server (screen -S zhinian-dev-ui -X quitandpkill -f 'next dev --hostname 127.0.0.1 --port 3000') to avoid stale Next dev chunk issues. - After build verification, restart the dev server in screen session
zhinian-dev-uion127.0.0.1:3000.
2026-05-29 Deployment Findings
- Server one-command deployment is now
bash scripts/deploy.sh. - Docker Compose service name is
zhinian-aigc. - Docker Compose defaults to exposing host port
3000; setAPP_PORTin.env.localor shell to change it. NEXT_PUBLIC_APP_URLshould be set to the public domain or server URL in production so generated local file URLs are correct.- Persistent runtime data is bind-mounted through
./.runtime:/app/.runtime; this folder should be backed up on real servers. .env.localis intentionally used as the composeenv_fileand remains ignored by Git.- Current local machine does not have the Docker CLI available, so Docker build was not run here; script syntax, app tests, production build, and local health were verified instead.
2026-05-29 Public API and Task Management Findings
- User confirmed multi-task support should be task management logic, not an external message queue.
- Public API v1 now uses
ZHINIAN_API_KEYS, supportingAuthorization: Bearer <key>andX-Zhinian-Api-Key. - Task creation and provider execution are now split: submit routes enqueue
GenerationJobrecords; Worker ticks claim and process jobs. generation_jobsnow carries external client, idempotency, priority, attempts, lock, schedule, timing, and webhook fields.- Supabase/Postgres production mode expects the
claim_generation_jobsfunction fromsupabase/schema.sqlfor atomic task claiming. - Local JSON mode serializes task claiming through the existing local write queue and is intended for single-instance development.
- Worker execution can run as
npm run worker,npm run worker:once, or thezhinian-workerDocker Compose service. - Internal Worker processing goes through
/api/internal/worker/tickprotected byZHINIAN_INTERNAL_WORKER_TOKENin production. - API v1 routes are
/api/v1/capabilities,/api/v1/assets,/api/v1/jobs,/api/v1/jobs/:id,/api/v1/jobs/:id/cancel, and/api/v1/openapi.json. - Local verification created a public API job and processed it to
succeededthroughnpm run worker:oncein mock mode. - Public API asset access now includes
/api/v1/assets/:idand/api/v1/assets/:id/download. - Uploaded and generated assets created through public API flows are tagged as
api-client:<clientId>so integrations can query and download their own results later. - OpenAPI is generated dynamically from the current deployment origin at
/api/v1/openapi.json. - Operations handoff docs live in
docs/DEPLOYMENT.md; partner API docs live indocs/API.md.
2026-05-29 Image Tuning Findings
- Jimeng image generation supports the current
scaleparameter, so UI presets should submit numeric text-influence values for that engine. - EvoLink image generation does not use Jimeng
scale; the per-request engine-aware control should submit EvoLinkqualityinstead. - Current local
/api/healthreportsimage.generateusing EvoLink, so/createshould show生成质量options rather than文本影响.
2026-05-29 Account Login / SSO Findings
- User requested account login before release so the project is safe to use.
- Provided SSO guide recommends OAuth2 Authorization Code for Web SSO: redirect to
${AUTH_BASE}/oauth2/authorize, receivecodeandstate, then exchange code server-side at${AUTH_BASE}/oauth2/token. - OAuth client defaults in the guide use
client_id=customPCandscope=server;client_secretmust stay on the server. - Access tokens are JWTs; resource services should verify locally with JWKS from
${AUTH_BASE}/oauth2/jwksrather than calling auth on every request. - Minimum JWT checks from the guide: RS256 signature,
exp,nbf/iat, issuerhttps://pig4cloud.com, OAuth client id, scope/authority requirements. - Logout endpoint is
DELETE ${AUTH_BASE}/token/logout, but local session deletion remains required because existing JWTs may stay valid untilexp. - Current app has no login middleware or session helper; pages are client components under a global shell.
- Current local data store defaults all assets/jobs to
DEFAULT_OWNER_ID = "demo-merchant", so account login must also address per-user owner IDs for first-party UI APIs. - Public API v1 already has separate API key auth through
ZHINIAN_API_KEYS; SSO should preserve that server-to-server surface. - First-party UI APIs that currently need session ownership include
/api/assets,/api/assets/upload,/api/assets/:id/*,/api/generations/image*,/api/generations/video*, and/api/settings. - Generation services already accept optional
ownerId, so route handlers can pass the authenticated owner without rewriting provider dispatch or worker logic. - Retry helpers currently preserve the original request payload; they need to override
ownerIdon retry so a user cannot retry another user's job if they know the id. /uploads/*and/generated-results/*serve local runtime files directly; middleware should protect these paths for cookie-authenticated browser sessions./api/v1/*and/api/internal/worker/tickmust remain outside browser SSO middleware because they use API keys and worker tokens.- Implemented browser SSO with signed HttpOnly
zhinian_sessioncookies; middleware validates the signed session instead of exposing JWTs to client JavaScript. - JWT access tokens are verified in the callback using RS256 and configured JWKS, with issuer, client id, expiry, not-before, issued-at, and scope checks.
- Local file routes now resolve
storagePathback to an asset record and require the current owner to match before serving bytes. - Public API v1 remains API-key based and can still read/download its assets through public API routes even when browser SSO protects the Web UI.
2026-05-29 Password Captcha Login Findings
- User provided live auth configuration and a password grant sample; real secret values remain only in the ignored local environment file.
${AUTH_BASE}/code/image?randomStr=...returns a PNG captcha image.- The password grant sample successfully returns a JWT access token, refresh token, expected client/user claims, tenant id, and
serverscope. - The local
/api/auth/passwordendpoint verified the returned JWT with JWKS and created a signed browser session for the authenticated user. - Browser form login with the displayed math captcha succeeded and redirected to
/create; the topbar showed the authenticated username and logout button. - Logout cleared the session and returned to
/auth/login?loggedOut=1.
2026-05-29 Standalone Login Page Findings
- Login routes under
/auth/*should not render the shared app topbar; the login page is a standalone entry surface. - The login page now intentionally presents only the NIANXX logo,
智念AIGC平台, and the account/password/captcha form. - The visible
统一认证中心OAuth entry was removed from the login page after user feedback. - The standalone login page uses the existing GSAP motion helper layer (
runScopedMotion,revealChildren,pulseFeedback) for consistent app motion. - Browser viewport checks passed at 1280x800 and 390x844: no topbar, no SSO link text, logo and platform name present, login panel present, and no horizontal overflow.
2026-06-09 Repository and Runtime Status Findings
- The current repository root is a Git checkout on
maintrackingorigin/main. - The remote is
https://git.nianxx.cn/wangxuming/NianAIGC.git. - The workspace was overwritten from remote and is currently clean at
d98e58a docs: update public api docs. - The project uses npm (
package-lock.json) and Next.js dev startup throughnpm run dev. - Local Node version during verification was
v22.22.1, satisfying thepackage.json>=20engine. - During startup verification, ports
3000,3001, and3002were already occupied by local Node/Next processes or listeners; use a confirmed-free alternate port rather than assuming3000. npm run dev -- --hostname 127.0.0.1 --port 3003successfully started the current project and reached Next Ready in 2.9 seconds.- A smoke request to
http://127.0.0.1:3003/returned307 Temporary Redirectto/auth/login?next=%2F, confirming the login-protected app flow. - Current status check on 2026-06-09 found no
next dev/next-serverprocess and no listener on3003; the project is verified startable but not currently running.
2026-07-01 Account ID Partitioning Findings
- First-party browser APIs already pass the authenticated session user id into asset and generation flows.
- Public API v1 currently authenticates by
clientId:key, but jobs and assets are still created withownerId = DEFAULT_OWNER_ID. - Public API listing/detail access filters with
externalClientIdandapi-client:<clientId>tags, which works as an access check but keeps all API account data in one default owner partition. - The data store and Supabase schema already support
owner_idindexes and idempotency uniqueness by(owner_id, external_client_id, idempotency_key), so the change can be made by deriving a stable account owner id from the API client id. - Job detail and cancel routes currently check only
externalClientId; they should also require the account owner id so a client id collision cannot cross account partitions. - Implemented owner derivation as
publicApiOwnerId(client), yieldingapi:<sanitizedAccountId>. - Public API job creation, idempotency lookup, job listing/detail/cancel, asset upload/register/list/detail/download now use the derived API account owner.
- Documentation now states that
ZHINIAN_API_KEYSuses账号ID:keyand that records are partitioned underapi:<账号ID>.
2026-07-01 Password Login Captcha Finding
origin/mainis up to date atd98e58a; relevant history includesce358df 修改认证中心对接方式and288e31d 移除验证码输入框.- Current
components/auth-login-panel.tsxdoes not render captcha controls and submits onlyusername,password, andnext. - Current
app/api/auth/password/route.tsno longer requires captcha; it only forwardscodeandrandomStrif they are present in the request body. - Current local
.env.localpoints password login athttps://onefeel.brother7.cn/ingress/authwith client idcustomPC. - A local login probe with fake credentials returned
验证码不能为空; a direct token request to the configured auth center with the same non-secret dummy credentials also returned验证码不能为空. - Therefore the observed captcha error is coming from the configured auth center instance, not from the current AIGC frontend or password route validation.
- The operations SSO guide defaults external projects to OAuth client
app/app, recommends adding token-flow clients tosecurity.ignore-clients, and notescustomPCis a special platform-user compatibility client that skips AES password decrypt. - Dummy token probes showed
app/appreaches normal credential validation (用户名或密码错误) whilecustomPCis intercepted by captcha validation on the current auth center instance. - Updated the project defaults, docs,
.env.example, and local.env.localto useapp/appplusZHINIAN_AUTH_PASSWORD_ENC_KEY=thanks,pig4cloud. - After restarting the dev server, local
/api/auth/passwordwith dummy credentials returns用户名或密码错误, confirming the captcha interception is bypassed for the configured client.
2026-07-01 Internal RBAC / Account Management Findings
- Initial requested guide
/Users/inmanx/Desktop/organization-external-api.mdwas missing; updated guide/Users/inmanx/Desktop/organization-external-api(1).mdis available and has 1310 lines. - Existing JWT session already stores
authoritiesandscopeonAuthUser, making it suitable for internal RBAC. - Before the RBAC change, navigation exposed
创作,结果,日志, and设置to every logged-in user. - Before the RBAC change, middleware only checked whether a user was authenticated and did not distinguish ordinary users from administrators.
- Before the RBAC change,
/api/settingscheckedrequireAppUser()but not admin permission. - Before the RBAC change,
/api/logsrelied on middleware authentication only and did not callrequireAppUser()or admin checks inside the route. - RBAC now hides admin navigation for ordinary users and protects
/logs,/settings,/accounts,/api/logs,/api/settings, and/api/admin/*. - Updated guide covers organization, department, role, member, and enterprise user operation APIs.
- Organization/member APIs are served by
basic-capability-services-biz; enterprise user management APIs are exposed byhotel-staff-server-biz. - Member list APIs remain
@Innerand require headerfrom: Y. - Enterprise user APIs require
Authorizationand a token with administrator role1; otherwise they return仅管理员角色允许调用. - Account creation should use
/adminOrganization/organizationMember/addOrganizationMemberAndCreatePlatformUserto create the enterprise user and bind the organization member in one call. - Password maintenance is available through
/adminPcUser/resetPlatformUserPassword, withtenantId, numericuserId,newPassword, and optionalmustChangePassword. - Updated
/Users/inmanx/Desktop/organization-external-api(2).mdconfirms the external member list path is/adminOrganization/organizationMember/organizationMemberListthroughhotelStaff; only the basic service internal/organizationMember/organizationMemberListpath needsfrom: Y. - The updated
hotelStaffmember-list proxy is reachable from the app, but the current login token can still be rejected by the upstream service with仅管理员角色允许调用. - Local RBAC allow-listing
ceshiopas a platform admin only controls this app's pages and APIs; it does not grant upstreamhotelStaffadministrator role1inside the authentication center token. - Account management now treats upstream member-list administrator denial like a recoverable member-list-only limitation: the page can still load configured organization data and keep account creation/password maintenance available when those upstream endpoints allow the token.
- The updated organization guide exposes department creation at
POST /organizationGroup/createOrganizationGroupwithorganizationId,groupName, optionalgroupDesc, and optionalparentId. - The account page can create a department from the member form and then refresh/select the created department, so admins do not need to leave account management before creating members.
2026-07-02 Image Template Findings
- User requested image-generation templates with selection-time effect preview and preset prompt support.
- This should be account-managed data, not global environment configuration.
- Existing browser image generation API already uses
requireAppUser()and submitsownerId: user.id, so template APIs should use the same owner boundary. - Template configuration should remain inside the image generation module rather than the global settings page.
- The create page already keeps image/video prompt state client-side, detects the active image engine from
/api/health, and submits prompt, materials, size, force-single, and engine-specific tuning to/api/generations/image. - Implemented image templates with fields for name, category, description, preset prompt, preview image URL, width/height, force-single, and sort order.
- New first-party routes are
/api/image-templatesand/api/image-templates/:id; both require the app user and use the authenticated user id as owner id. - Local JSON state now normalizes
imageTemplates; Supabase deployments need the newimage_templatestable fromsupabase/schema.sql. - Browser verification used a temporary auth-disabled dev server to create one demo template, confirm the
/createleft template rail and template application behavior, then deleted the demo template. - Final dev server was restarted with the normal
.env.localauth-enabled environment on127.0.0.1:3001.
2026-07-02 Large Auth Session Finding
- A new real account could receive a successful
/api/auth/passwordresponse and still be redirected back to/auth/login?next=/create, which indicates the browser did not persist a valid session rather than an upstream password failure. - The session cookie stores the signed session plus access token so the app can forward the current login token to organization/staff APIs.
- Accounts with larger JWT or authority payloads can exceed a single browser cookie's practical size limit; splitting
zhinian_sessioninto chunked cookies keeps the same signed payload while allowing middleware and server helpers to reassemble it. - Settings no longer exposes a template tab.
- In image mode,
/createnow renders a left vertical模板选择rail with an icon添加模板button and thumbnail/name-only template cards. - The add-template entry opens an in-module template form modal; it is not exposed from settings.
- Clicking a template selects it and brings its preset prompt, image size, and force-single value into the generation console on the right.
- Desktop and narrow browser verification confirmed the template picker is an independent left-side panel beside the generation panel, not a child of the generation panel, with no horizontal overflow.
- The image/video/edit mode switch is now contained by the generation panel, not placed above or across the far-left template module.
- Template preview image upload now uses the same
/api/assets/uploadroute as workbench materials, so configured OSS storage is reused instead of accepting a manually typed preview URL. - New template configuration no longer exposes category or remark fields; the user-facing free text field is
简介, backed by the existing template description data. - Template preset prompts can contain
@图片1style placeholders. The generation console parses those tokens and shows missing upload slots below the prompt. - Placeholder upload slots bind the uploaded asset to the exact requested token, so clicking the
@图片2slot creates/updates the@图片2material instead of relying on upload order. - Material labels are preserved after removal to avoid silently breaking prompt references created by templates.
- UI/UX skill guidance for the add-template modal was applied as a focused SaaS workspace form: strong preview region, fewer visible decisions at once, clear bottom actions, and responsive stacking on narrow screens.
- While editing a template prompt, parsed placeholder chips are shown below the textarea so users can confirm which upload slots the template will request after selection.
- Image templates now store the target image generation engine in
settings.engine, with Jimeng usingsettings.scaleand Image2/EvoLink usingsettings.quality. - First-party image generation now accepts a per-request
engineoverride and passes it intosubmitImageJob(), so template engine choices are honored by the provider payload. - The generation console exposes the current image engine selector; applying a template updates this selector plus the matching parameter control.
- Material placeholders now require an explicit number, so
@图片1and@图2create upload slots while bare@图片or@图片这种普通文字stay plain prompt text. - Template prompt editing includes explicit placeholder insertion controls for image/video/audio slots, making the placeholder confirmation action deliberate instead of relying on unfinished
@typing. - Existing image templates can be reopened from the top-right edit action on each template card and saved through the account-scoped PATCH route.
- The template rail is intentionally wider on desktop and each card separates image preview, template selection, and template editing into distinct controls.
- Clicking a template thumbnail opens a larger preview dialog; choosing from that dialog applies the template and closes the preview.
- Typing
@in prompt editors now opens a temporary material draft slot instead of immediately writing a partial token into the prompt body. - A material draft is confirmed into the prompt only by Enter or explicit option selection; confirmed values are inserted with text boundaries so subsequent Chinese text is not absorbed into the token.
- The temporary draft slot is shared by the generation prompt and template preset prompt; invalid text such as
图片这种普通文字stays in the slot and does not create upload requirements. - Confirmed numbered tokens keep using the existing visual token overlay, placeholder chips, and missing upload-slot UI.
- The temporary material draft slot should remain visually inside the prompt editor surface, not below the input, so
@entry feels like an inline editing affordance. - Template preset prompts use the same token overlay as the generation prompt; confirmed placeholders such as
@视频1are visibly colored inside the input area before saving the template. - The image template rail now uses a wider desktop/tablet track (
390-480pxdesktop,315-375pxtablet) and stacks above the generation panel on mobile so reference previews stay browseable. - Template thumbnails use a
4:3viewport with contained images, allowing both9:16and16:9reference images to be inspected without cropping. - Template card application now requires the explicit
选择模板button; thumbnail clicks open preview and template name/description text no longer selects the template. - The template rail remains visible in image and video generation modes; only the image-editing modes (
局部重绘,智能超清) use the standalone editor layout without the template rail. - Template cards are fixed compact items (
138px x 184px) inside the rail list, so each template has a consistent smaller footprint. - The template rail height is synchronized from the right generation panel through a
ResizeObserver; the rail body is split into fixed header/action rows and a scrolling template-list row. - The scrollbar belongs only to
.image-template-rail-list;模板选择and添加模板stay outside the scroll container. - Template rail cards now use their preview image as a full-bleed card surface with
object-fit: cover. - Template card title, intro, and select action sit inside a bottom floating frosted-glass overlay; the select button remains the only action that applies the template.
- The zoom icon was removed from template cards, while the edit icon remains on the top-right corner above the image.
- Selected template cards use a heavier layered shadow and slight upward transform to create stronger depth.
- The template intro/description field is no longer part of the create/edit template UI or card display; old stored descriptions may still exist in data but are ignored by this surface.
- The template edit icon now sits inside the card's frosted-glass overlay instead of on the image corner.
- Template selection is a toggle: the first click captures the current right-side image generation state and applies the template; clicking the selected template again restores that snapshot and clears the active template state.
- The compact full-bleed template card target is
132px x 176px; the widened desktop rail should fit three cards per row. - The card footer uses a two-row frosted-glass grid: title spans the first row, while the select/cancel button and edit icon share the second row to avoid cramped text or overlap.
2026-07-02 Create Task Module Findings
- The creation console can reuse existing first-party APIs for a task list:
/api/generations/image,/api/generations/video, and/api/assets. GenerationJobalready has the needed task fields: prompt/name fallback, status, created/updated timestamps, capability, and output asset ids.- Generated task thumbnails can be resolved by mapping
GenerationJob.outputAssetIdsto assets from/api/assets; queued/running tasks need a placeholder when no output asset exists yet. - The existing results page already has a task view and detail panel, so the create-page
查看详情action deep-links to/assets?view=tasks&taskId=<id>instead of creating a separate task page. - Desktop create layout now supports three independent modules: left template rail, center generation console, and right task module. At narrower widths the task module drops below the main modules, and at mobile widths all modules stack without horizontal overflow.
- The right task module now targets
560-640pxon desktop/wide screens so task name, status, elapsed time, and查看详情can stay on one horizontal row. - Queued/running jobs without output assets should render a clear
生成中thumbnail placeholder instead of a generic empty asset icon. - Completed image-task thumbnails are preview actions inside
/create; clicking them opens the same large asset-preview surface while查看详情remains the route to the full task page.
2026-08-11 Platform-Owned Account System Findings
Existing authentication boundary
- Browser authentication currently depends on an external OAuth2/OIDC-style service: authorization-code callback and password-grant login exchange against
ZHINIAN_AUTH_BASE_URL. - The app signs the resulting
AuthSessioninto thezhinian_sessioncookie and derives first-party ownership from external claims such asauth:<clientId>:<subject>. lib/types.tshas only a minimalAppState.usersdemo shape (id,email,displayName); it is not an authentication user store.supabase/schema.sqlhas no local users, organizations, memberships, password credential, or audit schema.- The current
/accountsimplementation proxies an external organization/member service and must be replaced for local account management. .runtime/data/web-app-state.jsoncontains a demo user, while historical assets/jobs use external-auth owner IDs; a migration must preserve those links via an import/mapping layer.
Confirmed product decisions
- Production source of truth: platform-owned user data in Supabase/Postgres; local development fallback:
.runtime/dataJSON. - OAuth2 dependency is removed from browser account authentication.
- Roles:
super_admin,organization_admin,user. - Super admins manage all organizations, accounts, logs, system settings, and global usage.
- Organization admins manage ordinary users in their own organization, reset passwords, and view organization aggregate usage; they do not view logs, manage system settings or organization lifecycle, view member assets/tasks, or grant organization-admin roles.
- Ordinary users access their own creation/assets/tasks and can change their own password.
- One account is created once and belongs to one organization only; no cross-organization membership or organization switching.
- Organizations are created, renamed, disabled, or deleted only by super admins.
- New accounts are administrator-created; public registration is not provided.
- Login identifier is a unique immutable phone number; email is removed.
- Users cannot self-edit their phone number.
- Admins set initial and reset passwords; users may change passwords in settings; first-login password change is not forced.
- Accounts can be disabled or permanently deleted. On permanent deletion, login identity is removed, usage records are retained for finance reconciliation, and assets/tasks are transferred to an organization archive owner.
- Legacy accounts and history are migrated by phone/account mapping; external password hashes are not migrated, so administrators set new passwords without forced first-login change.
- Unified phone/password login is used for all roles; users do not select a role at login.
- Login security: five failed attempts trigger a 15-minute account lock, with IP-level request limiting.
- The first super admin is created through a one-time initialization command.
Implementation defaults pending no further product decision
- Use Node's built-in
crypto.scryptpassword hashing with per-user salts and constant-time verification; never store plaintext passwords. - Keep the existing signed/chunked HttpOnly session-cookie mechanism, but issue sessions from local user records rather than external JWT claims.
- Treat the migration export/mapping file as an operator-provided input; do not invent legacy account rows that are not present in the repository.
2026-08-11 Implementation result
- Platform browser authentication is now first-party and uses only the immutable phone identifier plus a local password; the old OAuth routes remain as compatibility redirects/deprecated helpers and are not part of browser authentication.
- Supabase production tables and local JSON fallback share the same account-store contract, so login, role checks, lockout, organization scoping, and password changes do not depend on the external organization service.
- Organization status is enforced at login/session refresh. Disabling an organization therefore blocks its organization-admin and ordinary-user sessions while preserving their records for later reactivation or audit.
- Organization-admin usage responses intentionally omit account-level options/details; only the organization aggregate view is exposed to that role.
- Account hard deletion removes the login identity, reassigns assets/jobs/projects/templates to the organization archive owner, and leaves usage events untouched for finance reconciliation.
- Final local verification passed with 22 test files / 80 tests, TypeScript, script syntax checks, production build, health endpoint, login page, and role-boundary HTTP smoke tests.
Session: 2026-08-11 - Enterprise Billing Discovery
Official pricing catalog findings — 2026-08-11
- Alibaba Model Studio
wan2.7-image-proBeijing price is ¥0.50/image;wan2.7-i2vis ¥0.60/second at 720P and ¥1.00/second at 1080P. The current environment uses the datedwan2.7-i2v-2026-04-25model key, so the default rule follows that key. - Volcengine Ark
doubao-seedance-2.0official pricing is token-based. For the documented no-input-video, 16:9, 5-second example, the output-only totals are ¥2.31 at 480P, ¥4.97 at 720P, and ¥12.39 at 1080P; the catalog stores those as ¥0.46, ¥0.99, and ¥2.48 per output second after fen rounding. - EvoLink GPT Image 2 is token-based. The official estimator page gives approximately $0.047 for medium quality, 1K, 1:1, and no reference image; with the confirmed fixed FX rate of 7.20, the catalog stores ¥0.34/image as a rounded baseline rather than claiming a fixed provider bill.
- Jimeng/Volcengine Visual 4.6's official billing page says the latest price is shown in the console and bills by successful call. The catalog therefore uses the public ¥200/1000-image activity package only as an enabled, editable reference baseline of ¥0.20/image and labels it for administrator review.
- Default rules are inserted only when the same provider/capability/model/variant is absent. A pre-existing generic rule for a provider/capability suppresses automatic defaults for that scope, preserving administrator fallback behavior.
Confirmed from current code
- Platform-owned accounts and roles already exist:
super_admin,organization_admin, anduser. - Platform generation jobs carry
usageContextwith account and organization identity when a first-party session is available. UsageEventcurrently records capability/provider/account/organization and a quantity of1, but has no provider unit cost, markup, charged amount, currency, wallet, recharge, or ledger reference.recordUsageForJob()is idempotent byjobId, excludes mock and public API jobs, and currently only creates an analytics event after the job path is established; it is not a financial debit.- Existing usage reports aggregate task counts and are already role-aware through
/api/usageand/api/admin/usage; they need to be extended with money and balance views. - The data layer supports local JSON fallback and Supabase/Postgres, so billing must preserve both storage contracts and use an atomic/idempotent financial operation in production.
- Existing workspace has uncommitted account-system changes owned by the user; future billing edits must be additive and avoid resetting unrelated work.
- The current Node executable is not available as
nodein the shell PATH during this discovery call; verification should use the workspace dependency/runtime path if needed.
Open decision for user
- Recommended billing unit: charge each successful provider request using the provider's source-unit price from a super-admin-managed price catalog, then multiply by a configurable organization/platform markup; store the final amount and the exact price/multiplier snapshot on the debit ledger so later price changes cannot rewrite history.
Confirmed decisions and removal scope
- User confirmed the billing model: provider-native billing quantity × super-admin-managed markup, with the final amount and pricing snapshot retained in the ledger.
- User requested removal of both image capabilities:
image.upscale(高清/智能超清) andimage.inpaint(局部重绘). - Removal must cover the shared capability types, provider capability builders, generation routes, asset edit routes, create/editor UI, engine/settings status, public API validation/OpenAPI, tests, documentation, and stale environment variables.
- Remaining first-party generation capabilities are image generation and video generation; mock behavior remains available for local verification.
- Generation jobs are currently queued before provider execution, so billing integration should reserve funds at submission, release/refund the reservation on provider failure/cancellation/expiry, and finalize the debit on successful completion. This preserves balance safety while making the amount visible on the generation record.
Capability removal implementation map
components/create-studio.tsxcurrently embeds the edit-mode switch andImageEditor; the remaining studio should use onlyGenerateMode = image | video.components/image-editor.tsx,app/api/assets/[id]/inpaint/route.ts, andapp/api/assets/[id]/upscale/route.tsare capability-specific and can be removed without affecting upload or normal generation.app/image-edit/page.tsxis a legacy compatibility entry; it will redirect to/createwithout an edit mode so old bookmarks do not expose a removed capability.lib/jimeng/capabilities.ts,lib/evolink/image-client.ts,lib/server/app-settings.ts, public API parsing/OpenAPI, usage labels, and documentation contain stale capability definitions that must be reduced to image/video generation.- Historical asset
sourcevalueseditedandupscaledremain readable for backward compatibility, but no new job/API/UI path will create or advertise them.
Billing implementation map
- Billing amounts use integer fen (
CNY) to avoid floating-point ledger drift; price rules hold provider, capability, optional exactreqKey, native unit, standard unit price, and multiplier. - A generation job stores a billing quote snapshot in
generation_jobs.billing; charge/refund ledger entries are keyed byjob-charge:<jobId>andjob-refund:<jobId>for idempotency. - Real provider jobs require an organization and a matching enabled price rule; mock jobs remain uncharged so local mock mode can still be used before real provider prices are configured.
- Public API jobs without an organization context remain uncharged for backward compatibility; first-party platform jobs use the authenticated organization context.
- User billing routes expose organization balance, organization ledger, personal ledger summary, and offline recharge requests; super-admin routes expose price rules, organization wallets, and recharge review operations.
Billing implementation result
- Added local
billing-state.jsonfallback and Supabasebilling_price_rules,billing_wallets,billing_ledger, andbilling_recharge_requeststables with thebilling_post_wallet_entryatomic RPC. - Added corporate transfer account configuration through the super-admin service settings and exposed the account details on the member billing page before recharge submission.
- Added billing labels to create-task and asset-task views so each generation record exposes the quoted/charged/refunded amount.
- Final verification uses the project Vitest runner rather than Bun's built-in runner; all 23 test files and 81 tests pass.
Session: 2026-08-11 - Billing Center UI Redesign
Visual audit
- The previous super-admin page rendered balance cards, account settings, ledgers, price rules, organization wallets, and recharge review as one continuous stack. The long default-rule notes made the pricing area disproportionately tall and weakened the primary balance/action hierarchy.
- The redesign keeps the page light and editorial-minimal: organization balance is the first visual anchor, personal and organization consumption are secondary, and operational controls are grouped by role and task.
- Price-source links and explanatory notes remain available but are truncated to compact metadata lines. Custom rule creation is hidden behind an explicit disclosure so the default catalog stays readable.
- The page uses existing Lucide icons and motion helpers; no new dependency or image asset was needed for this data-heavy internal finance surface.
Tabbed operations and balance semantics
- The existing billing domain already models one organization wallet plus account-attributed immutable ledger entries. It does not model a personal wallet, so manual member selection must remain attribution-only to avoid contradicting the shared organization balance requirement.
- Existing super-admin settings persistence already supports the four corporate-account fields through
saveApiSettings; the billing center now exposes the same path through a focused billing endpoint instead of duplicating configuration storage. - The admin billing payload now includes organizations, members, all recent ledger entries, price rules, and recharge requests so the balance tab can show both organization wallets and per-member net consumption without additional per-row requests.
Billing error recovery — 2026-08-11
- Local fallback storage reproduces the new billing page and all initial billing endpoints successfully; the reported generic server error is therefore not reproducible without the user's deployed request/response or server log.
- The most likely external-state failure is an unapplied or partially applied Supabase upgrade: billing tables/RPCs or Phase 53
variant_key/sourcecolumns are missing even though the application code expects them. - Billing storage now translates matching Supabase schema-cache/relation/column/function errors into an explicit instruction to execute
supabase/schema.sql; the client also handles HTML/plain-text 500 responses without throwing a JSON parse error.
Super-admin blank state — 2026-08-11
- A super-admin is intentionally not required to belong to an organization. The member billing endpoint therefore returns 422 for that identity, which must not prevent the admin billing center from rendering.
- The super-admin view now treats
/api/admin/billingas its source of truth and aggregates organization wallets and ledger entries into a platform overview payload for the balance hero and recent ledger.
Parameterized billing rules — 2026-08-11
- The billing domain currently stores only
variantKey,unit, base unit price, and markup. Matching only readsresolution; it cannot distinguish user-selected quality, size, aspect ratio, or reference-image count. - The generation request payload already carries nested
settings,providerPayload, andinputrecords. Video requests expose duration/resolution/ratio; image requests expose provider-specific size/quality and image count in their provider payloads/settings. - The safe extension is structured JSON conditions plus an explicit quantity source. Legacy
variantKeyvalues remain readable and are translated into a resolution condition during matching. - Quote selection should prefer exact
reqKeyand the highest number of matching conditions; equal-specificity matches must be rejected as ambiguous in configuration rather than silently choosing by insertion order. - The client may request a quote for preview, but task submission must call the same server matcher again and persist the normalized parameters, matched rule, and final amount snapshot.
Parameterized billing implementation — 2026-08-11
BillingPriceRulenow supportsconditions,quantitySource, andpriority; the Supabase schema persists them and the local JSON store canonicalizes condition keys/values for duplicate detection.- Normalized billing parameters are derived from the actual provider payload rather than from a client-supplied price. Current UI choices map as follows:画幅→
size/aspectRatio, EvoLink quality→quality, Jimeng text influence→scale, video ratio→aspectRatio, video duration→duration, video resolution→resolution, and image/video references→referenceImageCount. - A generic rule remains a deliberate fallback. To require every combination to be explicitly configured, the super-admin can disable/remove the generic fallback and keep only conditional rules; an unmatched enabled scope then returns a billing configuration error before a real job is created.
- The quote snapshot stores the effective legacy/structured conditions, normalized parameter map, quantity source, quantity, markup, and final fen amount. Later price edits therefore cannot rewrite historical ledger entries.
Task detail consolidation — 2026-08-11
- The user confirmed that “删除结果目录” means removing the visible result-directory experience from the front end, not deleting generated-result files or storage records.
- The user confirmed that task details, prompt/material inspection, result preview, and download should all be handled inside the
/createtask module. - The user confirmed that the platform is desktop-only; no additional mobile breakpoint work is part of this change.
- The implementation keeps
/api/assets,/api/assets/[id]/download,/api/v1/assets, and local/OSS result storage intact./assetsremains only as a compatibility redirect to/create.
Direct billing top-ups — 2026-08-11
- The current recharge flow is request/review based: members POST
/api/billingwith payer and transfer details, the request is stored aspending, and super admins PATCH/api/admin/billing/recharges/:idto approve or reject it. - The billing center exposes this workflow through a member
线下充值tab, a super-admin充值审核tab, a pending-recharge metric, and review actions inRechargeTable. - The existing
/api/admin/billing/adjustmentsendpoint already supports direct positive/negative organization-wallet entries; positive entries are the correct current administrator top-up path and write immutable ledger entries. - The billing account configuration (
/api/admin/billing/account) should remain because it contains the organization’s future payment/account information, but it should not be tied to a review queue. - The recharge-request model is only referenced by the billing UI, billing GET/POST routes, the admin review route, billing store/service, schema table, docs, and billing tests. It can be removed from the active application surface without affecting task charging/refunding.
- Product rule confirmed by the user: no manual review; administrator top-ups post directly, and a future user payment callback should post automatically after payment success.
- The implementation now removes the active request/review UI and APIs, keeps the corporate account settings for future payment display, and exposes
postOrganizationTopUpas the shared idempotent entry point for administrator credits and future payment-success callbacks. - Verification confirms there are no remaining active references to recharge requests, review endpoints, review UI, or pending-review CSS. The direct top-up test records a
rechargeledger entry and preserves shared-wallet attribution.
Simplified billing price controls — 2026-08-11
- The current
PriceManagementUI exposes provider, capability, unit, standard price, markup, req key, legacy variant, quantity source, priority, seven condition fields, notes, custom rule creation, JSON condition editing, and enable/disable controls. - The catalog is already auto-seeded from provider reference prices and parameter variants; the requested admin responsibility is only the customer-facing markup multiplier.
- The safe product boundary is read-only standard catalog data plus a single multiplier update action. The admin price PATCH route should reject edits to base price, conditions, quantity, provider/model identity, and enabled state.
- Implemented the boundary: the billing center now renders a compact read-only price catalog, and each row exposes only
调整倍率; the former parameterized-rule creation form and structural controls are removed. - The admin price collection endpoint is now read-only, while the item PATCH endpoint accepts only
markupMultiplierand rejects changes to platform standard costs, provider/model identity, parameters, quantity rules, priority, notes, and enabled state. - Verification passed with
bunx tsc --noEmit,bunx vitest run(23 files / 86 tests), Node-backed production build,git diff --check, browser no-legacy-form/no-horizontal-overflow checks, and a clean local-server restart.
Parameterized billing catalog — 2026-08-11
- User confirmed that every service/model must list its cost-affecting parameter tiers, while the super administrator only edits the multiplier for each tier.
- Confirmed implementation direction: platform-owned parameter dimensions (for example quality, resolution, and reference-image count) are listed under each service; the quote engine combines the selected tiers instead of requiring a manually maintained Cartesian-product rule for every combination.
- EvoLink GPT Image 2 exposes quality, resolution, size/aspect, count, and reference-image controls; its public page states that quality changes output-token cost and that high quality is approximately four times medium, so a single generic medium rule is insufficient.
- Implemented
parameterDimensionson the platform catalog. Each tier exposes a read-only standard factor/rate and an editable markup multiplier; combinations multiply the standard factors and use the highest selected markup multiplier once. - EvoLink now lists quality, resolution, aspect-ratio, and reference-image tiers. Existing video resolution rules are grouped under their service/model, while legacy single-rate services remain visible as a default tier.
- The admin price PATCH route accepts either a legacy service multiplier or a dimension/tier multiplier target, while rejecting standard-cost and catalog-structure edits. The Supabase schema adds
parameter_dimensionsand the seed path backfills existing built-in rules without overwriting configured multipliers. - Verification passed: EvoLink medium/high quote tests, tier-multiplier update test,
bunx tsc --noEmit,bunx vitest run(23 files / 88 tests),bun run build, browser service-card/dimension/overflow checks,git diff --check, and clean local-server health (/api/health200).
Account directory redesign — 2026-08-11
- The current
/settingspage rendersAccountSecurityPanel, while/accountsis currently restricted to admin sessions and only renders administrator organization/member management. - Moving password change into
/accountswithout changing access would remove ordinary users' ability to change their own password. The safe product interpretation is to make/accountsauthenticated-user accessible, render self-security for every user, and keep/api/admin/*and administrator management controls role-protected. - The existing account page is a dense vertical stack with a six-column create form that becomes visually cramped at the tested desktop viewport. The redesign will use a restrained two-column desktop composition: personal security as the stable account surface, administrator controls in a separate work area, and members in a dedicated list.
- Existing API contracts and field names remain unchanged:
/api/auth/password/change,/api/admin/accounts,/api/admin/accounts/password, and/api/admin/organizationscontinue to own their current operations. - Design read: modern minimalist enterprise utility surface, preserving the current green accent and light theme with
DESIGN_VARIANCE 5,MOTION_INTENSITY 3, andVISUAL_DENSITY 4. - Final layout uses a stable desktop grid at the tested 1280px viewport with no horizontal document overflow. The page keeps the account directory as a real product surface rather than a marketing composition, so no generated imagery or decorative motion was added.
Account workspace correction — 2026-08-11
- The first account redesign over-separated identity, security, admin actions, and member data. “账户信息” and “超级管理员” were two labels for the same current-user identity and should not be rendered as separate surfaces.
- The corrected information architecture is a single workspace with a single identity summary, followed by security, administrator operations, and member directory sections. This removes visual fragmentation while preserving all actions and APIs.
Organization-only billing top-ups — 2026-08-12
- The billing wallet is already organization-scoped, but the administrator adjustment API and UI still accepted an optional
accountIdfor recharge/debit ledger entries. - The member balance table still exposed “归属上账”, which contradicts the confirmed rule that organization administrators and employees all use the same organization quota and that no personal top-up ownership exists.
- New recharge and balance-adjustment entries will be normalized without
accountId; generation charge/refund entries retain the acting account ID so member consumption reporting remains available. Existing historical ledger entries are preserved. - Implemented the invariant in the local billing store and Supabase wallet RPC; stale callers may still send
accountId, but new recharge/adjustment rows are persisted without it. - Removed the member selector and “归属上账” action from the billing center, while retaining the member consumption read-only table.
- Ledger display now hides historical personal labels for recharge/adjustment rows; task charge/refund rows can still show the executing member for consumption context.
- Verification passed with focused billing tests (13/13), full Vitest suite (24 files / 92 tests), TypeScript, production build, and
git diff --checkfor the reviewed tracked files.
Frontend encoding diagnosis — 2026-08-12
- Source files inspected (
app/layout.tsx, billing page/component, and global CSS) decode as UTF-8 without BOM, null bytes, or replacement characters. - Both
127.0.0.1:3000andlocalhost:3000returntext/html; charset=utf-8; HTML contains correct Chinese strings and no common mojibake markers. agent-browserrendered the login page and an isolated auth-disabled billing page with correct Chinese text; the billing screenshot also appears visually normal.- Two Next dev-server processes currently listen on the same workspace/port family (IPv4 and IPv6), so stale browser state or hitting different dev processes is a plausible local explanation. No encoding source change was made because the reported corruption is not reproducible yet.
Autofilled login submission — 2026-08-12
AuthLoginPaneldisabled the submit button when React state values were empty, even though browser autofill could populate the visible inputs without firing the stateonChangehandler.- The form now has named, required controls and reads
phone/passwordfromnew FormData(event.currentTarget)on submit; the button is only disabled by unavailable configuration or an active submission. - A browser regression check filled the displayed phone/password and confirmed a
POST /api/auth/passwordrequest was sent after clicking the button. - Verification passed with the focused auth-panel tests (2/2), full Vitest suite (24 files / 92 tests), TypeScript, and production build.
Next development cache recovery — 2026-08-12
- The reported runtime overlay was
Cannot find module '/9971.js', with the require stack rooted in.next/server/webpack-runtime.jsand the password route. - The active
.nextdirectory referenced9971.jsbut the chunk was missing; two Next dev servers were also running against the same workspace/port family. - Stopped both stale dev servers, moved
.nextto.next.corrupt-20260812-1042instead of deleting it, and started one cleannext devserver on127.0.0.1:3000. - The clean server compiled
/create,/auth/login,/api/auth/password, and/api/health; browser verification reached the login API and received the expected 401 for intentionally invalid credentials, with no missing-module overlay.
Dev/production cache isolation — 2026-08-12
- The follow-up
ENOENTreferenced a missinglucide-reactvendor chunk under.next/server/vendor-chunks, showing that a later development restart was still sharing the production build directory. next.config.tsnow selects.next-devfornext devand keeps.nextfor production builds;.next-dev/is ignored by Git.- Moved the partially generated
.next-devdirectory to.next-dev.corrupt-20260812-1108for recovery, then started exactly one clean development server. - Browser and HTTP verification passed:
/auth/loginrendered Chinese labels without a runtime overlay,/api/healthreturned 200,/createredirected to login as expected, and the regenerated Lucide vendor chunk exists in.next-dev.
Local super-admin credential recovery — 2026-08-12
- The local account store contains the super-admin phone and password hash, not a recoverable plaintext password; the old password therefore could not be displayed safely.
- After the user selected the super administrator, generated a new strong password and updated the account through
/api/admin/accounts/password; the new password is not stored in source, documentation, or planning files. - Saved the credential under the project browser vault profile
super-admin, then cleared cookies and verified the saved profile logs in successfully and reaches/createwith super-admin navigation.
Inline generation cost estimate — 2026-08-11
- The create page already debounced requests to
/api/billing/quotewith the same generation payload used on submit, but the preview lived beside the top-level submit button rather than beside the parameters that change the quote. - The cost card now surfaces the resolved amount, quantity, effective multiplier, and matched parameter tiers next to the parameter controls. It also distinguishes initial, loading, and unavailable states without inventing a client-side price.
- The center workbench is narrow at 1280px because the template and task rails are both visible. A container query keeps the estimate beside the fields when there is room and stacks it inside the parameter area when the actual center column is too narrow; the document remains overflow-free.
Fixed EvoLink quote and user-facing estimate — 2026-08-11
- The overcharge came from an invisible 2K provider default being combined with the 4× high-quality factor and the 1.5× platform multiplier:
¥0.34 × 4 × 4 × 1.5 = ¥8.16. - The confirmed product rule is now enforced in the provider client: EvoLink requests use fixed 1K resolution, and the billing catalog defaults to the same 1K tier. The old resolution environment setting/configuration field was removed to prevent request/quote drift.
- The previous
1.5×multiplier produced¥0.51/张for medium and¥2.04/张for high quality; the platform-wide default is now1.2×, so the same baseline becomes¥0.41/张and¥1.64/张. - Ordinary users now see only
本次预计消耗额度and the amount; platform multiplier, quantity, selected tiers, and helper/status copy are not rendered in the estimate card. Super-admin price controls remain available in the admin billing center. - Earlier browser verification confirmed the card resolved to
¥0.51for standard and¥2.04for high under the previous 1.5× baseline; the current 1.2× values are covered by the live quote checks recorded below.
Unified default billing multiplier — 2026-08-11
- The platform default markup is now
1.20×for all built-in provider rules and every seeded parameter tier, replacing the previous1.50×default. - The local initialized billing state was synchronized to
1.20×for all service rules and parameter tiers; standard-cost values remain unchanged. - The admin adjustment capability remains available for intentional service/tier overrides; only the default baseline changed.
Cross-provider pricing audit — 2026-08-11
- The shared quote formula is
ceil(effective provider standard unit cost × native quantity × one platform markup). Parameter standard factors are applied to the provider baseline first; the platform multiplier is not applied once per parameter dimension. - Bailian
wan2.7-image-procurrently matches the official Beijing price of ¥0.50 per generated image. Its current platform quote is ¥0.60/image at the default 1.20× multiplier. - Bailian
wan2.7-i2v-2026-04-25currently matches the official Beijing prices of ¥0.60/second at 720P and ¥1.00/second at 1080P. The current quantity source is video duration, so a 5-second quote is ¥3.60 and ¥6.00 respectively after 1.20× markup. No additional parameter multiplier is indicated by the official model page. - Ark
doubao-seedance-2.0currently stores the documented no-input-video, 16:9, 5-second examples as rounded per-second baselines: ¥0.46/second (480P), ¥0.99/second (720P), and ¥2.48/second (1080P). These produce current platform quotes of ¥2.76, ¥5.94, and ¥14.88 for five seconds at 1.20×. - The Ark source explicitly defines Seedance video billing as
token price × token usage, with token usage affected by input-video duration, output duration, output dimensions, and frame rate; input video costs more than no-input video, and exact usage is returned inusage.completion_tokens. The current flat resolution-only Seedance catalog is therefore correct only as a no-input-video estimate, not as an exact quote for all accepted video/audio/material combinations. - The current Seedance UI/API accepts video and audio materials, but billing normalization records only duration and resolution for the Seedance rule. This is a pricing-model gap: input-video tasks can be underquoted, and exact ratio/frame-rate/material dimensions are not represented in the catalog.
- The current Seedance per-second storage also introduces sample-rounding drift versus the official 5-second totals: 0.46×5=¥2.30 vs ¥2.31, 0.99×5=¥4.95 vs ¥4.97, and 2.48×5=¥12.40 vs ¥12.39. The difference is small but comes from rounding the sample total to a per-second fen price before multiplying.
- Jimeng/Volcengine Visual
jimeng_seedream46_cvtobhas no stable public API price in the current official API document. The catalog's ¥0.20/image is explicitly an editable reference baseline derived from the public ¥200/1000-image activity package, not a guaranteed live API list price. The create UI forces single-image output, so the current one-image quantity is consistent with the active first-party flow. - No code or production pricing data was changed during this audit. A follow-up implementation needs a product decision for Seedance: keep conservative fixed reference estimates, or add provider-native token/usage reconciliation and a visible estimate policy for input-video jobs.
Seedance native usage settlement — 2026-08-12
- The confirmed policy is: submit-time billing is a wallet reservation; successful Seedance tasks settle against the provider's
usage.completion_tokens; a missing usage field leaves the reservation unchanged. - Seedance 2.0 token prices are represented as fen per million tokens: 480p/720p
4600without input video and2800with input video; 1080p5100/3100; 4K2600/1600. The platform markup is applied once to the actual token cost. - The initial Seedance quote now uses the official token-usage estimate. If an input video is present without duration metadata, the reserve uses the supported 15-second upper bound so the organization is not under-reserved by default.
- Query responses read both top-level and nested
usage.completion_tokens/completionTokensshapes. Settlement usesjob-settlement:{jobId}as its idempotency key, so worker retries cannot create a second adjustment. - A lower actual amount creates one
refunddifference entry; a higher actual amount creates one additionalchargedifference entry. The job billing snapshot is updated to the final amount and retains the reservation and provider usage details. - The Seedance catalog now includes the documented 4K reference variant, and the shared video resolution options accept
4k; fast-model resolution restrictions remain unchanged.
Unbound account quote preview — 2026-08-12
- The empty estimate was caused by the current platform super-admin record having no
organizationId. The quote route reused the strict organization check used for real task submission, returned an error, and the client rendered the missing quote as—. - Quote-only helpers now allow an unbound session to resolve platform pricing. This does not change charging:
submitImageJob/submitVideoJobstill call the strict path, and an unbound real task cannot be submitted or charged. - Browser verification of the reported parameter combination (
Image2,9:16,精细) now returns¥1.64; direct service verification with one reference image resolves the same amount and parameter snapshot.
Production billing runtime and Mock removal — 2026-08-17
- The live create UI already debounces
POST /api/billing/quote, but it clears any non-2xx response or{ quote: null }to—, hiding whether the backend is unavailable or billing is intentionally bypassed (components/create-studio.tsx). - After the static frontend refactor, Next has no API routes; the Go application owns
/api/billing/quoteand mounts it through/api/billing/(backend/internal/httpapi/billing.go,backend/internal/application/application.go). Catalog.Quotecurrently returns no quote forprovider == "mock". The runtime automatically selectsmockwhen credentials are absent or the*_MOCKsetting is truthy/auto (backend/internal/billing/catalog.go,backend/internal/application/runtime.go). This is incompatible with the confirmed formal-production requirement to remove Mock mode.Dockerfilebuilds only the static Nginx Web image, while the checked-indocker-compose.ymlstarts that image plus the obsolete Node Worker and routes the Web container's/apito an intentional 404. Without a Go API service or reverse proxy, quote requests cannot reach the billing service.- Confirmed implementation boundary: production provider targets must never become
mock; required provider credentials are validated at Go application startup, and quote/creation requests fail with an explicit configuration error if the selected provider is unavailable. The billing catalog and wallet charge/refund flow remain authoritative.
Quota guard and super-admin billing exemption — 2026-08-12
- Confirmed product rule: ordinary platform accounts must not dispatch a real generation task when the shared organization balance cannot cover the frozen quote; the API should return the existing 402 insufficient-balance error so the create page can show it.
- Confirmed product rule: super-admins still receive a provider-cost quote and successful jobs still write the calculated amount into usage records, but super-admin generation is quota-exempt. It must not require an organization, inspect wallet balance, create charge/refund ledger entries, or block on quota.
- The existing submission path already charges before worker/provider dispatch and maps
InsufficientBalanceErrorto HTTP 402. The missing pieces are role propagation, a persisted quota-exemption marker, and skipping wallet operations while retaining Seedance actual-usage settlement in the billing snapshot.
Billing UI alignment and multiplier dialog — 2026-08-11
- Price-source links and explanatory notes now use a two-track metadata row: the source action keeps its intrinsic width while the note truncates in the remaining space, preventing baseline drift across service cards.
- The create-page estimate card uses the compact
预估消耗label and stays in the same parameter row at the tested desktop width; a live quote check showed EvoLink Image2 standard at¥2.04for one image. 调整倍率now opens an in-app modal with the current multiplier, standard cost, current customer price, projected customer price, range validation, keyboard focus, and Escape dismissal. The browserwindow.promptpath is removed.- Design polish followed the taste-skill direction: compact enterprise information hierarchy, shared alignment grid, restrained motion, and an in-context dialog instead of a browser-owned prompt.
Production billing runtime and Mock removal — final verification — 2026-08-17
- Removed the remaining active placeholder-generation path: real provider targets are the only runtime targets,
ImportMock/the public mock video asset are gone, and missing credentials produce explicit failed/unavailable states. - Verified the Go backend with
go test ./...,go vet ./..., and a successful API package build using a temporary official Go toolchain. - Verified the frontend with TypeScript (
--incremental false), 54 Vitest files / 173 tests, and a successful Next production build. ACK manifest assertions, JSON contract parsing, shell syntax, andgit diff --checkalso pass. - Docker Compose was not started because this host has no Docker CLI/daemon. The Compose topology and Nginx proxy are statically checked; live PostgreSQL/provider credentials and deployment rollout remain deployment-time prerequisites.
- Historical database rows, usage exclusions, UI labels, and explicit test doubles still recognize
mockonly for compatibility/fixture handling; no new production task can select or execute Mock mode.
2026-08-12 - Alibaba Cloud RDS PostgreSQL Refactor
Confirmed facts
- The current production database path is not a direct PostgreSQL connection.
data-store,account-store, andbilling-storeuse@supabase/supabase-jsover PostgREST and fall back to local JSON when Supabase variables are absent. scripts/worker.mjsremains an HTTP poller and does not connect to the database; in the current architecture only Web pods require a database pool.- The existing SQL model uses PostgreSQL-compatible tables, JSONB/array/timestamp types, indexes,
FOR UPDATE SKIP LOCKED, and two PL/pgSQL functions. Its application coupling is Supabase client semantics, not Supabase-only SQL namespaces. - Existing tests intentionally clear Supabase variables and exercise local JSON; there is no real PostgreSQL or RDS integration test and no live RDS credential is available in this workspace.
- The existing
/api/healthroute reports process/configuration state but does not probe database connectivity.
Decisions
- Introduce explicit
ZHINIAN_DATA_BACKEND=local|postgres. Production defaults must fail closed when the backend is missing or invalid; PostgreSQL mode must fail whenDATABASE_URLis absent. - Preserve local JSON for development/tests, but never silently fall back from an explicitly selected PostgreSQL backend.
- Centralize
pg.Pool, TLS certificate loading, numeric environment validation, transactions, query execution, connection shutdown, and readiness in one server-only module. - Keep current store exports stable so routes, services, and components do not learn database transport details.
- Preserve database-side
claim_generation_jobsandbilling_post_wallet_entryfunctions for concurrency correctness. - Use an independent versioned migration runner with advisory locking; ACK runs it as a one-shot Job before Web rollout, not as a per-pod init container.
- Web receives database/session/provider/storage secrets; Worker receives only its internal token and internal Web URL.
Unverified assumptions and external boundaries
- The target RDS PostgreSQL major version, instance connection limit, internal endpoint, TLS enforcement setting, CA bundle, database/user names, and network ACL/security-group rules are unknown and must be supplied/verified during deployment.
- Without live credentials or an ACK context, code/build/schema checks can verify implementation shape but cannot prove real RDS connectivity, migration success, or cluster rollout.
Implementation and audit findings
- Direct PostgreSQL now uses a lazy process-level
pg.Pool, explicit backend selection, validated integer limits, verified CA TLS, parameterized queries, a single-client transaction helper, and schema/privilege-aware readiness. Connection-string SSL query parameters are rejected so they cannot silently override the mounted CA policy. - The three stores and both account scripts no longer use Supabase/PostgREST. Local JSON remains explicit for development/tests; production without a valid backend or PostgreSQL URL fails closed.
- The migration runner serializes with a session advisory lock, verifies immutable checksums, wraps each version in a transaction, provisions exact current-object grants for a constrained application role, revokes public function execution, and deliberately avoids blanket/default future-object grants.
- Read-only audits found and implementation fixed four concurrency/semantic defects: concurrent duplicate wallet idempotency, concurrent failed-login count loss, PostgreSQL billing-update 23505 mapping, and JSONB condition-array canonicalization.
- ACK templates now keep Web at one replica until storage is externalized, separate Web/Worker/DB secrets, isolate the public internal-worker prefix through a selectorless Service, mount the RDS CA, disable service-account token mounts, and split liveness from database/schema readiness.
- The first final Sol review returned FAIL and uncovered additional production risks. All reported code findings were addressed before re-review: wallet idempotency is organization-scoped and binds immutable account/job/kind/amount/currency fields while treating description/metadata as mutable audit detail; destructive usage deduplication became a fail-safe preflight; app grants are explicit with no future-object defaults; readiness covers all runtime tables; password changes use a row-lock transaction; server-only imports are enforced; and npm/pnpm resolve the same pinned Next/React toolchain.
- Main-thread final verification passed 31 test files / 119 tests, TypeScript, the Next production build, all 8 ACK manifest assertions, frozen lockfile validation, script syntax, diff hygiene, and documentation drift. The final independent Sol review returned
PASS; live RDS/ACK/Docker verification remains explicitly outside the available environment.
Image generation completion mismatch — 2026-08-17
- Initial report: the platform can show an image task as completed even though no generated image is available; the user suspects the post-refactor frontend/backend contracts no longer align.
- Initial code search shows the frontend create flow still requests
/api/generations/image,/api/generations/video, and/api/assets, while the Go backend also exposes a newer orchestration/public API surface. The exact completion path and payload contract remain under inspection. - The Go backend still mounts compatibility routes for
/api/generations/image,/api/generations/video, and/api/assets; these routes return the samejobs/assetscollection keys the frontend reads. The Go output-registration processor is intended to reject a succeeded job when provider output URLs cannot be resolved, so a falsesucceededstate may be caused by the running worker/deployment or by a read/write scope mismatch rather than only the route names. - Deployment inspection confirms a concrete boundary break: the root
Dockerfileserves static Nginx and deliberately returns 404 for/api,/uploads, and/generated-results;docker-compose.ymlstarts that Web image plus the legacy Node Worker, but does not start the Go API image. The Node Worker calls/api/internal/worker/tickon the static Web service, so the migrated Go job/asset pipeline is not actually wired into the shipped Compose topology. - The Go provider path includes a Mock fallback when credentials are absent; Mock reports
succeededand creates a generated placeholder asset, but that relative result path is also hidden by the static Web 404 boundary. This can surface as a completed task with no visible image in the current deployment. - The worktree contains substantial pre-existing edits in
runtime.go,application.go,catalog.go,ledger.go,settings/service.go,create-studio.tsx, and CSS; they are being preserved. The current runtime source has already removed the Mock provider fallback, whileruntime_test.gostill asserts the old Mock behavior; this is a partially applied refactor that must be compiled/tested in an environment with Go before claiming end-to-end success.
Current implementation correction — 2026-08-17
- The deployment bullets immediately above describe the pre-fix state found during inspection. The current worktree now has
zhinian-go-apibuilt frombackend/Dockerfile.alpine,zhinian-webserving the static export, anddeploy/nginx-compose.confforwarding/api,/uploads, and/generated-resultsto the Go API; the Go process owns the embedded WorkerLoop. - The current runtime no longer registers or selects
mock; PostgreSQL production startup validates the real Volcengine, EvoLink, Bailian, and Seedance credentials, and billing maps a removed/unavailable provider to an explicit service-unavailable response. ProviderProcessornow rejects a provider-reported success without an output URL before terminal finalization. The create page and result-asset task view also treat a succeeded job without a resolvable output asset as结果同步中, so neither frontend surface presents a false已完成state to the user.docs/API.mdand deployment/README guidance now describe the Go API + embedded Worker topology. The oldscripts/worker.mjsremains only as legacy source and is no longer part of the checked-in Compose or package-script path.- Verification is limited by the host environment:
git diff --checkpasses; Go, frontend dependency/typecheck/build, and Docker Compose execution still require a deployment/CI environment with those toolchains.
Production provider bootstrap requirement — 2026-08-18
- PostgreSQL production startup currently calls
validateProductionProviderConfigurationfromapplication.Newbefore the HTTP server is created, so missing any of the four provider credential groups terminates the process before it listens. - The runtime settings endpoint persists provider secrets to
.env.local/the configured settings file and reportsRestartRequiredfor non-billing updates, but the provider registry andProviderJobBuilderare created once during application composition; saved credentials therefore require a process restart. - The safe bootstrap boundary is an explicit environment flag,
ZHINIAN_ALLOW_UNCONFIGURED_PROVIDERS, that bypasses only the startup guard. The real provider adapters remain the only production adapters, and a selected provider with missing credentials must fail before quote/creation with a clear service-unavailable response. - Compose injects
.env.localthroughenv_file, and ACK injects runtime values through the Go API ConfigMap/Secret, so the bootstrap flag can be supplied by deployment configuration without exposing it as a mutable settings-panel field.