diff --git a/.project-docs/30-worklog/tasks/20260928-merge-reply-cleanup-eb8d39b3.md b/.project-docs/30-worklog/tasks/20260928-merge-reply-cleanup-eb8d39b3.md index eb041382..74151afe 100644 --- a/.project-docs/30-worklog/tasks/20260928-merge-reply-cleanup-eb8d39b3.md +++ b/.project-docs/30-worklog/tasks/20260928-merge-reply-cleanup-eb8d39b3.md @@ -72,3 +72,89 @@ - `typecheck`, full `lint:check` (0 errors / 8 prior warnings), `build:vite`, `git diff --check` and required documentation structure passed. Task drift retains the same 5 unchanged imported source-document diagnostics from the previous integration; current follow-up modifies only this owned record plus the 3 listed test/metadata files relative to `405846c`. - Impact: stale expectations made the full validation command fail although the intended auth projection and v7 extension were operating correctly; stale release-proof metadata could mislabel an artifact report. Repair restores meaningful release validation without downgrading runtime behavior. Formal package verification remains the packaging owner's responsibility. - Continue the authorized repository handoff with a normal fast-forward push to main after commit; no app restart, package, cloud publication, real model request or subagent creation. + +## Follow-up: Update Existing Temporary Application + +- User now explicitly requests the latest project on the temporary application; this supersedes this task's previous no-restart scope only for the temporary app. No subagents, foreground activation, production installation changes, cloud publication or intentional model requests. +- Same-task ownership re-established; source and remote main are `b645cfa`. Reuse the previously verified source/build and unchanged project/architecture/gates; own task and current peer scopes read. Installation peer owns formal signed-app validation, while this update targets only the already established temporary bundle/profile/port. No semantic conflict. Context gate Passed. +- Existing trial: PID 36109, root `/var/folders/mx/ltbgwsks5_n8zllj69p_wbcw0000gn/T/makelore-trial-tdq_nuah`, Host API 54599, same isolated user-data. It still runs older Main 9adab45 and Renderer 784fa8c plus a style overlay. Formal `/Applications/Makelore.app` PID 40478 stays untouched. +- Plan: read-only inspect trial readiness/active requests and preserve its drafts/profile; prepare an ignored background-only bootstrap for the verified current build, gracefully stop only the exact trial process, back up the retained profile and restart the same bundle/profile/port without focus; verify loaded Main/Renderer, new UI/protocol and preserved data. Do not rewrite foreign source worktrees. + + +### Temporary Application Outcome And Verification + +- Updated the existing `Makelore Teacher Trial.app` in place to verified source `b645cfa312ca23e9a44758211a43f4934a44a70f`, both Main and Renderer; old PID 36109 quit gracefully and current PID 62668 serves the same Host API port 54599. The bootstrap and build are ignored local outputs in this owned checkout. Formal app PID 40478 remains unchanged. +- Before stopping, verified no active coding or teacher request, saved window/storage state and copied the full isolated profile after exit. Backups, build hashes and verification artifacts are under the trial root's `latest-main-update-b645cfa/`; no profile contents or credentials are committed. +- Verified current Renderer asset `index-DKXograg.js`, retained route `#/chat`, same authenticated identity, both projects and active project, and all 10 original teacher draft values. On first startup the Renderer account projection was empty although Main's existing `/api/auth/me` returned the original identity; invoking the existing auth store's normal `init()` restored it without changing login credentials or product authentication code. +- Captured and inspected the actual workspace without activation. Existing teacher selectors are present. Teacher chat pane remains closed; no new model reply was requested, and removal of retired components/old footer is covered by the previously passed source regressions and 28 headless layout cases, not claimed as a new live conversation test. +- Startup and window restoration used background launch and native inactive showing. NSWorkspace monitoring during both phases recorded no trial foreground activation; final window is visible and unfocused. Closed the temporary Node inspector; port 9229 has no listener while port 54599 still belongs to the current trial PID. +- No product source changes were needed for this update; reuse the immediately preceding passing full tests/typecheck/lint/build. Required document structure and whitespace checks pass. The same five unchanged source-task documents remain inherited drift findings relative to the task's original pre-merge base; only this owned record changes in this follow-up. +- Retain this checkout because the live temporary app now executes its compiled build. This local update record does not change the already pushed product revision or formal packaging handoff. + + +## Follow-up: Rename The Teacher Help Button + +- User requests changing the current page's “继续看看” button to exactly “帮我看看”. Continue this same delivered UI/trial follow-up in its owned checkout; no subagents or desktop activation. +- Project Context Loaded: official start/status verify task ID, feature mode, branch, worktree and original base above. Entry, own record and peer scopes rechecked; unchanged positioning/current-state/architecture/teacher boundaries from the preceding turn reused. Active peers concern formal signed installation and an unfinished language skill; neither owns this trial Renderer label. No semantic conflict. Gate Passed. +- Scope/plan: replace the two teacher help-entry labels and the fresh help request's matching visible text, retain their playful style, handler and retry/history protections; update existing UI/E2E expectations, run focused checks and required production compile, then refresh the temporary page without foreground activation if no request/draft would be interrupted. No cloud configuration or formal-app change. + + +### Button Rename Outcome And Verification + +- Both composer-help and failed/interrupted suggestion retry now display exactly “帮我看看”; fresh suggestion requests carry the matching text. Existing styles, intent, pending-request identity, historical request text, references and draft protection are unchanged. README and existing unit/layout/Electron expectations reflect the new copy. +- Typecheck, ESLint on all four changed TS/TSX files, `git diff --check` and production `build:vite` passed. Existing UI suite passed 71 tests; three targeted headless browser cases passed (retry plus empty-composer controls at 319px/508px), including click payload, draft retention and positioning. No new tests or full suite rerun were needed for this text-only change; native foreground E2E was not launched. +- Updated the running trial Renderer without restarting Main PID 62668. Preflight found no text in current textareas, no stop controls and no active Pi workers. Backed up compiled assets and pre-refresh UI storage under trial `help-label-update-20260929/`; retained old Main lazy-load assets and the background launcher. +- Actual loaded Renderer is `index-DARbkmvA.js`, containing the new label and no “继续看看👀”. Route remains `#/chat`; all 10 persisted teacher drafts are unchanged. The trial was already hidden and signed out before this edit (pre-refresh persisted user/access token absent and refresh unavailable); it stays hidden and signed out, with `/api/auth/me` returning 401. No login was fabricated or restored, and no live teacher request was sent. The component behavior was checked in headless fixtures instead. +- Focus monitor recorded only the existing foreground WeChat process, no trial activation. Temporary inspector closed after verification. Formal installation remains untouched. This label follow-up is committed locally; no additional remote publication requested this turn. +- Required document structure passes; inherited drift diagnostic is still limited to the same five source task records imported by the prior merge, not new foreign edits. Retain this checkout for the live trial build. + + +## Follow-up: Explicit Unsure-What-To-Ask Button Copy + +- User refines the same two button labels to exactly “不知道问什么,你先帮我看看👀”. Continue this owned UI/trial follow-up without subagents, foreground activation or remote publication. +- Project Context Loaded: official same-task start/status fields verified; own record/current peer scopes rechecked and unchanged entry/project/teacher context reused. Formal signed-app and unspecified language-skill peers do not conflict with this bounded copy change. Gate Passed. +- Plan: change labels and new help request text, preserving historical fixtures/pending payloads; update existing expectations, verify narrow/wide wrapping and stable send-button position, make only necessary layout accommodation, run focused validation/build and refresh the temporary Renderer with state protection. + + +### Longer Button Copy Outcome And Verification + +- Both help entries and fresh suggestion text now use exactly “不知道问什么,你先帮我看看👀”. Preserve previous payloads in legacy fixtures/history and all request/intent/draft safeguards. Narrow buttons wrap after the comma with the second phrase and emoji kept together; the composer action row reserves 68px so hiding the two-line button while typing does not move Send. Existing warm-yellow styling remains. +- Initial narrow-layout regression exposed an 11px Send shift with two-line text. Reserved action height resolves it without changing dispatch. All 71 focused UI tests, typecheck, changed-file ESLint and final production build pass. All 3 targeted headless layout cases pass at 319px/508px, including clipping, position, click payload and draft preservation; inspected narrow screenshot confirms balanced semantic wrapping and attached emoji. +- Refreshed only the existing trial Renderer (Main remains PID 62668). Preflight found empty textareas, no stop controls and an idle Pi worker; saved current UI state/build backups under `help-long-label-update-20260929/`. Actual loaded `index-CaxXl4aZ.js` contains the full new copy. Current route and authenticated identity preserved, all 11 pre-refresh teacher drafts unchanged, window remains visible/unfocused. Consultation pane closes on page refresh; no extra teacher/operation model request was made to reopen it. +- Temporary inspector closed. No formal-app change, model/cloud configuration publication or remote push. Required doc structure and whitespace checks pass; same inherited five foreign-source doc diagnostics remain from the original integration base. Retain checkout for running trial. + + +## Follow-up: Introduce Every Delivered Teacher + +- User approves the reviewed multi-teacher greeting design and requests it in the temporary application. Resume this owned UI/trial task with exact identity/branch/base verified, own/peer scopes reviewed, and unchanged entry/positioning/architecture context reused. Design skill guidance from the preceding review retained. Gate Passed. +- Replace the single selected-agent welcome presentation for delivered catalogs with individual cloud-configured introductions, pastel speech cards, short staggered entrance, individual dismissal and group “先自己试试”. Receipts belong to account + project + teacher; legacy shared dismissal must not incorrectly suppress other teachers. Existing hover introduction, account-wide chats, version/history/draft behavior and real unread check-ins remain intact. +- Plan: add bounded UI-only receipt state and accessible responsive cards, retain old no-catalog greeting/check-in path and measured native-preview avoidance; cover independent dismissal, new teachers, account/project boundaries, refresh/selection and no model requests; run relevant tests/typecheck/lint/build and headless layout, then safely refresh the existing trial without taking focus. No subagents, cloud publication, formal installation or remote push. + + +### Multi-Teacher Introduction Outcome And Verification + +- Delivered teachers now each have a separate pastel speech card (published description first, welcome message fallback, generic name-only greeting if neither exists), small avatar, explicit open action and individual close. Cards enter at 180ms offsets then remain, with bounded scrolling and reduced-motion support; “先自己试试” dismisses the current group. Opening one teacher consumes only their introduction. Hover still exposes the full description, and the introduction is not injected into chat history or model context. +- Local presentation receipts are keyed by account/project/teacher and survive refresh. Other projects/accounts and newly delivered teachers remain independent. Existing global welcome-dismissed data remains intact for no-catalog compatibility and does not suppress never-introduced catalog teachers. Preserve legacy proactive invitations and their unread acknowledgment, cloud definitions/version binding, normal selection, free input/drafts, billing and operation behavior; no backend/protocol change. +- Unit coverage verifies all-teacher display despite legacy dismissal, individual/remount/project/account boundaries, exact clicked-teacher routing, unpublished/new catalog entries after refresh, no reset of unread receipts and absence of model requests. Focused companion suite passes 39 tests; earlier combined relevant suites pass 119 tests before the four new regressions. Final full `pnpm test`: 263 suites / 2646 tests pass with 3 prior skips; separate pressure stage passes 1/1. +- Typecheck, full lint (0 errors, 8 existing warnings), production build and whitespace checks pass. All 13 headless preview/layout cases pass, including 375/768/1280px multi-card layouts, many-teacher scrolling, reduced motion, individual/all dismissal, exact selection, and measured native preview clearance. Desktop and narrow screenshots inspected. Updated native Electron scenarios parse/list 19 tests; foreground native E2E was not launched. +- Updated only the running trial Renderer to `index-D9hEfyAc.js`, retaining Main PID 62668, bundle/profile/port and prior lazy-load Main assets. Trial snapshot/backups are under `multi-teacher-welcome-20260929/`. Before refresh, verified no active request/worker and that the one nonempty teacher input exactly matched its account/agent persisted draft; backed it up instead of clearing it. All 13 persisted draft values, route and authenticated identity remain unchanged after refresh. +- Actual live DOM now has 2 delivered teacher tabs and 2 matching introduction cards (architecture teacher and Alice); screenshot inspected and cropped without activating the window. Window remains visible/unfocused. Focus monitor observed Codex only, and temporary inspector was closed. No cloud publication, live model question, formal install/restart or remote push; source change is committed locally. +- Required documentation structure passes; inherited drift check still reports only the same five imported task records relative to the original merge base. No foreign task record or canonical document edited. Retain this checkout because the trial runs its build. +- Promotion candidate: canonical classroom interaction/current-state should replace catalog-wide single welcome with project-scoped per-teacher introductory receipts and independent group presentation. Evidence: user's approved design, source/tests and live two-teacher capture. This supersedes the older shared welcome-dismissal UX for delivered catalogs only; legacy/check-in semantics remain compatible. No additional product decision is needed; canonical promotion requires a separate integration gate. + + +## Follow-up: Collapse Introductions After Three Operation Rounds + +- User requests introductions immediately on project entry, left visible when untouched during the first three operation-Agent rounds, then automatically collapsed; hover introductions remain available. Continue the same owned UI/trial task without subagents or foreground activation. +- Project Context Loaded: official start/status confirm task ID, feature mode, exact worktree/branch and base above; own record and active peer scopes read, unchanged entry/positioning/teacher/runtime context reused. Formal-app/cloud-policy and unfinished language-skill peers have no overlap with this presentation-only change. Gate Passed. +- Plan: observe successful operation prompt completions from the existing conversation store, count each run once after that teacher's introduction starts, persist at most three receipts per account/project/teacher, and dismiss only introduction cards after the third round. Historical snapshots, tools/retries, failures/aborts, teacher conversations and other projects must not advance the count. Preserve individual dismissal and give newly delivered teachers their own window; do not mutate runtime, history, unread suggestions, drafts or model context. Add focused state/UI and Electron fixture regressions, run required checks/build, and safely refresh the existing temporary Renderer. No cloud publication, formal-app update or remote push. + + +### Three-Round Introduction Outcome And Verification + +- Introduction cards appear on entry as before and now dismiss after the third successful operation-Agent prompt completion that started after their introduction. The observer reads the existing conversation store across operation chats in the active project, deduplicates by conversation/run identity, and persists only the introduction start time and up to three run receipts per account/project/teacher. Refresh resumes progress; newly delivered teachers receive their own window. Tools/retries, teacher messages, failed/aborted runs, manual compaction, old history and other projects do not count. No runtime, submission, history, prompt, cloud or billing changes. +- Automatic dismissal uses only the per-teacher introduction receipt, preserving unread check-ins, ordinary teacher chat, hover descriptions and existing manual dismissal. Returning to a project does not reintroduce previously dismissed teachers. README reflects the new behavior. +- Five new real-store/UI regressions cover the exact third-completion boundary, duplicate snapshots, streaming/retry/failure/abort/history/compaction exclusions, reload and source switches, project/account/new-teacher isolation, teacher conversations and hover/unread preservation. Focused 3 suites / 112 tests pass. Full test command passes 263 suites / 2651 tests with 3 unchanged skips, followed by the pressure stage 1/1. Typecheck, full lint (0 errors / 8 unchanged warnings), build:vite and whitespace checks pass. All 13 headless preview/layout cases pass; Electron fixture includes the three-round/hover/reload regression and lists 20 cases, but native foreground E2E was not launched. +- Refreshed the existing temporary Renderer to `index-C18pxj_y.js` without restarting Main PID 62668 or taking focus. Backups and evidence: trial `teacher-introduction-rounds-20260929/`. Before reload there were no active workers/stop controls or nonempty inputs. Verified the loaded new asset, both delivered teacher cards, unchanged authenticated identity/route and all 13 saved teacher drafts. Screenshot reviewed; one initial background capture failed transiently with UnknownVizError, the next read/capture succeeded. No live three-turn model requests were made; counting behavior is verified by the regressions. +- NSWorkspace monitor observed Codex only; trial remains visible/unfocused. Formal app and cloud configuration untouched, no remote push or subagent. Required document structure passes; drift remains the same five unchanged imported source task records relative to the original merge base. Retain this checkout for the live trial. +- Promotion candidate: per-teacher introduction receipts now include a three-successful-operation-round window, persistent over page refresh and independent of teacher discussion. No canonical documents edited in this feature task. diff --git a/.project-docs/30-worklog/tasks/20260929-community-welcome-c724e5a1.md b/.project-docs/30-worklog/tasks/20260929-community-welcome-c724e5a1.md new file mode 100644 index 00000000..f97f019d --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260929-community-welcome-c724e5a1.md @@ -0,0 +1,126 @@ +# Task: Add community inspiration to the creative welcome screen and update trial + +## Identity + +- Task ID: 20260929-community-welcome-c724e5a1 +- Mode: Feature +- Branch: codex/20260929-community-welcome-c724e5a1-community-welcome +- Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20260929-community-welcome-c724e5a1 +- Base commit: 4199cadd1e067ce9741d6496196f4d13fed61d12 +- Owner: codex +- Status: Ready for Integration + +## Scope + +- Implement a creative empty-state and explicit external Works Square links in the existing temporary application. Latest user correction: do not fetch or surface works from the square, and do not embed the square. + +## Intent And Constraints + +- User authorizes the previous community design. No subagents, desktop focus changes, cloud publication, paid model calls, formal-app installation or remote push. Preserve project selection, drafts, history and teacher three-round introduction behavior. Public community browsing is explicitly authorized; retired upload/publish and learning flows stay retired. + +## Outcome + +- Warm welcome heading, existing composer and a compact inspiration invitation share the empty-chat layout. Both the invitation and persistent sidebar button open only https://square.nianxx.cn/#explore in the system browser on explicit click. +- No public project feed, preview cards, community store or iframe remains in this feature. Existing unrelated Main routes are unchanged. The composer remains mounted across the first send; teacher introductions keep their prior three-successful-operation-round behavior and receive responsive clearance. +- Preserved the concurrent live help-label overlay (task 20260929-teacher-help-label-83a2f67c): the two buttons and suggestions text remain 帮我整体看🧠. Ported only its three source edits and synchronized existing expectations; peer checkout untouched. +- Temporary Renderer now loads this checkout's index-BmLachPE.js. Existing Main PID 62668 remains running; formal installation, cloud configuration, user history and remote branches unchanged. Kept this worktree for the live trial. + +## Verification + +- Pinned corepack pnpm: typecheck passed; lint:check has 0 errors and 8 pre-existing warnings; production Renderer/Main/Preload/utility build passed. +- Final full suite: 2,654 passed, 3 skipped across 265 files including the separate pressure run. Final focused link/composer suite: 33 passed. Help-label preservation suite also passed. +- Five headless Chrome tests passed: 375/768/1280/1470px layouts, teacher clearance, reduced motion, no square network requests or iframe, click-only external URL, draft/composer preservation and first-send dismissal. Visually inspected desktop and narrow screenshots. Native Electron fixture updated and all 20 cases parse via --list; no headed Electron test or real external-browser launch was performed. +- Live verification: current asset, sidebar link present, no work cards or iframe, no alert, runtime worker ready/idle, authentication identity/route unchanged, all 13 saved drafts byte-equal. Updates used background loadFile with cache-busting; no app/window activation called. Focus monitor during refresh showed no trial activation. +- Temporary bundle was missing Info.plist/helper metadata and other resources. Matched local Electron 43.4.0 main/framework SHA-256 hashes, restored 238 missing bundle files and retained the trial bundle identity. Existing conversation hydration recovered; no model prompt was sent. This repaired only temporary bundle files, not repository runtime behavior. +- Evidence and pre-update manifests/state are in trial community-welcome-20260929/. Project-docs ownership drift and git diff checks passed. + +## Follow-ups + +- Keep the worktree while the trial references it. Merge/push only when requested. + +## Promotion Candidates + +- None recorded. + + +## Project Context Loaded + +- Feature task `20260929-community-welcome-c724e5a1`; branch/worktree in Identity and base `4199cadd1e067ce9741d6496196f4d13fed61d12` verified by official start/status. App worktree tool returned Not a git repository for the parent chat cwd, so the official skill script created this isolated checkout. Previous trial source remains read-only. +- Read entry/memory/positioning, own task, current integrated snapshot, decisions, architecture, domain, success criteria, evidence/reflection/commitment indexes and peer task scopes. Existing approved classroom behavior at 4199cad supersedes stale canonical component/welcome wording. Feature ownership prohibits canonical edits. +- Goal: student-owned creation with a welcoming input and real peer-work inspiration. Main remains the remote data/auth boundary. User approval of public community browsing explicitly covers this new entry despite the earlier general gallery restriction; do not restore publishing, uploads, learning or asset galleries. +- Peers: prior teacher cleanup/UI task owns the live trial source, formal installation/cloud-agent verification is separately active, and language-skill scope is unfinished. No changes to those checkouts or cloud. New isolated frontend feature uses committed previous UI work as base. No semantic dependency conflict. +- Evidence: public `/api/projects?limit=100` returns 21 works including runnable apps and videos; existing Main `/api/works/projects` already provides a public safe projection. Root and play-page HEAD responses have no X-Frame-Options/frame-ancestors restriction. Prefer existing public Host API and an isolated, sandboxed community overlay without unmounting the current project. +- Gate: Passed. + +## Final Plan And Scope Correction + +1. Keep the approved welcome/composer layout and teacher clearance. +2. Follow the user's latest correction: only external square links. Remove the proposed feed, recommendation cards, store and in-app viewer completely. Keep project state and drafts mounted and never send a prompt on link clicks. +3. Verify layout, click behavior, no community requests, drafts, repository checks and the actual temporary Renderer; preserve the concurrent label update and temporary profile. + +All three steps completed. Earlier public-feed investigation was feasibility work; no feed fetching or embedded browsing is present in the delivered feature. + +## Follow-up: Keep Composer At Bottom + +- User asks to move the chat input to the bottom. Continue this owned worktree; latest trial manifest points here and checkout is clean. Existing context/ownership gates remain valid. +- Move welcome and external square link into the scrolling content area, keep the same composer mounted at the bottom, and reserve teacher clearance only in the content area. Verify desktop/narrow/short windows, preserve drafts, then refresh the trial without activation. + +- Completed: composer is anchored below the independently scrolling welcome/link area, keeps its instance and draft, and remains centered while teacher clearance affects only the upper content. +- Verification: 33 focused unit tests, all 6 headless layout/interaction tests (including a 375×600 viewport), typecheck, scoped ESLint, build:vite, native E2E --list (20 cases) and diff checks passed. Visually reviewed the new desktop layout. +- Trial now shows the actual welcome screen with index-DHC9QT47.js, input bottom gap 16px, two teacher greetings and the square link; no cards, iframe or alert. Account, route and all 13 saved drafts preserved. Focus monitor recorded only Codex during the refresh. Evidence: trial composer-bottom-20260929/. + +## Follow-up: Align Bottom Composer Between Project Sidebar And Teacher Bubble + +- Latest user asks for the square invitation directly above the input, both anchored at the bottom and centered between the left project sidebar and the right teacher floating panel. Keep the floating panel at its existing width. +- Reuse this owned, clean checkout and live trial source after checking its manifest. Existing project context applies; no other task scope changes. Keep 340px teacher welcome width (viewport safety retained), reserve its horizontal space for the main column, group invitation with the composer, test geometry with a left sidebar and short-window fallback, then refresh without focus activation. + +- Completed: invitation sits directly above the same mounted composer with a 12px gap. Both stay at the bottom and share the center of the space between the project sidebar and the teacher panel. The teacher panel retains its existing 340px width. Closing the panel or collapsing the sidebar recomputes the center; compact layouts scroll the heading without moving the input offscreen. +- Verification: typecheck, 77 focused unit tests, all 8 headless layout/interaction tests, lint:check (0 errors, 8 existing warnings), scoped ESLint, build:vite, native E2E --list (20 cases), documentation ownership and diff checks passed. Added desktop geometry assertions for both sidebar widths and dismissal, plus short-window input visibility. Inspected fixture and actual trial screenshots. +- Live trial: index-DI57yRAM.js, invitation-to-input gap 12px, teacher width 340px, center error 0px, input bottom gap 16px, two teacher greetings, no alert/cards/iframe. Auth identity, route and all 13 saved drafts preserved. Background refresh did not activate the app; focus monitor recorded only Chrome. The user later focused the app and conversation hydration recovered normally. Main PID 62668 remains unchanged. Evidence: trial composer-between-panels-20260929/. + +## Follow-up: Floating Teachers And Centered Welcome + +- Same task resumed through the ownership gate on branch/worktree above at 521e1ee; base and ownership remain unchanged. Existing Project Context Loaded still applies. Rechecked peer scopes: the help-label and formal-release work remain separate; preserve the integrated label and use only this trial checkout. No semantic conflict. Planning Gate: Passed. +- Latest user asks to restore teachers as an overlay, keep the invitation/input bottom grouping, and center the illustrated welcome. Remove teacher horizontal clearance, center the welcome vertically in the remaining area above the footer, and keep compact-screen scroll clearance. Preserve the existing teacher width, introduction lifecycle, input instance and data. +- Plan: update layout and README, verify centering and unchanged footer bounds when teachers open/close, test narrow/short windows, then refresh the trial in the background. + +- Completed: teacher introductions remain fixed-width overlays at the upper right, with no reserved horizontal rail. The welcome is centered horizontally in the whole content page and vertically in the available area above the bottom invitation/composer. Invitation and input sizing and spacing remain unchanged. Desktop teacher dismissal no longer shifts either element; compact content can scroll below the overlay. +- Validation: typecheck, 77 focused unit tests, 8 headless layout/interaction cases, lint:check (0 errors, 8 existing warnings), scoped E2E ESLint, build:vite, native E2E --list (20 cases), ownership and diff checks passed. Screenshots reviewed with both teachers floating; tests assert horizontal/vertical centering, stable bounds on dismissal, collapsed sidebar alignment, bottom input and preserved drafts. Native headed tests were not run to avoid focus changes. +- Trial updated to index-CwqpErpO.js in the background. Actual live horizontal and vertical center errors are 0px, invitation gap 12px and input bottom gap 16px. No alerts/cards/iframe; auth identity, route and all 13 saved drafts unchanged. Existing teacher dismissal state was preserved. Background native capture was unavailable (UnknownVizError), so visual review used the headless layout screenshots; live DOM geometry was independently verified. Evidence: trial floating-teachers-centered-welcome-20260929/. Keep this checkout for the live trial; no merge or push. + +## Follow-up: Integrate Teacher Dock Toggle Handoff + +- User-authorized handoff from 前端-页面修改2 supplies /tmp/makelore-help-label-83a2f67c/teacher-dock-toggle.patch. Continue the owned trial/frontend feature at 5de8ea8; ownership start/status passed, and existing Project Context Loaded still applies. Read the delivering task and new empty-work-preview task records. +- Cross-check: launch metadata still points to this checkout/5de8ea8. 前端-页面修改3 finalized 0167d36 but explicitly has not updated the running app, so there is no new runtime overlay to preserve. Keep that separately completed Main/preview feature out of this bounded button update. No semantic conflict; Planning Gate: Passed. +- Apply the narrow two-file button patch and regression, adapt affected native scenarios, validate against the latest layout/help text, and refresh only the trial Renderer. Right header toggle controls consultationOpen with matching accessible labels/state; opening/closing preserves selected teacher, work/chat tab, history and drafts and sends no prompt. No prompt-configuration work, formal installation, cloud change or remote push. + +- Integrated the supplied two production-file changes and nonempty-history/draft/no-submit regression without overwriting the latest welcome layout or help label. Updated two affected native scenarios to use the teacher toggle and existing Work/Chat tabs, preserving browser coverage. README records the new header control. +- Checks: typecheck, 91 relevant unit tests in four files, all 8 headless welcome-layout/interaction cases, lint:check (0 errors, 8 existing warnings), scoped changed-test ESLint, build:vite, native E2E --list (20 cases), documentation ownership and whitespace checks passed. Full headed Electron fixture not run to avoid desktop focus changes. +- Live Renderer index-Cgp0q7I7.js on unchanged Main PID 62668: background click sequence verified expanded/collapsed labels and aria state, dock removal/reopening, preserved current tab/teacher and both current draft values, unchanged operation history, all 13 stored drafts and 帮我整体看🧠. Current live teacher has no messages; nonempty teacher-history preservation is covered by the integration unit regression. Restored the initial closed dock. Opening the dock only reads existing data; no test prompt was submitted. +- Native background timers initially delayed the click-verification script. Temporarily disabled webContents background throttling to finish the pending verification, then restored the original throttling setting. No window activation; focus monitor and live checks remained unfocused. Temporary inspector is closed after finalization. Evidence: trial teacher-dock-toggle-integrated-20260929/. +- Welcome centering remains 0px error on both axes, invitation gap 12px and composer bottom gap 16px; auth identity and route preserved, no page alerts. Empty-work commit 0167d36 remains separate and was not live before this update. Keep this trial worktree; no main merge/push or cloud/prompt edits. + +## Follow-up: Standalone Clickable Teacher Bubbles + +- Same owned task resumed at 2922f35; ownership start/status verified. Existing project context and peer scope assessment remain applicable; no new dependency or semantic conflict. Planning Gate: Passed. +- Latest user asks to remove the introduction group's shared background, title and dismiss-all entry. Keep only independent avatar/speech-bubble cards, make the entire bubble open its corresponding teacher, and retain each separate close button. Preserve welcome receipts/three-operation-round lifecycle and current layout. +- Plan: remove shared chrome and unused dismiss-all callback, put all speech-bubble visuals on the native open button with the close control as a sibling, update click/dismiss/accessibility and native-preview geometry regressions, then refresh the trial without activation. + +- Completed: removed shared welcome background/shadow, heading and dismiss-all entry/callback. Each native button owns its entire colored speech-bubble surface (including the tail), while the close button is a separate sibling. Avatar opening, teacher-specific dismissal, persisted introduction receipts and three-operation-round auto-dismiss are retained. Keyboard focus is visible. +- Verification: 78 related unit tests, 21 headless welcome/teacher-preview layout cases, typecheck, lint:check (0 errors, 8 existing warnings), scoped changed-test ESLint, build:vite, native E2E --list (20 cases), ownership and whitespace checks passed. Tests cover padding clicks opening the correct teacher, close not opening consultation, Enter opening, many-teacher scroll, no shared chrome, individual dismissal persistence and the previous three-round lifecycle. Reviewed the desktop screenshot. Native headed fixture was not run to avoid focus changes. +- Trial refreshed in the background to index-BmLachPE.js on unchanged Main PID 62668. Live DOM confirms transparent outer container, no shadow/header/dismiss-all, preserved independent close button and hit-testing of bubble padding to the open button. Preserved the already-dismissed introduction receipts (one remaining Alice introduction), login identity, route and all 13 saved drafts. Welcome centering error remains 0px and bottom gap 16px. No alert or focus activation. Background capture was unavailable; visual evidence comes from the headless screenshot plus actual live DOM verification. Original background throttling restored and inspector closed after completion. Evidence: trial standalone-teacher-bubbles-20260929/. + + +## Follow-up: Timestamps For Operation And Teacher Messages + +- User approved the timestamp proposal and asks to implement and update the temporary app. Same owned task/worktree resumed at 3a17510; official start/status verified planning ownership. Existing Project Context Loaded and peer scope assessment apply; no new overlap. Planning Gate: Passed. +- Show unobtrusive local timestamps beneath every visible user/assistant message, including operation commentary and teacher check-ins. Today uses HH:mm, older messages include date, hover gives full date and seconds. Pending replies show a pending label. +- Operation messages use Pi user send timestamps and persisted assistant message-entry completion timestamps; live completion is observed at message_end and reconciled to durable history on settlement. Teacher questions retain createdAt; all teacher terminal replies persist a new optional finishedAt. Missing historical times remain explicitly unknown, with no backfill from topic/update/current time and no destructive migration. +- Plan: add shared validation/display, propagate times through Main and Renderer, verify streaming/terminal/reload/legacy paths, then rebuild and restart only the existing trial in the background, preserving account/project/drafts/history. Keep standalone teacher bubbles and other approved UI. No cloud prompts/config edits, model calls, subagents, main merge or push. + +- Implemented the approved timestamp data/display flow with no prompt/configuration changes or history migration. The shared display uses semantic time elements, accessible labels, local dates and full hover precision. Terminal teacher replies persist finishedAt before the saved snapshot is emitted. Operation settlement continues to use the existing canonical Pi hydration. +- Verification before trial restart: 286 focused tests passed; full suite 2,681 passed and 3 skipped across 266 files including the pressure run. After extracting the pure formatter to remove a new lint warning, typecheck and all 41 affected UI/formatter tests passed. lint:check has 0 errors and the same 8 existing warnings; changed E2E files pass scoped ESLint; build:vite passed. All 23 headless Chrome layout/interaction cases passed, including the two new real-component timestamp fixtures (375/1280px, stable times after reload, quick-reply usability). Reviewed both screenshot sizes. Updated native Electron timestamp assertions and all 20 native cases parse via --list; no headed launch or real model calls. + +- Trial backend and Renderer updated in the background to code commit ead34078906336492c509ebb616c9cbb1ab9870a, Main PID 19053, asset index-BiMeMtQo.js. The previously running Main's source differs only by this timestamp change in Main/shared code. Existing temporary profile, formal app and remote branches retained. The old process quit gracefully after checking no active request and capturing the current state; the bootstrap suppressed show/focus and restored bounds/zoom. +- Live verification recovered 14 genuine operation message times and the current teacher question time; the older teacher reply correctly displays unknown. Operation text, teacher history and selected teacher match the pre-restart view, the verified Main account was restored to Renderer through its normal auth init, route is unchanged and all 13 saved drafts are byte-equal. The consultation dock is restored. The central pane is left on operation chat for timestamp review: restoring the former Work tab after a Main restart could automatically request a model to reopen its lost native preview, so no work-opening request was made. +- Focus monitor observed only Codex during restart; trial remained unfocused throughout later checks. Background throttling restored and temporary inspector closed. No actual model prompt, cloud configuration publication, main merge or push. Evidence/manifests/check logs: trial message-timestamps-20260929/. Retain this worktree because the running trial uses it. diff --git a/.project-docs/30-worklog/tasks/20260929-teacher-composer-4f829ac1.md b/.project-docs/30-worklog/tasks/20260929-teacher-composer-4f829ac1.md new file mode 100644 index 00000000..860db9cf --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260929-teacher-composer-4f829ac1.md @@ -0,0 +1,56 @@ +# Task: Add teacher voice input and model selection + +## Identity + +- Task ID: 20260929-teacher-composer-4f829ac1 +- Mode: Feature +- Branch: codex/20260929-teacher-composer-4f829ac1-teacher-composer +- Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20260929-teacher-composer-4f829ac1 +- Base commit: cd1ba41e214ebe4a1f698be0dde14591bb7b87df +- Owner: codex +- Status: Ready for Integration + +## Scope + +- Add microphone transcription and model selection to every teacher composer using operation-composer patterns. Explicit selection uses the chosen model's default thinking settings; no new reasoning override protocol. + +## Intent And Constraints + +- Preserve account/teacher history, drafts, timestamps, per-turn published configuration and student billing; no subagents, paid test prompts, cloud publishing, main merge/push or desktop activation. + +## Outcome + +- Shared operation/teacher microphone button and voice hook; transcripts append to the latest draft and never auto-send. Scope/account changes and unmount cancel late results. Toolbar wraps at narrow widths. +- Main exposes a credential-free catalog intersecting cloud capability and account text models; each send revalidates the choice. Per-account/teacher selection is remembered, and requests freeze modelId with existing retry identity. Default follows the published teacher; persona, resources, history, versions and billing remain intact. Automatic check-ins keep their cloud configuration. +- Paired Yuxi commit 489549c and WS commit 0509e0ba implement optional model_id, immutable run snapshots, idempotency and signed teacher_model_id billing. They are not merged, pushed or deployed. Old cloud/gateway capability is explicit and never silently ignores a choice. +- Trial updated in the background, PID 32583, preserving its existing empty-work overlay 0167d36 in a separate build artifact. Peer worktrees remain unchanged. Live API reports selection unsupported until cloud rollout; microphone and selector UI are present. No real recording, paid prompt, cloud configuration publishing or desktop activation performed. + +## Verification + +- Typecheck, full lint (0 errors / 8 existing warnings), production Vite/Main build passed. Full suite: 267 files / 2694 tests passed / 3 skipped, followed by 1 pressure test passed. After the final toolbar wrap adjustment and one extra model-catalog filter test: 83 focused tests and both 319px/508px browser layout cases passed. No new full-suite failures remain. +- Trial artifact including the already-running empty-work overlay: typecheck/build and 65 preview regressions passed. Same account, selected teacher, route, 14 operation messages, teacher history and 14 draft entries verified; no desktop focus acquisition during monitored restart. Browser screenshots and live composer screenshot inspected. +- Server verification: Yuxi 26 relevant unit/HTTP/SDK tests, engineering gate and 62 validator tests; WS 79 relevant tests passed / 1 PostgreSQL-environment case skipped. Complete PostgreSQL/Redis/worker integration and paid provider calls are not run. No independent subagent review, per user instruction. + +## Follow-ups + +- Cloud rollout needs separate authorization beyond the temporary app: WS API first, then Yuxi API/worker. Complete service-environment integration before production release. No teacher republish or history migration is needed. Keep the running trial source and profile. + +## Promotion Candidates + +- Target: teacher/model capability ADR and architecture. Proposal: explicit per-question teacher model with cloud capability gating, unchanged persona/resources/version/payer; voice is draft-only. Evidence: service/UI/billing tests above. Future impact: three-part rollout and immutable retry choice. Earlier cloud-only model ownership is extended by this user's explicit selection request; canonical promotion belongs to integration. + + +## Project Context Loaded + +- Task 20260929-teacher-composer-4f829ac1, feature, owns this worktree and branch per Identity at base cd1ba41. App worktree creation returned Not a git repository for the parent chat directory; official task_context isolated from the verified previous trial source. Start/status passed. +- Read entry/memory index, active task, positioning, current integrated snapshot, decision index and teacher/model-capability ADRs, system overview/module map/data flow, domain/success criteria and evidence/reflection/commitment indexes. Read every peer scope/constraints/promotion record through status registry. Current source includes newer approved timestamp/bubble/community changes than the integrated snapshot. +- Teacher capability/persona is cloud-owned, with per-account/teacher local history, student-paid runs and per-question version/project/read-only context. The user now explicitly wants client model selection, extending the earlier cloud-only model choice while preserving cloud persona/resources and billing. Main remains the validation and credential boundary. +- Peers: earlier trial/community work retained for running app; teacher-help-label and formal application release work remain separate; empty-work-preview and operation language work are not in this baseline. No identified semantic conflict; retain all current labels/UI and leave peer checkouts unchanged. +- Unknown dependency: current cloud /questions client has no model override field; inspect the committed Yuxi contract before deciding server support. No cosmetic model dropdown that silently keeps another model. +- Gate: Passed for client work and dependency investigation. + +## Plan + +1. Reuse the operation voice hook/button and input safeguards, scoped to account/teacher/context; keep transcription as an editable draft. +2. Trace cloud model capability/override support and implement an authoritative model selection contract, preserving request identity, version binding, student billing and history; selected models use their defaults. +3. Verify account/teacher switches, requests/retries, unavailable models and responsive composer layout; run required checks and update the trial in the background when the working integration is ready. diff --git a/.project-docs/30-worklog/tasks/20260929-teacher-prompt-buttons-a682d30f.md b/.project-docs/30-worklog/tasks/20260929-teacher-prompt-buttons-a682d30f.md new file mode 100644 index 00000000..f55adfc8 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260929-teacher-prompt-buttons-a682d30f.md @@ -0,0 +1,85 @@ +# Task: Move configured teacher prompts above composer + +## Identity + +- Task ID: 20260929-teacher-prompt-buttons-a682d30f +- Mode: Feature +- Branch: codex/20260929-teacher-prompt-buttons-a682d30f-teacher-prompt-buttons +- Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20260929-teacher-prompt-buttons-a682d30f +- Base commit: 78cfde67340aaccea8e8edc2505136cfcf865eda +- Owner: codex +- Status: Ready for Integration + +## Scope + +- Move Yuxi-configured teacher shortcuts above the composer as small pastel buttons with soft shadows; preserve exact configured label/prompt and existing send/voice/model behavior. + +## Intent And Constraints + +- User authorized code and background trial update. No subagents, cloud deployment, paid prompts, main merge/push, focus changes or history deletion. + +## Outcome + +- Configured teacher shortcuts now sit outside and directly above the composer. Buttons use 13px normal text (same as reply body), pale cream fill, no border and soft shadow; wrapping/overflow/keyboard focus supported. +- Adapted product/tests dependency from main 53d5b6b (bb96941); original source task record remains in its source commit, excluded from this feature boundary. Explicit empty arrays hide prompts; absent historical field retains approved fallback. Published label and prompt are unchanged. +- Trial Renderer updated in place in PID 32583; Main and empty-work preview overlay remain intact. Live Alice cloud v53 supplies “继续看看咯”. + +## Verification + +- Typecheck, lint (0 errors / 8 existing warnings), production build passed. 75 focused unit tests and 23 headless browser scenarios passed, including 319/508px overflow, keyboard focus, exact configured prompt sends, preserved drafts, cloud refresh/empty list and voice/model controls. +- First test pass exposed two stale assertions in the imported tests (send argument position and old toolbar ownership); adapted them. One rerun hit the existing old-topic stream timing test; serial rerun passed all 75 with no product changes. +- Trial screenshot confirms 13px, weight 400, no shadow, buttons above composer, no horizontal overflow. Same account/storage/drafts/teacher/tab; focus monitor reports no trial activation. +- Operation hydration initially collided with the existing E2E trial 250ms background-sleep timer while the desktop was at loginwindow. Read-only inspector diagnosis traced stop to BackgroundLifecycleController.sleepIfIdle (same failures present at 11:28Z before this update). A temporary Main-owned snapshot-verification lease allowed normal selectConversation hydration; then released it. No runtime code or user files changed. Final comparison: all 14 operation messages, teacher turns, drafts, selected teacher, route/tab unchanged; 15 timestamps visible. No live prompt or microphone call. All inspector breakpoints were removed. + +## Follow-ups + +- Feature branch awaits separate integration; no main merge/push or cloud deployment in this task. Prior model-selection cloud rollout remains pending. + +## Promotion Candidates + +- Target: integrated classroom UI documentation. Proposal: configured prompts live above the composer as borderless body-size buttons with soft shadows; exact cloud labels/prompts, absent/empty compatibility and send guards retained. Evidence: user approval, 75 unit / 23 browser tests and live trial comparison. No product-policy conflict; canonical promotion belongs to integration. + +## Project Context Loaded + +- Task ID/mode/branch/worktree/base: matching official reservation and owner verified; feature task at 78cfde67340aaccea8e8edc2505136cfcf865eda in the isolated path above. +- Read: AGENTS; entry, memory index, active task, positioning, current state, decisions/teacher ADR, architecture, domain rules, success criteria, evidence/commitment indexes and all registered peer Scope/Intent/Promotion sections. +- Goal: child-friendly creation with cloud-defined teachers and Main-owned auth/model access. Current code and README supersede stale canonical mentions of retired discussion components. +- Active peers: window-close (native lifecycle, explicitly excludes trial), help-label/replies-release, child-language (undefined scope), and completed teacher/composer/community/empty-preview tasks. No concrete semantic conflict; preserve their committed behavior and do not modify peer checkouts. +- Relevant dependencies: main 53d5b6b adds configured shortcuts; current trial has 78cfde6 microphone/models and empty-preview 0167d36 overlay. Only shortcut support and presentation are brought into this task. +- Files: TeacherChatPanel, classroom-workspace.css, shared definition and focused UI/layout fixtures, README. Existing single light palette and body font remain; design skill recommendations subordinate to approved layout. +- Gate: Passed. + +## Plan + +1. Reconcile committed Yuxi shortcuts support with trial composer, preserving empty-array versus absent compatibility. +2. Move controls above composer and give them body-size, normal-weight, pale rounded styling with keyboard focus and wrapping. +3. Verify UI behavior/layout, typecheck/lint/build; update current trial in background preserving source overlays and user state; document evidence. + +## Compact Shadow Follow-up + +- Same-task ownership resumed and verified at efd1894. Reused the unchanged planning context above and reread current peer scopes; no new conflict. Gate passed. +- User now wants smaller buttons without a visible border, using background and soft shadow. Keep 13px text for readability, reduce minimum height from 36px to 30px and padding to 4px/10px, soften corners. +- Plan: update only prompt CSS, existing layout assertions and product/task documentation; verify targeted layouts and build. Refresh the built stylesheet in the running trial without remounting conversations or activating the window. + +- Completed: prompt button minimum height 30px (was 36px), padding 4px/10px, 8px corners, no border, soft 2px/6px shadow and pale cream background. Font remains 13px/400. +- Verification: typecheck, lint (0 errors, 8 existing warnings), source/staged production builds and six 319/508px prompt/model/microphone layout scenarios passed. Existing checks now assert no border, a shadow and 30px height. +- Updated the production stylesheet link in the live trial, with new built assets/index installed for subsequent reload. No Renderer remount or Main restart; live JS behavior is unchanged. Native capture confirms 30px, zero border and soft shadow. All localStorage, input text, operation messages, teacher turn text and route remained byte-identical; window stayed unfocused. + +## Click Affordance Follow-up + +- Same-task ownership and existing planning context verified again at dc8f766; peer scope decisions remain compatible. Gate passed. User wants the compact borderless prompts to look unmistakably clickable. +- Plan: retain 30px/13px sizing, strengthen the warm fill and bottom shadow, add a small decorative chevron, and provide hover/pressed depth feedback with reduced-motion support. Update existing layout checks and hot-swap the built stylesheet in the trial, preserving live conversations. + +- Completed: 30px borderless buttons now have a warmer pale-yellow fill, 2px bottom shadow and decorative chevron. Hover raises 1px and pressed state lowers 1px; reduced-motion keeps position stable. Exact configured text and accessible name unchanged. +- Verification: typecheck, lint (0 errors / 8 existing warnings), source and combined-stage production builds, and six focused browser cases passed. Existing cases now exercise hover, pressed and reduced-motion feedback as well as 319/508px layout and exact prompt sends. +- Trial stylesheet index-ydJGBX8O.css installed and activated without reload. Native screenshot inspected; computed height 30px, font 13px, border 0 and raised shadow confirmed. All storage, inputs, 14 operation messages, teacher message text and route unchanged; focused=false before/after. No cloud changes or live messages sent. + +## Smaller Reply Cards Follow-up + +- Resumed the same visual refinement task at 79b1dc1; verified ownership, existing planning context and peer registry against the last reviewed state. No new overlap. Gate passed. +- User identifies the generated three-color reply cards, separate from the configured prompt buttons. Reduce one size: text 16px to 14px, padding 14px/16px to 10px/12px, radius 16px to 12px, gap 10px to 8px, arrow 16px to 14px. Keep full row width and at least a 44px target, colors and click/send behavior. +- Plan: change scoped CSS, existing browser assertions and docs; build and swap the current trial stylesheet without remounting conversations or changing focus. + +- Completed the requested size step for generated reply cards only: 14px text, 10px/12px padding, 12px radius, 8px row gap, 14px chevron and 44px minimum target. Three colors, full-width layout and click behavior unchanged; configured prompt buttons retain their previously approved size/style. +- Verification: typecheck, full lint (0 errors / 8 existing warnings), source/combined-stage builds and 10 browser reply-history/card interaction cases passed at 319px and 508px. +- Hot-swapped trial stylesheet index-CZpr_a7Y.css without reload or focus acquisition. Actual three screenshot cards now measure 45.8px high with 14px text. Native image inspected; storage, current text inputs, operation messages, teacher history and route match before the update. No live model calls. diff --git a/.project-docs/30-worklog/tasks/20260929-teacher-ui-merge-67ac892e.md b/.project-docs/30-worklog/tasks/20260929-teacher-ui-merge-67ac892e.md new file mode 100644 index 00000000..760227a1 --- /dev/null +++ b/.project-docs/30-worklog/tasks/20260929-teacher-ui-merge-67ac892e.md @@ -0,0 +1,57 @@ +# Task: Merge teacher composer and prompt UI into latest main + +## Identity + +- Task ID: 20260929-teacher-ui-merge-67ac892e +- Mode: Feature +- Branch: codex/20260929-teacher-ui-merge-67ac892e-teacher-ui-merge +- Worktree: /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20260929-teacher-ui-merge-67ac892e +- Base commit: e30d357d9875233ffc1a1da6f7d0fc55f9595635 +- Owner: codex +- Status: Planning + +## Scope + +- Merge committed teacher UI source fef3b3a and its approved composer, timestamp and welcome dependencies into latest main; preserve current published shortcuts, catalog refresh, project-scoped history, bundled child-reply plugin, empty-work preview and window lifecycle. + +## Intent And Constraints + +- User authorizes fetching latest code and merging into local main. Preserve source commits and foreign worktrees. No subagents, remote push, cloud publication, paid calls or trial restart. Main is owned by a ready historical task; all preparation occurs in this isolated checkout, with only the explicitly requested final clean fast-forward in main. + +## Outcome + +- Prepared a verified merge of source fef3b3aa9bc048a42f5f7b6c0a936c2519afe7dc into main e30d357d9875233ffc1a1da6f7d0fc55f9595635. Resolved nine files by combining project-scoped routing/history and directory revision refresh with teacher models/voice, timestamps, standalone welcome bubbles, community link, prompt placement and smaller reply cards. README preserves project isolation and current cloud configuration ownership. Source history retained. Final clean-main fast-forward pending. + +## Verification + +- Repository-pinned pnpm 10.33.4. Typecheck, full lint (0 errors / 8 pre-existing warnings), production Renderer/Main/Preload/utility build and 388 focused unit tests passed. +- Full suite: 274 files / 2764 tests passed / 3 skipped, plus the separately serialized pressure test passed (2765 total passed). No failing tests. +- Browser regression: 45 initial cases passed; the two timestamp cases exposed a fixture missing the new project-scoped teacherConversationsPath export. Added the matching fixture export and models response, then both cases passed. All 47 affected cases now pass across 319/375/508/768/1280/1470px as applicable. +- Six Electron scenarios passed: draft preservation and work/chat switching, incomplete-reply retry, project-separated chats/drafts through reload, catalog refresh, three-completed-round introduction dismissal, and click/dismiss persistence. Used isolated synthetic profiles/Host API fixtures. An untracked built-entry wrapper suppressed native activation for these tests; restored exact production entry afterward. No live trial, user conversation, cloud configuration or paid model was touched. +- Full main-only window lifecycle, empty-work preview, bundled language plugin and existing catalog/project-isolation behavior remain in the merge; no file outside this accepted feature chain was replaced with the older source tree. +- Project doc drift reports only four imported source task records as FOREIGN_DOC because it compares source history against this task main base. Verified each working/index blob byte-for-byte against fef3b3a (74151afe73, f97f019d0a, 860db9cfc7, f55adfc884); all canonical files are unchanged. The explicit user request to merge this branch authorizes carrying its committed history. This is not adoption or editing of unknown work; no registry/script bypass or override was used, and this diagnostic is not reported as a passing drift check. + +## Follow-ups + +- Fast-forward main only after rechecking its clean state and original HEAD. No remote push or trial restart requested. Keep feature/composer sources available for continuing temporary-app work and the pending separately authorized cloud rollout. Offer optional cleanup of this completed merge worktree after integration. + +## Promotion Candidates + +- Existing source UI/model/timestamp promotion candidates remain for the canonical integration owner. This merge adds no product decision and does not modify canonical records. + +## Project Context Loaded + +- Task ID/mode/branch/worktree/base: 20260929-teacher-ui-merge-67ac892e / feature / codex/20260929-teacher-ui-merge-67ac892e-teacher-ui-merge / /Users/chillishark/Makelore 麦洛/.codex-worktrees/makelore/20260929-teacher-ui-merge-67ac892e / e30d357d9875233ffc1a1da6f7d0fc55f9595635. Official start/status verifies ownership and reservation. App worktree tool reported parent directory is not a Git repository; official skill isolation succeeded. +- Read AGENTS, entry/memory/planning gate, own record, positioning, current state, decisions and teacher ADR, system overview, relevant domain/success criteria, evidence/reflection/commitment indexes, and all peer Scope/Intent/Promotion sections. Source composer and prompt outcomes/verification read. +- Goal: child-friendly creation with cloud-defined teachers, Main-owned credentials/tools/billing and per-account/project/teacher ongoing chats. Latest main includes version/revision refresh, configured prompts, empty preview, bundled language plugin and native close fixes absent from the trial branch; merge must combine these rather than replace main. +- Peer ownership retained: composer/prompt/community are completed source dependencies; empty-work and window-close outcomes already in main. Active help-label/release and undefined child-language scope remain separate. Older component lifecycle proposals are superseded by current text/quick-reply-only implementation. No unresolved product-policy conflict. +- Existing canonical integration lock belongs to classroom-integration; this code merge uses feature mode and does not reconcile canonical documents. Source task records remain unchanged. +- Model selection is capability-gated; paired cloud changes remain separately pending, and this merge does not deploy them. +- Files: teacher routes/service/hooks/panels, shared model/timestamp contracts, chat layout/welcome, README and existing tests. +- Gate: Passed. + +## Plan + +1. Merge committed feature source into a candidate based on clean latest local main; resolve textual overlaps using both approved behaviors. +2. Run typecheck, relevant tests then full unit suite, lint, production build and affected browser/Electron checks. +3. Verify source ancestry and preservation of main-only changes, commit integration evidence, and fast-forward local main. Retain trial and foreign worktrees. diff --git a/README.md b/README.md index 65c6e9e3..1f84dca6 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ Code 会话保存的模型被下架后,会提示重新选择可用模型;用 Makelore 是一个面向软件、视觉创作、智能机器人与个人云智能体的 AI 桌面工作台。当前版本为 `2.0.0`,源码提供四个模块入口;云智能体需配套配置 WS/Yuxi 服务。模块入口页按 Agents、Code、Canvas、Robot 排列,统一采用横向插画卡片;Agents 标语为“打造你想象中的AI助手”。工作区左上角入口点击后返回模块入口页: - `Makelore Code|AI 编程`:管理本地项目、项目智能体、对话、文件上下文、代码变更和运行时。 - 会话工作台采用浅蓝项目栏、彩色通用手柄图标与卡片选中态;中间对话/作品使用圆角标签,右侧智能体咨询使用奶油色标题区。沿用真实项目和会话入口、侧栏伸缩与账户菜单,样式只作用于 Code 会话工作台。 + 会话工作台采用浅蓝项目栏、彩色通用手柄图标与卡片选中态;中间对话/作品使用圆角标签,右侧智能体咨询使用奶油色标题区。沿用真实项目和会话入口、侧栏伸缩与账户菜单,样式只作用于 Code 会话工作台。可编辑的空对话在输入区上方的可用区域中央显示童趣欢迎语;「去作品广场逛逛」入口紧贴输入框上方,两者一起位于底部。老师介绍以固定宽度浮在右上角,不占用中间布局;欢迎语和底部输入区始终相对项目栏右侧的整个内容区域居中;小窗口的欢迎内容独立滚动;发送首条消息后收起欢迎区。侧栏保留「作品广场」入口,只有点击后才在系统浏览器打开公共广场,不请求、摘取或内嵌展示广场作品。打开广场保留当前项目、对话、草稿和附件,不自动发送消息;老师求助按钮保留临时应用的最新文案「帮我整体看🧠」。 - `Makelore Agents|AI 智能体`:配置个人云智能体并试聊,发布后正式对话、接入个人微信或创建应用 API Key。支持流式回复、审批、排队请求、附件与产物、自动任务和活动历史;重新进入时恢复最近智能体与对话,未确认操作可按原输入重试。聊天可提出日程建议,由创建者核对能力、时间和费用后启用。知识库上传、替换及导入本人会话附件后自动解析并建立索引;文档卡片区分排队、解析、索引、可用和失败,持续刷新已加载文档,失败可原地重试;零文本内容不会显示为可用,新文档可用后才移除被替换的旧文档。支持删除知识内容;个人 MCP 凭据、Skills、子智能体可自助管理。支持智能体和会话归档、发布版本比较及恢复为草稿。除知识库 embedding 向量化外,模型调用(包括知识处理触发的对话模型调用)仍按原规则由创建者个人词元点数支付,可按智能体设置每次任务和每日上限,费用按时间、来源、应用分页统计;embedding 向量化由平台承担,不计入用户用量、词元点数或智能体费用上限;应用调用者保有自己的内容空间。Main 管理云会话和本机文件,Renderer 通过 Host API 操作。配套服务接入见 [Yuxi MakeLore 说明](https://xerrors.github.io/Yuxi/advanced/makelore-agents.html)。 编辑采用全高桌面分栏,支持拖动调整与展开、独立滚动、小窗口配置/试用切换。首次创建按选用途、说要求、试一试引导,提供科学问答、故事、英语和笔记整理起点;回答方式可多选,特别要求直接编辑,仍以草稿的 system_prompt 保存。新助手预选云端目录的第一个可用模型并明确显示,可自行更换;已有助手不自动换模型。编辑区以“我的要求”和“高级设置”两个 Tab 切换,默认显示基础要求;高级设置包含能力、知识与限制,返回时保留上次分类,切换保留未保存的输入及当前试用。已开始使用的助手在顶部显示“修改已保存,尚未应用”或“已应用最新修改”,并提供显眼的“应用最新修改”按钮;保存只更新草稿,应用沿用同一发布操作,失败重试保留原操作身份。草稿明确保存(Ctrl/⌘+S),保存与切换页面保留当前试用及未发送文字;“保存并重新试用”使用最新保存的要求,保留上次试用供对比,并可复用上次问题,不自动发送。示例问题仅填入输入框;有运行、排队、审批或未确认操作时先处理当前试用。工具确认先展示实际操作说明与参数,完整技术详情可展开。确认“开始使用”会创建仅自己可用的发布版本并进入独立对话,微信渠道与应用接入仍单独配置。费用上限在限制中独立保存。Enter 发送、Shift+Enter 换行,F6 切换面板焦点、Esc 恢复分栏。 云知识库的 embedding 模型只由平台管理员在 Yuxi 后台维护:在「智能体管理 → 模型供应商」启用供应商,按供应商要求填写有效的服务地址和 API Key,添加并启用 embedding 模型并填写实际向量维度;embedding 向量化费用由平台承担,不计入用户用量、词元点数或智能体费用上限;one-api 不参与 embedding 配置,Works Square 只负责其他应计费模型调用的创建者词元点数账本。客户端区分目录加载中、加载失败与无可用模型,空状态提供管理员配置说明;「刷新知识库与模型」保留未提交名称,未确认的创建请求沿用原操作身份。 @@ -153,6 +153,7 @@ Pi 正式包必须继续运行 `pnpm run verify:artifact:pi`、`pnpm run smoke:p - Conversation 历史按需从 Main-owned Snapshot 读取。Session hydration 沿完整 active branch 投影可见历史;公开 SSE 交付 Snapshot、会话元数据变更通知,以及按 Conversation、worker generation 分组的 `patch-batch`。Renderer 整批校验连续 seq 后在一次状态事务中顺序应用,缺口或畸形批次只恢复目标 Conversation,隐藏 Conversation 的流式更新不会提交选中时间线。Main 发出 `lifecycle:sleep` 时页面关闭旧事件流;视图挂载、项目上下文变化、窗口重新可见或获得焦点时,已选 Conversation 会静默重读权威 Snapshot,从而收敛后台已结束的任务,但绝不重放已接受或状态不确定的 mutation。时间线默认挂载最近 120 个节点,接近顶部时按 100 个节点自动追加更早内容并补偿新增高度,保持当前阅读锚点。 - 核心时间线按每条用户消息组织为“用户消息、连续处理过程、结论回复”。Pi 的流式 thinking、工具、压缩、重试、通知和 `subagent.v1` 单个/并行/串行子任务共享一条轻量过程轨道;运行时显示“处理中”和活动指示,完成后按耗时(历史缺少计时时按项目数)收纳为可展开的“已处理”摘要。文字层级按结构化事件类型划分,不按文案关键词猜测:助手在工具调用前后直接对用户说的过程正文与最终结论都使用黑色;thinking、工具输入输出、压缩、子任务和运行状态使用浅灰辅助文字,仍在运行的灰色缩略文字带有低对比度 Shimmer 擦拭高光,结束后停止动画;错误状态保持红色。折叠的 thinking、助手过程正文和工具输出固定展示第一个可见块的首个非空行,横向偏移始终为零;尾部流式更新不会替换未变化的首行预览,展开后仍可查看完整内容。两类流式文字均在原位更新且不做位移动画,助手过程正文也不使用 Shimmer。工具调用用一行摘要持续展示工具名称、最新输入或输出和运行状态,并可按流式批次轻量整行轮动。思考、助手过程正文和工具节点均可展开查看完整详情,工具的累计输出和浏览器附件留在对应的二级展开区而不形成独立气泡。结论回复按安全 Markdown 排版标题、列表、引用、代码和表格;可点击的 HTTP/HTTPS 与本地 HTML 链接统一使用无底色的橙色文字。左键通过 Main-owned 系统入口交给默认浏览器打开;右键可打开系统菜单,网页链接可复制地址,本地链接还可在访达或系统文件夹中显示。同一回复已经给出完整 HTML 路径时,对应的相对查看路径也会解析到该文件。内部 `turn-start`/`turn-end` 只维持事件顺序、不呈现在界面。 - Composer 使用贴底的复合输入面板,支持文字、登录后的语音转写,以及粘贴或选择 PNG/JPEG/WebP/GIF 图片;每条消息最多 16 张图片、最多并行上传 4 张。语音录制通过现有 Main-owned Works Speech Host API 转成文字,并在项目或账号切换时立即停止;当前对话的模型与思考强度组合入口、语音及发送/中止入口保留在底部控制行,模型入口默认使用透明底色。模型与思考强度通过向上展开的分级菜单切换,只列出当前 Provider 或 Makelore 已验证模型档案实际支持的选项;Qwen3.6 Plus 使用混合思考开关,Qwen3.8 Max 暴露低/中/高三档并把产品“高”映射到服务端 `xhigh`。模型仅报告 `off` 时明确显示“不可调”,不伪造未知模型的推理档位。切换完成后静默刷新当前 Snapshot,只有真正恢复 worker 时才显示本地 Agent 重连提示。重复读取 Snapshot 不发布无变化的 worker 状态;Snapshot 与 SSE 批次竞态时,Renderer 会丢弃已覆盖事件并只应用连续的新尾部,避免形成伪重连循环。上下文接近模型限制或发生溢出时由 Pi 自动整理,Composer 不提供手动整理入口;自动整理的进度和结果仍显示在对话过程轨道中。图片在发送前只保留本地预览,点击发送时才经 Main-owned 有界二进制接口上传一次;Main 在落盘前核对 MIME 与最小图片签名,状态与事件只保存 attachment id,时间线按需读取二进制并创建临时 object URL,不保存重复 base64。发送后立即展示图片附件;Pi 实时消息与历史会话恢复都将图片映射到同一附件存储,重复加载复用已有文件。智能体报告 `changed-file.v1` 后,输入框上方会展示当前轮次的文件数与增删行统计,并可展开查看 Main-owned 变更快照中的逐文件差异。 +- 操作 Agent 和所有老师的消息下方显示本地时间:提问记发送时间,回复记完成时间;当天显示时分,较早消息加日期,跨年加年份,悬停显示完整日期和秒。流式回复显示“回复中…”。操作历史读取 Pi 原始用户时间和回复条目落盘时间;实时回复在结束时记录,再由持久化记录校准。老师每次回复(含主动检查、取消与失败)单独保存完成时间;旧记录缺少时间时显示“时间未记录”,不使用当前时间或话题更新时间回填,也不改写用户历史。 - Makelore 在应用侧按 Session 独立提交、跟踪和隔离运行状态,不使用“当前对话正在回复”的全局界面锁;同一 Session 的后续消息仍按顺序排队。最终产品中的 Pi 运行时会通过受控 Provider-shaped 回环 smoke 验证同一 Agent Server 内多个 Conversation 逻辑线程的重叠执行、状态隔离、凭证引用隔离、单线程关闭和 Server 崩溃后重启,以及独立子 Agent 进程的隔离与回收;真实外部 Provider 的并发、限流、协议兼容和凭证隔离仍是独立风险,未执行真实 Provider 验证时不得标记为 Pass。 - 首次发送会立即生成稳定的乐观用户消息;HTTP 202 只表示本地 Agent 已接收。后续失败不会删除已接受消息,不确定交付不会自动重发;准备失败可在目标 Conversation 上手动恢复。 - 运行中的 Conversation 可把新消息作为 steer 引导当前回答或 follow-up 排到下一轮,并显示队列位置;队列正常在 `agent_settled` 后释放,用户可中止当前运行。若 Pi 已终结但该握手丢失,Main 会在 30 秒宽限后仅探测目标线程:权威 idle 状态会补齐持久化最终回复并 exactly-once 释放 ownership,矛盾状态则显式失败该目标,且不会重放已接受请求。select/confirm/input/editor 交互在输入区上方回答,select 除预设选项外支持自由输入;交互提交或取消后立即从输入区移除,失效请求会就地提示并允许重试或取消。 @@ -172,26 +173,27 @@ Pi 正式包必须继续运行 `pnpm run verify:artifact:pi`、`pnpm run smoke:p - 本地读取使用连续分页:目录按层展开,默认四层、最多六层,跳过 `.gradle` 等生成目录;条目随页面按需产出,深层目录可单独查询;文件和会话默认每页 120 行、最多 400 行,返回连续原文、起始行列与精确 next 游标,列按 Unicode 字符计数、换行统一为 LF。单个云端结果最多 8 KiB,长行也能继续读取;不受文件 UI 的 256 KiB 预览限制。会话索引保留消息 ID 和摘要,按 ID 读取完整公开原文。每题最多十二批、累计成功结果 64 KiB(包含结果元数据),不足 512 字节时收尾;不上传 Pi 原始日志或思考。云端保留智能体对话及收到的片段,完整工程仍在本机。本地旧版模型仍按自身上下文和六批额度缩小结果。 - Code 课堂左侧项目栏默认宽 171px,可拖动右边缘在 128–256px 间调整,双击恢复默认并在本机记住宽度;标题栏同步对齐。项目名与会话名超长省略并可悬停查看;右侧咨询栏默认宽 508px,可拖动至 640px,紧凑窗口仍使用覆盖式咨询。 -- 客户端没有内置老师/朋友角色。已下发智能体以头像和名称并排显示在标题栏,点击后打开对应智能体的讨论;数量较多时横向滚动,咨询面板不再提供智能体下拉框。名称、头像、简介、欢迎语和推荐问题均由服务端提供。首次使用默认选择运营指定项。同一账号在同一项目内与同一智能体只有一个持续聊天;不同项目的历史、未发送草稿、未读状态与阅读位置分别保存,切回项目后恢复,不再显示新话题按钮或话题选择。旧版跨项目聊天按每轮保存的项目来源拆入对应聊天,原始文件保留;归属不明的旧消息留在原文件,不分配给任一项目。每轮接受时固定项目、操作对话与当前发布版本;旧的未分配项目草稿可展开查看,不自动转给其他智能体。打开或切换只读取配置和历史,不触发模型;咨询栏支持调整宽度和重新打开。 -- 鼠标悬停或键盘聚焦顶部智能体入口时,以奶油色圆角卡片显示该智能体的发布介绍;离开、Escape 或点击入口后收起,没有介绍则不显示空卡片。咨询栏不再显示重复的头像、名称标题条,也不显示介绍及新话题、展开、关闭三个图标;老师身份保留在顶部入口及消息署名中。智能体生成的追加问题以淡蓝、奶油黄、淡紫卡片依次承载,正文 16px、行高 1.8,保留整卡点击发送与忙碌时禁用。回复下方不再显示重复的输入引导或省略早期消息条数;输入框提示为“和老师聊聊”。再次点击当前入口或在咨询栏按 Escape 可收起,切换其他入口仍直接打开对应讨论;拖动边缘调宽保留,已下发智能体仅保留只读旧记录入口。悬浮介绍不会创建话题、调用模型或确认未读消息,作品原生预览同时避让介绍卡和真实主动气泡。 +- 客户端没有内置老师/朋友角色。已下发智能体以头像和名称并排显示在标题栏,点击后打开对应智能体的讨论;数量较多时横向滚动,咨询面板不再提供智能体下拉框。名称、头像、简介、欢迎语和推荐问题均由服务端提供。首次使用默认选择运营指定项。同一账号在同一项目内与同一智能体只有一个持续聊天;不同项目的历史、未发送草稿、未读状态与阅读位置分别保存,切回项目后恢复,不再显示新话题按钮或话题选择。旧版跨项目聊天按每轮保存的项目来源拆入对应聊天,原始文件保留;归属不明的旧消息留在原文件,不分配给任一项目。每轮接受时固定项目、操作对话与当前发布版本;旧的未分配项目草稿可展开查看,不自动转给其他智能体。打开或切换只读取配置和历史,不触发模型;咨询栏支持调整宽度和重新打开。顶栏最右侧面板按钮用于收起/展开老师对话栏,不切换“操作对话/作品”、不改变所选老师,保留历史及两侧输入草稿,也不发送消息。 +- 鼠标悬停或键盘聚焦顶部智能体入口时,以奶油色圆角卡片显示该智能体的发布介绍;离开、Escape 或点击入口后收起,没有介绍则不显示空卡片。咨询栏不再显示重复的头像、名称标题条,也不显示介绍及新话题、展开、关闭三个图标;老师身份保留在顶部入口及消息署名中。智能体生成的追加问题以淡蓝、奶油黄、淡紫卡片依次承载,正文 14px、行高 1.7,使用更紧凑的内边距和圆角,保留整卡点击发送与忙碌时禁用。回复下方不再显示重复的输入引导或省略早期消息条数;输入框提示为“和老师聊聊”。再次点击当前入口或在咨询栏按 Escape 可收起,切换其他入口仍直接打开对应讨论;拖动边缘调宽保留,已下发智能体仅保留只读旧记录入口。悬浮介绍不会创建话题、调用模型或确认未读消息,作品原生预览同时避让介绍卡和真实主动气泡。 - 主动发言采用紧贴小头像的短气泡;只有存在真实主动消息或运营欢迎语时才显示这组浮层,收起消息时头像和气泡一起消失,顶部入口仍保留。长消息在气泡中最多显示三行,点击接回原智能体对话查看全文。不提供“智能体偶尔来看看”开关或本地模拟巡看控制,既有自动跟进与真实消息接收逻辑保留。作品原生预览对整组气泡和头像测量避让,避免图片被原生页面遮住。 - 新的单会话入口停止旧版前端定时跟进派发;项目主动观察由独立功能衔接,不能重新创建可见话题。未下发目录的旧版入口仍保留原跟进合同:前端每 5 分钟请求一次智能体跟进;窗口隐藏、来源归档、主对话正在执行、智能体正在回复或学生正在智能体栏写草稿时延后。Main 再检查在线启停、来源归属、项目级冷却和已完成文本指纹,未变上下文按下述十五分钟冷却处理。跟进沿用运营模型、已发布 Skills 与当前智能体话题,生成符合所选配置的具体建议或引导;以主动智能体发言持久化,不伪造学生消息。模型调用沿用智能体计费规则。 -- 进入项目时,智能体头像旁先显示运营发布的欢迎语气泡,不调用模型、不声称已检查项目;点击智能体进入咨询时立即收起欢迎语,并按账号/项目记住已查看,在同一项目关闭、重新打开或刷新后不重复弹出;主动收起也保留同样记录。真实的未读主动建议优先替换欢迎语,打开咨询不会自动将这些消息标为已读。主动发言在头像旁直接显示气泡,不抢焦点、不盖住作品;作品区按气泡与原生预览的实际交叠范围避让,已有工具栏高度计入计算,气泡位于独立智能体栏时不额外留白,窗口/分栏尺寸变化和收起气泡时实时归还空间;“和智能体聊聊”接回同一段咨询,“等会儿聊”收起并保留历史。已有未处理气泡时不堆叠新邀请;咨询已展开时仍显示真实未读主动消息气泡,直到学生主动点击查看或收起,期间的其他回复不会吞掉未读邀请。五分钟检查计时在刷新和项目切换后保留;智能体咨询里的新讨论也算进展,即使操作对话没有变化也会继续跟进;上下文完全未变时放缓到十五分钟一次,不永久停止。事件连接丢失时,每十五秒只读同步未完成回复,收到结果后停止轮询,不重复调用模型。失败如实提示,不以本地文案替代模型回复。已接受请求恢复使用原身份,退出账号和来源删除仍沿用 Main 中止边界。 -- 智能体支持自由提问;输入框为空时,底部显示暖黄色小按钮“继续看看👀”,输入文字后收起,清空后重现,发送按钮保持原位。只有学生点击才调用模型,程序说明学生主动求助且尚未提出具体问题,智能体结合可用上下文按云端配置回应。点击快捷回复会主动开始讨论;回复下方不再附加固定的二次求助链接;失败或中断后,可点暖黄色小按钮“继续看看👀”重试。这些快捷求助保留已有输入草稿,网络结果不确定时复用请求身份,已确认终态的请求不重复执行;解析失败可重新求助,不用固定问题伪装模型结果。服务端下发的推荐问题只填入草稿,由用户确认发送。界面不提供独立示范页面、“记一下”、共识或自动待办,智能体的回复下不再展示“我去试一试”和“复制”按钮,学生通过已有的操作对话/作品入口继续创作。 +- 进入项目时,每位已下发智能体各显示一个浅色自我介绍气泡,外层透明,不显示公共底板、标题或整组关闭入口;整张气泡可点击,独立关闭按钮只收起该老师介绍,优先使用云端介绍,缺省使用发布欢迎语;不调用模型、不声称已检查项目。气泡进入项目即依次出现,不点击时保留到操作 Agent 第 3 轮完整回复结束后自动收起,也可分别关闭;按账号、项目、智能体保存计数及已介绍状态,刷新与操作对话切换延续进度。旧历史、老师咨询、工具调用、重试中间状态、失败/中止和其他项目不计入,新增智能体单独补上并从零计数。点击气泡或头像进入对应咨询,只记住这一位已介绍;咨询期间收起介绍组,关闭后其余未查看介绍仍可见,悬停头像仍可查看完整介绍。没有下发目录的旧入口保留原欢迎语展示兼容。真实的未读主动建议优先替换欢迎语,打开咨询不会自动将这些消息标为已读。主动发言在头像旁直接显示气泡,不抢焦点、不盖住作品;作品区按气泡与原生预览的实际交叠范围避让,已有工具栏高度计入计算,气泡位于独立智能体栏时不额外留白,窗口/分栏尺寸变化和收起气泡时实时归还空间;“和智能体聊聊”接回同一段咨询,“等会儿聊”收起并保留历史。已有未处理气泡时不堆叠新邀请;咨询已展开时仍显示真实未读主动消息气泡,直到学生主动点击查看或收起,期间的其他回复不会吞掉未读邀请。五分钟检查计时在刷新和项目切换后保留;智能体咨询里的新讨论也算进展,即使操作对话没有变化也会继续跟进;上下文完全未变时放缓到十五分钟一次,不永久停止。事件连接丢失时,每十五秒只读同步未完成回复,收到结果后停止轮询,不重复调用模型。失败如实提示,不以本地文案替代模型回复。已接受请求恢复使用原身份,退出账号和来源删除仍沿用 Main 中止边界。 +- 智能体支持自由提问;输入框为空时,上方显示老师已发布的快捷提示,输入文字后收起,清空后重现,发送按钮保持原位。只有学生点击才调用模型,程序说明学生主动求助且尚未提出具体问题,智能体结合可用上下文按云端配置回应。点击快捷回复会主动开始讨论;回复下方不再附加固定的二次求助链接;失败或中断后,可点暖黄色小按钮“帮我整体看🧠”重试。这些快捷求助保留已有输入草稿,网络结果不确定时复用请求身份,已确认终态的请求不重复执行;解析失败可重新求助,不用固定问题伪装模型结果。服务端下发的推荐问题只填入草稿,由用户确认发送。界面不提供独立示范页面、“记一下”、共识或自动待办,智能体的回复下不再展示“我去试一试”和“复制”按钮,学生通过已有的操作对话/作品入口继续创作。 - 老师咨询只展示老师正文和可点击的快捷回复卡片,学生也可在“和老师聊聊”输入框自由输入。点击卡片原样发送该回复,保留当前输入草稿;旧话题的数据不会恢复想法板、结构图、流程图或对照表及其操作。 -- 客户端只提供入口事实、可用上下文和界面协议。讨论切入点、教学方式、是否提问、正文长度、前缀和表达风格由所选智能体的云端配置决定;不在“继续看看👀”、引导开口或后台检查时附加教学策略。当前新回复允许 0–3 条快捷回复,没有时用空数组,不要求凑满。 +- 客户端只提供入口事实、可用上下文和界面协议。讨论切入点、教学方式、是否提问、正文长度、前缀和表达风格由所选智能体的云端配置决定;不在快捷求助、引导开口或后台检查时附加教学策略。当前新回复允许 0–3 条快捷回复,没有时用空数组,不要求凑满。 - Main 对咨询回复统一使用 `{reply, quickReplies}`,本地读取工具的前言和云端非最终片段不当作最终回答;完整解析后展示正文和卡片,主动进展提醒仍直接展示正文。普通 Markdown、JSON 数据和代码示例保持正文。兼容读取旧 `{intro, questions}` 和带 `tool` 的回复,只提取正文与快捷回复,不恢复或更新组件。新输出超过 3 条快捷回复时保留完整正文、提示格式错误并保留原始回答,不展示超额卡片或静默截取;历史中已有的更多卡片在读取与后续保存时仍原样保留。独立资源上限只用于防护,不做旧式字数截断;格式错误的原文默认折叠、字面显示,不再次送进模型。 - 回复中的引号或换行格式有误时,仅在正文边界明确、修复后整体合法的情况下恢复完整文字,不把半句话当成完整回答。附加建议损坏时仍展示完整正文;正文无法确认完整时显示“这次回复未能完整显示”,由学生点击“重新回答”,沿用原问题、引用与来源;持续聊天须回到原项目和操作对话后重试,新一轮按当前发布版本执行,旧话题仍使用绑定版本,保留当前草稿,连续点击不重复发送。打开聊天或旧话题会从保留的原文中在本地恢复可确认的正文,不调用模型,不因显示修复而改写磁盘记录;原始内容继续折叠保留。不完整的回答和建议不进入后续模型上下文。 - 咨询正文支持 Markdown 标题、列表、表格、代码围栏、HTTP(S) 链接/图片和数学公式,长代码与表格在栏内横向滚动,不执行 HTML。云端正文维持 string 合同,未知对象不会猜测转成回答。工具活动只从云端主线程的类型化工具事件及 Main 本地读取过程获得,按本次问题/工具调用身份合并名称和状态,跨云端暂停、续接仍只计一次;暂停读取不算失败,状态以实际读取结果或问题终态为准,单独折叠显示;参数、结果原文和内部错误不混入回复,思考内容不作为正文。客户端仅保存自己实际收到的工具状态,不能补回旧历史或断线期间已经过期的事件。 - 云端咨询在 local_context 声明 read_protocol=2;需先部署配套 Yuxi API 和 worker,再升级客户端。云端持久累计读取字节及批次,每轮告知模型剩余额度;预算耗尽后消费最后一批结果,并以 tool_choice=none 要求根据现有证据形成答案和说明缺口。Main 限制实际返回量并拒绝第十三批读取,区分读取达到上限、上下文失效及格式无效。服务端对未声明协议的已安装旧客户端保留原工具参数与六批边界。 - 智能体人设、职责、提示词和 Skills 由发布配置决定。Main 不追加固定教学基线、不生成“小麦”、不清空所选智能体的 Skills,只添加真实工具能力、上下文边界和本轮界面协议。智能体名称叫“老师”“朋友”或“代码顾问”不改变调用路径或权限。 -- 输入框为空时显示当前老师已发布的快捷提示,按 Yuxi 中配置的顺序展示文案,点击直接发送该项实际提示词。Yuxi“编辑 → 快捷提示”支持最多 8 项,每项文案最多 40 字、提示词最多 6000 字;保存并发布后,目录更新即可生效,历史和未发送草稿保留。明确发布空列表隐藏全部按钮;缺少字段的历史版本保留“继续看看👀”。多个长文案在限定高度内滚动,运行中禁止重复发送。上线需先更新 WS 登记接口,再更新 Yuxi 和客户端。 +- 输入框为空时,在输入框外上方显示当前老师已发布的快捷提示;采用与回复正文一致的 13px 常规字重、无边框、浅色底与底部立体阴影的小巧圆角按钮,配有小箭头和悬停/按下反馈,横向排列并自动换行。按 Yuxi 中配置的顺序展示文案,点击直接发送该项实际提示词。Yuxi“编辑 → 快捷提示”支持最多 8 项,每项文案最多 40 字、提示词最多 6000 字;保存并发布后,目录更新即可生效,历史和未发送草稿保留。明确发布空列表隐藏全部按钮;缺少字段的历史版本保留“帮我整体看🧠”。多个长文案在限定高度内滚动,运行中禁止重复发送。上线需先更新 WS 登记接口,再更新 Yuxi 和客户端。 - `reply.ts` 只声明正文与快捷回复的输出格式,语言、长度、前缀与表达风格由所选智能体的云端配置决定。客户端不再发送选择、生成、转换或维护展示组件的指令。旧话题内的组件状态、快照和解析诊断作为不透明历史数据原样保留,不校验删除、不恢复交互;旧组件修改路由已移除。 - 当前输入是按预算截取的所选操作对话已完成分支文本、明确引用、咨询正文和快捷回复,并支持上述只读原文恢复;旧组件状态、快照和未解析原文不加入模型上下文或咨询历史读取结果。没有跨全部操作会话自动读取,也未向操作 Agent 注入咨询共识。提示词不声称看过未提供的画面或试玩过作品。Yuxi 话题经云端原生执行,旧版本地智能体由 Main 调用计费网关;均不创建本地 Pi 编程 worker。 - 智能体输入裁剪区分两种计量:本地模型以约每 2 个 UTF-8 字节估算 1 Token,并计入工具与读取预留;这不是精确分词或扣费依据,模型上限与实际 usage 仍由服务端拥有。云端沿用 query 字节上限,以完整 JSON 转义后的大小裁剪来源节选。智能体指令、当前问题和明确引用不会被静默裁掉;固定配置或回复协议超限时明确提示调整配置,避免让学生反复缩短短问题或新建话题。完整来源仍可由只读工具按需读取。 - `tests/fixtures/teacher-guidance-examples.json` 保留人工样例作为旧回复的读取兼容资料,仅验证正文和快捷回复投影,不加载到运行时,也不代表真实模型教学效果已验收。云端定义由 Yuxi 维护并由运营显式同步下发;本地代码修改不会自动改写云端配置或学生已接受问题的固定发布版本。 - 下发智能体的图标随发布版本保存,顶栏、咨询面板、欢迎区和消息使用定义中的 `icon_url`;未设置或图片加载失败时使用内置头像。运营在 Yuxi 更换图标后需同步下发配置,联系人使用当前下发的名称和图标;每轮执行仍记录实际发布版本。相对图片地址由服务端使用 Yuxi 公网入口补全。 -- 咨询聊天和文字草稿按当前云端账号、已下发智能体保存;切换项目或操作对话仍延续同一个聊天,每轮发送冻结当时的项目、来源和发布版本并检查启停。引用保留原项目/来源,切换后必须移除失配引用再发送;旧组件仅保留原始归档,不恢复交互。历史按50轮分页,流式事件只传当前轮;本机每轮独立原子落盘,索引保存消息摘要,旧轮次可通过只读工具按ID读取。可确认归属的旧话题按来源ID幂等迁入,原文件保留;“旧记录”入口只读查看升级前项目/来源话题。项目、Pi来源或版本变化,以及首次从旧组件协议升级时,切换内部云线程并带入有预算的近期正文和快捷回复,同一段由Yuxi原生历史压缩管理上下文。此版聊天历史在本机保存,不提供跨设备同步。关闭面板或切换会话不取消已接受的问题,退出账号会中止。请求先落盘再调用模型,同一请求标识不重复发送,重启后未完成请求标记中断。云端断线按游标和持久状态恢复;停止或退出账号会取消原云端问题,下次提问先停止旧的未完成问题再绑定新上下文。只接收云端主线程回答,子智能体文本不混入正文。没有操作对话时也可咨询,但无操作文本上下文。 +- 老师输入框复用操作 Agent 的麦克风,语音识别只追加当前草稿,不自动发送;切换账号、老师、项目或来源、关闭面板时丢弃过期识别。模型选择按账号和老师记忆,默认跟随老师配置;显式模型仅用于后续手动提问,思考使用模型默认设置。每轮冻结选择及幂等身份,保留发布人设、资源、版本与学生计费;主动检查继续遵循云端老师配置。模型目录与准入由 Main/云端验证,旧云服务显示待更新,不静默忽略选择。需先更新 WS 模型授权接口,再更新 Yuxi API/worker,客户端不会修改或发布云端配置。 +- 咨询聊天和文字草稿按当前云端账号、项目、已下发智能体保存;不同项目分别恢复各自聊天,同项目切换操作对话仍延续同一个聊天,每轮发送冻结当时的项目、来源和发布版本并检查启停。引用保留原项目/来源,切换后必须移除失配引用再发送;旧组件仅保留原始归档,不恢复交互。历史按50轮分页,流式事件只传当前轮;本机每轮独立原子落盘,索引保存消息摘要,旧轮次可通过只读工具按ID读取。可确认归属的旧话题按来源ID幂等迁入,原文件保留;“旧记录”入口只读查看升级前项目/来源话题。同项目Pi来源或版本变化,以及首次从旧组件协议升级时,切换内部云线程并带入有预算的近期正文和快捷回复,同一段由Yuxi原生历史压缩管理上下文。此版聊天历史在本机保存,不提供跨设备同步。关闭面板或切换会话不取消已接受的问题,退出账号会中止。请求先落盘再调用模型,同一请求标识不重复发送,重启后未完成请求标记中断。云端断线按游标和持久状态恢复;停止或退出账号会取消原云端问题,下次提问先停止旧的未完成问题再绑定新上下文。只接收云端主线程回答,子智能体文本不混入正文。没有操作对话时也可咨询,但无操作文本上下文。 - 删除操作对话会停止正在使用该来源的回复,保留项目级咨询历史;归档来源需先恢复后才能引用。原有来源会话级智能体 API 与存储保持兼容,原路径历史不自动迁移到新项目级咨询栏。运营草稿试聊仍使用 `niancode://coding-teacher/preview?draft_revision=N` 打开独立示例上下文,不读取学生项目,要求桌面当前账号具有运营权限。 - 旧版客户端生成的朋友话题在“以往讨论”中标记为仅查看,不再创建或继续调用,也不自动绑定某个云端智能体;可选择下发项进入唯一聊天。统一咨询保留原有项目草稿;旧版朋友未发送的文字和引用可从“查看旧版朋友草稿”只读查看,不自动提交。旧文件和原发布版本保留,不复制或改写历史。已有 `coding-teacher` 云端契约及历史目录名保留,避免无必要的数据迁移。 diff --git a/electron/api/routes/coding-teacher.ts b/electron/api/routes/coding-teacher.ts index 536fb59d..e8ca6571 100644 --- a/electron/api/routes/coding-teacher.ts +++ b/electron/api/routes/coding-teacher.ts @@ -33,10 +33,11 @@ export async function handleCodingTeacherRoutes( const config = url.pathname === '/api/coding/teacher/config' || url.pathname === '/api/coding/friend/config'; const catalog = url.pathname === '/api/coding/teacher/teachers'; const catalogRevision = url.pathname === '/api/coding/teacher/teachers/revision'; + const models = url.pathname === '/api/coding/teacher/models'; const draft = url.pathname === '/api/coding/teacher-preview'; const pending = url.pathname === '/api/coding/teacher-preview/pending-link'; - if (!legacy && !conversation && !source && !preview && !projectTopics && !checkIn && !config && !catalog && !catalogRevision && !draft && !pending) return false; - if ((config || catalog || catalogRevision || draft || pending) && req.method !== 'GET') { + if (!legacy && !conversation && !source && !preview && !projectTopics && !checkIn && !config && !catalog && !catalogRevision && !models && !draft && !pending) return false; + if ((config || catalog || catalogRevision || models || draft || pending) && req.method !== 'GET') { sendJson(res, 405, { error: '不支持此操作。' }); return true; } @@ -87,6 +88,12 @@ export async function handleCodingTeacherRoutes( sendJson(res, 200, await service.catalogRevision()); return true; } + if (models) { + const runtime = url.searchParams.get('runtime'); + if (runtime !== 'local' && runtime !== 'yuxi') throw new TeacherError(422, 'teacher_runtime_invalid', '模型来源无效。'); + sendJson(res, 200, await service.models(runtime)); + return true; + } if (catalog) { sendJson(res, 200, await service.catalog()); return true; diff --git a/electron/coding-runtime/pi/event-projector.ts b/electron/coding-runtime/pi/event-projector.ts index f49e5116..5d6d7c54 100644 --- a/electron/coding-runtime/pi/event-projector.ts +++ b/electron/coding-runtime/pi/event-projector.ts @@ -1,3 +1,4 @@ +import { messageTimestamp } from '../../../shared/message-timestamp'; import type { ConversationContentBlock, ConversationMessageNode, @@ -529,6 +530,7 @@ export class PiEventProjector { ? { clientRequestId: optimistic.clientRequestId } : {}), role: 'user', + timestamp: messageTimestamp(message.timestamp) ?? optimistic?.timestamp ?? this.now(), status: 'complete', blocks: await liveContentBlocks( id, @@ -723,6 +725,8 @@ export class PiEventProjector { kind: 'message', id, role: 'assistant', + // Live observation; settlement hydration replaces it with Pi's durable entry time. + timestamp: this.now(), status: message.stopReason === 'aborted' ? 'aborted' : message.stopReason === 'error' ? 'error' : 'complete', diff --git a/electron/coding-runtime/pi/runtime.ts b/electron/coding-runtime/pi/runtime.ts index a56cf174..2ed7eed2 100644 --- a/electron/coding-runtime/pi/runtime.ts +++ b/electron/coding-runtime/pi/runtime.ts @@ -814,6 +814,7 @@ export class PiConversationRuntime implements CodingConversationRuntime { id: messageId, clientRequestId: input.clientRequestId, role: 'user', + timestamp: this.now(), status: 'optimistic', blocks: [ ...(input.text.length > 0 diff --git a/electron/coding-runtime/pi/session-projector.ts b/electron/coding-runtime/pi/session-projector.ts index 9dbf6c6b..b1fbdb44 100644 --- a/electron/coding-runtime/pi/session-projector.ts +++ b/electron/coding-runtime/pi/session-projector.ts @@ -1,3 +1,4 @@ +import { messageTimestamp } from '../../../shared/message-timestamp'; import type { CodingRuntimeErrorCode, ConversationContentBlock, @@ -235,6 +236,10 @@ async function projectEntries( id: messageId, sourceEntryId: entry.id, role: message.role, + // Pi appends the entry on message_end; assistant.message.timestamp is its start time. + timestamp: message.role === 'user' + ? messageTimestamp(message.timestamp) ?? messageTimestamp(entry.timestamp) + : messageTimestamp(entry.timestamp), status: message.stopReason === 'aborted' ? 'aborted' : message.stopReason === 'error' ? 'error' : 'complete', diff --git a/electron/coding-teacher/cloud-runner.ts b/electron/coding-teacher/cloud-runner.ts index 3958ff05..5c213b10 100644 --- a/electron/coding-teacher/cloud-runner.ts +++ b/electron/coding-teacher/cloud-runner.ts @@ -251,6 +251,7 @@ export function prepareCloudTeacher( '/questions', { teacher_version: currentRequest?.teacherVersion ?? topic.version, + ...(currentRequest?.modelId ? { model_id: currentRequest.modelId } : {}), thread_id: currentRequest?.runtimeThreadId ?? topic.id, request_id: requestId, query, diff --git a/electron/coding-teacher/model-catalog.ts b/electron/coding-teacher/model-catalog.ts new file mode 100644 index 00000000..7a592dd0 --- /dev/null +++ b/electron/coding-teacher/model-catalog.ts @@ -0,0 +1,38 @@ +import type { TeacherModelCatalog } from '../../shared/coding-teacher'; +import { normalizeManagedModelCatalog } from '../../shared/managed-model-capabilities'; +import { teacherCloudRequest, assertTeacherAccount, TeacherError, type TeacherAccount } from './config-client'; +import { teacherCloudTransport } from './cloud-runner'; + +export function validateTeacherModelId(value: unknown): string | undefined { + if (value === undefined) return undefined; + if (typeof value !== 'string' || !value.trim() || value.length > 200 || value !== value.trim()) + throw new TeacherError(422, 'teacher_model_invalid', '请选择有效的模型。'); + return value; +} + +/** Main strips credentials; each selected model is revalidated when the question is accepted. */ +export async function teacherModelCatalog(account: TeacherAccount, runtime: 'local' | 'yuxi'): Promise { + if (runtime === 'yuxi') { + let result; + try { result = await teacherCloudTransport(account).json('/models'); } + catch (error) { + if (error instanceof TeacherError && error.status === 404) return { + supported: false, models: [], message: '老师服务更新后即可切换模型,目前使用老师配置。', + }; + throw error; + } + assertTeacherAccount(account); + if (result.model_selection === false) return { supported: false, models: [], message: '老师服务更新后即可切换模型,目前使用老师配置。' }; + if (result.model_selection !== true || !Array.isArray(result.models) + || result.models.some(item => !item || typeof item !== 'object' || typeof item.id !== 'string' || !item.id || item.id.length > 200)) + throw new TeacherError(502, 'teacher_models_invalid', '模型列表暂不可用,请重试。'); + const local = await teacherModelCatalog(account, 'local'); + const textModels = new Set(local.models.map(item => item.id)); + return { supported: true, models: result.models.filter(item => textModels.has(item.id)).map(item => ({ id: item.id as string })) }; + } + const config = await teacherCloudRequest<{ models: string[]; model_capabilities_v2: unknown }>(account, '/api/auth/me/model-config'); + const catalog = normalizeManagedModelCatalog(config.model_capabilities_v2, config.models); + assertTeacherAccount(account); + return { supported: true, models: config.models.filter(id => catalog?.models[id]?.inputModalities?.includes('text') + && catalog.models[id]?.outputModalities?.includes('text')).map(id => ({ id })) }; +} diff --git a/electron/coding-teacher/service.ts b/electron/coding-teacher/service.ts index bd12a9b9..69bf041a 100644 --- a/electron/coding-teacher/service.ts +++ b/electron/coding-teacher/service.ts @@ -33,6 +33,7 @@ import { TeacherConversationStore } from './conversation-store'; import { readJsonFile } from '../coding-projects/atomic-json'; import { compileTeacherContext } from './context'; import { prepareTeacherModel } from './model-runner'; +import { teacherModelCatalog, validateTeacherModelId } from './model-catalog'; import { prepareCloudTeacher } from './cloud-runner'; import { readTeacherSource } from './source-reader'; import { applyTeacherReply, teacherReplyInstructions } from './reply'; @@ -60,6 +61,7 @@ export interface TeacherServiceOptions { prepareModel?: typeof prepareTeacherModel; prepareCloud?: typeof prepareCloudTeacher; catalog?: typeof teacherCatalog; + models?: typeof teacherModelCatalog; readSource?(scope: TeacherScope): Promise; } export class CodingTeacherService { @@ -101,6 +103,9 @@ export class CodingTeacherService { : null; return { ...status, definition: published?.payload ?? null }; } + async models(runtime: 'local' | 'yuxi') { + return (this.options.models ?? teacherModelCatalog)(await this.account(), runtime); + } async catalog() { return (this.options.catalog ?? teacherCatalog)(await this.account()); } @@ -421,6 +426,7 @@ export class CodingTeacherService { checkIn?: { source: TeacherSourceContext; fingerprint: string } ): Promise { teacherTopicId(input.requestId); + const modelId = validateTeacherModelId(input.modelId); const intent = input.intent === undefined ? 'question' : input.intent; const structuredReply = intent !== 'check-in'; if (!['question', 'suggestions', 'guided-help', 'check-in'].includes(intent) || (intent === 'check-in' && !checkIn)) @@ -458,6 +464,7 @@ export class CodingTeacherService { if (existing) { if ( existing.text !== input.text || + existing.modelId !== modelId || (scope.agentId && existing.projectId !== scope.projectId) || (existing.intent ?? 'question') !== intent || JSON.stringify(existing.references) !== JSON.stringify(refs) || @@ -485,6 +492,7 @@ export class CodingTeacherService { const previous = topic.requests.at(-1); const reuse = previous && !previous.origin && previous.projectId === scope.projectId && previous.sourceConversationId === input.sourceConversationId + && previous.modelId === modelId && previous.presentation === 'reply-v1' && previous.teacherVersion === selected.version && previous.runtimeThreadId; newConversationSegment = !reuse; @@ -551,6 +559,15 @@ export class CodingTeacherService { return structuredClone(ref); }); const isCloud = topic.definition.runtime === 'yuxi'; + if (modelId) { + const models = await (this.options.models ?? teacherModelCatalog)(account, isCloud ? 'yuxi' : 'local'); + if (!models.supported || !models.models.some(item => item.id === modelId)) + throw new TeacherError(422, 'teacher_model_unavailable', models.message ?? '这个模型暂不可用,请重新选择。'); + this.assertAccount(account); + } + const modelDefinition = modelId + ? { ...topic.definition, model: { model_id: modelId, reasoning_choice: { mode: 'default' as const } } } + : topic.definition; if (!isCloud && !topic.definition.system_prompt.trim()) throw new TeacherError(422, 'teacher_definition_invalid', '请先配置智能体的系统提示词。'); const access = scope.projectId === 'preview' ? undefined : { @@ -579,7 +596,7 @@ export class CodingTeacherService { topic.revision++; this.events.emit(key, structuredClone(topic)); }) - : await (this.options.prepareModel ?? prepareTeacherModel)(account, topic.definition, + : await (this.options.prepareModel ?? prepareTeacherModel)(account, modelDefinition, access, { finalOnly: structuredReply }); if (checkIn) this.assertAccount(account); @@ -620,6 +637,7 @@ export class CodingTeacherService { text: input.text, references, createdAt: new Date().toISOString(), + ...(modelId ? { modelId } : {}), sourceCursor: source.cursor, sourceCapturedAt: source.capturedAt, includedSourceMessageIds: compiled.includedSourceMessageIds, @@ -677,7 +695,8 @@ export class CodingTeacherService { current.response = ''; delete current.suggestedQuestions; } - topic.updatedAt = new Date().toISOString(); + current.finishedAt = new Date().toISOString(); + topic.updatedAt = current.finishedAt; topic.revision++; try { await store.save(topic, current.id); diff --git a/shared/coding-conversation-contracts.ts b/shared/coding-conversation-contracts.ts index 1e52995e..86a5aeee 100644 --- a/shared/coding-conversation-contracts.ts +++ b/shared/coding-conversation-contracts.ts @@ -142,6 +142,8 @@ export type ConversationContentBlock = export interface ConversationMessageNode { kind: 'message'; id: string; + /** Epoch ms: user send time / assistant completion time. Absent for undated history. */ + timestamp?: number; sourceEntryId?: string; clientRequestId?: string; role: 'user' | 'assistant'; diff --git a/shared/coding-teacher.ts b/shared/coding-teacher.ts index 5875c44c..1e4ed1fd 100644 --- a/shared/coding-teacher.ts +++ b/shared/coding-teacher.ts @@ -78,6 +78,8 @@ export interface TeacherRequest { projectId?: string; projectName?: string; teacherVersion?: number; + /** Explicit per-question model; absent follows the published teacher configuration. */ + modelId?: string; runtimeThreadId?: string; origin?: { projectId: string; topicId: string; requestId: string }; /** discussion-v1 is retained only to identify historical replies. */ @@ -103,6 +105,8 @@ export interface TeacherRequest { text: string; references: TeacherReference[]; createdAt: string; + /** Actual terminal time, absent in older history; never inferred from topic.updatedAt. */ + finishedAt?: string; sourceCursor: TeacherSourceContext['cursor']; sourceCapturedAt: string; includedSourceMessageIds: string[]; @@ -152,7 +156,13 @@ export interface TeacherTopicList { items: Array<{ id: string; title: string; updatedAt: string; version: number; teacherId?: string; legacyRole?: 'friend'; legacySourceId?: string }>; lastSelectedTopicId: string | null; } +export interface TeacherModelCatalog { + supported: boolean; + models: Array<{ id: string }>; + message?: string; +} export interface TeacherSend { + modelId?: string; projectId?: string; intent?: TeacherRequestIntent; sourceConversationId?: string; diff --git a/shared/message-timestamp.ts b/shared/message-timestamp.ts new file mode 100644 index 00000000..02e8d915 --- /dev/null +++ b/shared/message-timestamp.ts @@ -0,0 +1,7 @@ +/** Read only real, valid epoch milliseconds / ISO dates; never invent a history time. */ +export function messageTimestamp(value: unknown): number | undefined { + const timestamp = typeof value === 'number' ? value + : typeof value === 'string' && /^\d{4}-\d{2}-\d{2}T/.test(value) ? Date.parse(value) : NaN; + return Number.isFinite(timestamp) && timestamp >= 0 && !Number.isNaN(new Date(timestamp).getTime()) + ? timestamp : undefined; +} diff --git a/src/components/community/CommunityInvitation.tsx b/src/components/community/CommunityInvitation.tsx new file mode 100644 index 00000000..4faf365a --- /dev/null +++ b/src/components/community/CommunityInvitation.tsx @@ -0,0 +1,8 @@ +import { CommunityLink } from './CommunityLink'; + +export function CommunityInvitation() { + return
+

还没想到?去找找灵感吧

作品广场里,有小伙伴们用麦洛做出的奇思妙想。

+ +
; +} diff --git a/src/components/community/CommunityLink.tsx b/src/components/community/CommunityLink.tsx new file mode 100644 index 00000000..55c34eca --- /dev/null +++ b/src/components/community/CommunityLink.tsx @@ -0,0 +1,24 @@ +import { useState } from 'react'; +import { ArrowUpRight, Compass } from 'lucide-react'; +import { openCommunity } from '@/lib/community'; +import './community.css'; + +export function CommunityLink({ variant = 'inline', collapsed = false }: { + variant?: 'inline' | 'sidebar'; collapsed?: boolean; +}) { + const [opening, setOpening] = useState(false); + const [error, setError] = useState(false); + return
+ + {error && 没能打开作品广场,请再点一次试试。} +
; +} diff --git a/src/components/community/community.css b/src/components/community/community.css new file mode 100644 index 00000000..531c1018 --- /dev/null +++ b/src/components/community/community.css @@ -0,0 +1,37 @@ +.coding-chat-body { display: flex; flex: 1; flex-direction: column; min-height: 0; min-width: 0; overflow: hidden; } +.coding-chat-body[hidden] { display: none; } +.coding-chat-body[data-welcome="true"] { padding: 0 24px 16px; background: radial-gradient(ellipse at 50% 40%, #fff8e8 0%, #fffdf8 28%, #fff 67%); } +.coding-chat-body[data-welcome="true"] .classroom-chat-view { flex: 1 1 0; overflow-y: auto; padding: 24px 0; } +.coding-chat-body[data-welcome="true"] .classroom-composer-view { width: 100%; max-width: 740px; margin: 0 auto; padding-top: 16px; } +.coding-chat-body[data-welcome="true"] .classroom-composer-view > .community-invitation { margin: 0 auto 12px; } +.coding-chat-body[data-welcome="true"] [data-testid="coding-message-composer"] { width: 100%; padding: 0; } +.coding-chat-body[data-welcome="true"] .chat-composer-surface { background: #fff; border-color: #d7dfda; border-radius: 20px; box-shadow: 0 5px 0 #eaf0e8, 0 12px 32px #69774b08; } +.coding-start-heading { position: relative; flex-shrink: 0; width: 100%; max-width: 740px; margin: var(--teacher-top-inset, auto) auto auto; padding: 0 12px; text-align: center; } +.coding-start-mark { display: inline-grid; position: relative; place-items: center; width: 57px; height: 59px; border-radius: 18px; border: 1px solid #f2d6a7; background: #fff0d7; box-shadow: 0 4px 0 #eeddbd; transform: rotate(-7deg); animation: community-hello 650ms ease-out both; } +.coding-start-mark img { width: 35px; height: 35px; } +.coding-start-mark > svg { position: absolute; width: 17px; height: 17px; right: -24px; top: -2px; color: #c78b36; } +.coding-start-mark > svg:last-child { width: 12px; height: 12px; left: -23px; top: 39px; color: #83a496; } +.coding-start-eyebrow { margin: 18px 0 7px; color: #7a7769; font-size: 12px; letter-spacing: .08em; } +.coding-start-heading h1 { color: #304968; font-size: clamp(24px, 2.5vw, 34px); line-height: 1.45; font-weight: 650; letter-spacing: -.04em; } +.coding-start-heading h1 span { position: relative; white-space: nowrap; background: linear-gradient(transparent 73%, #ffe6a6 73%, #ffe6a6 94%, transparent 94%); } +.coding-start-caption { margin-top: 10px; color: #718096; font-size: 13px; line-height: 1.8; } +.community-invitation { display: flex; flex-shrink: 0; align-items: center; justify-content: space-between; gap: 18px; width: 100%; max-width: 740px; margin: 5px auto 0; padding: 21px 23px; border: 1px dashed #e4d9bc; border-radius: 18px; background: #fffaf0; color: #304968; } +.community-invitation h2 { margin-bottom: 5px; font-size: 15px; font-weight: 600; } +.community-invitation p { color: #7c806e; font-size: 12px; line-height: 1.7; } +.community-link { display: inline-flex; align-items: center; justify-content: center; gap: 7px; min-height: 40px; padding: 8px 13px; border: 1px solid #dce5d0; border-radius: 12px; background: #f0f5e8; color: #526b46; font-size: 13px; cursor: pointer; white-space: nowrap; } +.community-link:hover:not(:disabled) { background: #e5eed9; border-color: #b7c6a4; } +.community-link:disabled { opacity: .6; cursor: wait; } +.community-link:focus-visible { outline: 2px solid #547ab0; outline-offset: 3px; } +.community-link svg { width: 17px; height: 17px; flex-shrink: 0; } +.community-link .community-link-arrow { width: 14px; height: 14px; } +.community-link[data-variant="sidebar"] { justify-content: flex-start; width: 100%; margin-top: 12px; border-color: transparent; background: transparent; text-align: left; } +.community-link[data-variant="sidebar"]:hover { background: #e8f0e7; } +.community-link[data-collapsed="true"] { justify-content: center; padding-inline: 0; } +.community-link-error { display: block; margin-top: 6px; color: #965a45; font-size: 12px; line-height: 1.6; } +@keyframes community-hello { 0% { transform: rotate(-15deg) translateY(5px); opacity: 0; } 70% { transform: rotate(3deg); opacity: 1; } 100% { transform: rotate(-7deg); } } +@media (max-width: 600px) { + .coding-chat-body[data-welcome="true"] { padding: 0 16px 16px; } + .coding-start-caption { max-width: 230px; margin: 9px auto 0; } + .community-invitation { flex-direction: column; align-items: flex-start; gap: 13px; padding: 19px; } +} +@media (prefers-reduced-motion: reduce) { .coding-start-mark { animation: none; } } diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx index 1a59ef0a..730bb70d 100644 --- a/src/components/layout/Sidebar.tsx +++ b/src/components/layout/Sidebar.tsx @@ -53,6 +53,7 @@ import { useProviderStore } from '@/stores/providers'; import type { CodingProjectSummary } from '@/types/coding-project'; import { useProjectConfigStore } from '@/stores/project-config'; import { toast } from 'sonner'; +import { CommunityLink } from '@/components/community/CommunityLink'; type OpenDialogResult = { canceled: boolean; @@ -435,6 +436,7 @@ export function Sidebar({ {!sidebarCollapsed && '新建项目'} +
+