Commit Graph

8959 Commits

Author SHA1 Message Date
dolphin b608efd6b8 fix(chat): render daily-mode thinking and tool calls in true event order
The stream, the DB and the grouped event array all carry a truthful
interleaving — the backend closes the open thinking segment before every tool
call precisely so each ReAct round can stand on its own. The final render then
threw that away: two filter() calls merged every thinking fragment into one
block at the top and dropped all tool rows below it, so reasoning that happened
after a search read as if it happened before.

Replace the filters with an in-order walk that folds only ADJACENT thinking
fragments (task mode's DeepStepGroup algorithm), leaving each tool call where
it occurred; the rail connector now follows "is there a next row" regardless of
kind. Adjacent rounds join with a paragraph break — they are complete passages,
not per-frame chunks. A just-opened live segment with no text yet is skipped so
the connector never points at a row that renders nothing. In-place completion
updates and collapse/reopen were already order-safe; backend untouched.
2026-07-31 18:53:13 +08:00
Kinyoo 2901c03087 style(knowledge): solid blue-50 for the selected file card and row
The 7% brand-alpha tint let the white below bleed through, reading a
touch dimmer than the design's selection fill. blue-50 is the same hue
as an opaque swatch — still a brand token, so it keeps following the
blue⇄green theme. Same change as cofco's fbcd7adc8, applied to the
main line.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 16:25:03 +08:00
dolphin 9d321aa495 fix(chat): keep the newest knowledge selection leftmost after a reload
The attachment row orders chips by when each one first appeared, newest
leftmost. Knowledge selections are stored newest-first (the picker prepends)
while the file arrays are oldest-first, so when a whole set first appeared in
one frame — restoring a conversation from localStorage — the newest space was
handed the lowest sequence and the row came back reversed. Picking spaces one
at a time had always been right, which is why the two disagreed.

Feeding the knowledge array in oldest-first makes the sequence mean the same
thing for every source. File and skill chips are untouched.
2026-07-31 15:12:19 +08:00
Kinyoo 3b4be57eea chore(scripts): converge media files ingested with the ASR JSON envelope
One-off cleanup for rows the previous commit's bug left behind: media
knowledgefile rows marked SUCCESS whose stored transcript is empty or
the raw ASR response envelope.

The script scans media-extension SUCCESS rows, downloads each file's
MinIO transcript preview (preview/{id}.md) to judge the ingested text,
and converges hits to the same terminal state as every other
unrecognizable-audio case: Milvus/ES vectors deleted (MinIO source file
kept for re-parse/download), status=FAILED, remark=10956 error json —
the client then shows the 失败 badge with reason and stops the row
being clickable. Dry-run by default, --apply to write, scopable via
--knowledge-id / --file-id; idempotent since converged rows drop out of
the SUCCESS selection.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 15:05:58 +08:00
Kinyoo 2cbd49835b fix(knowledge): never store the raw ASR envelope as a media transcript
When Aliyun ASR answered 200 with no recognized sentences
(output: null), the fallback did str(result) — stringifying the whole
RecognitionResult — so the file was ingested with
'{"status_code": 200, "request_id": ...}' as its transcript and marked
SUCCESS. Silent media looked successfully transcribed, with JSON
garbage as the recognized text.

The fallback now only accepts an explicit plain-text field on the
output payload; with nothing there it returns no segments, which flows
into the existing no-recognizable-audio failure (10956) like every
other silent-media case. Tests cover both the envelope case and the
legitimate output.text fallback.

Existing dirty rows are converged by a follow-up script (next commit).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 15:05:45 +08:00
Kinyoo 489787f640 fix(knowledge): stop sidebar sections jumping when one collapses
The empty-state stretch logic made created/joined sections flex-grow
whenever EITHER was empty, so collapsing or filling one section moved
its neighbours around. Now the list always fills its scroll viewport
and only the LAST section (joined) absorbs the leftover height:

- Upper sections keep a fixed 96px empty-state slot, so nothing above
  ever shifts — collapsing only pulls the content below upwards.
- Joined keeps flex-1 with its automatic min-height, so a long list
  still grows past the viewport and scrolls instead of being squeezed.
- The three duplicated empty-state blocks fold into one renderEmptyState.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:13:15 +08:00
Kinyoo 4a8f7015d9 feat(workbench): follow the configured menu names inside each module
1bb87ba22 renamed the sidebar entries but each module still greeted the
user with its fixed i18n title — configure 订阅 as "资讯" and the page
banner still shouted 订阅. The in-module surfaces now read from
useWorkbenchMenuNames too: the subscription banner/switcher/sidebar
headings and dialog aria-labels, the app-center page title and mobile
header, and the knowledge-space sidebar/drawer titles.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 10:13:05 +08:00
Kinyoo 1bb87ba220 feat(workbench): admin-configurable names for the sidebar menu entries
The four client workbench entries (首页 / 知识空间 / 订阅 / 应用) were
fixed i18n strings; deployments wanting their own wording had no knob.
Each module's platform config tab (构建 → 工作台) now carries a required
菜单显示名称 field, and the client sidebar renders whatever was saved.

- Backend: home/app-center names ride on WorkstationConfig (they share
  the daily config record); knowledge-space and subscription get a
  menu_display_name on their own config models, surfaced through
  /workstation/config alongside assistant_name.
- Platform: shared clampMenuName truncates as you type by DISPLAY width
  (full-width chars count 2, cap 8 units — 4 CJK chars or 8 latin),
  since the sidebar column is 64px at 10px type; saving an emptied field
  is rejected rather than silently falling back.
- Client: new useWorkbenchMenuNames hook resolves each entry to the
  admin value or the localized default (blank = unconfigured, so
  deployments that never touch it still follow the UI language);
  consumed by the hub tabs, sidebar, mobile drawer and the
  menu-unavailable page.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 18:22:30 +08:00
dolphin a9d9ead028 feat(knowledge): keep the department category on the space page when it is empty
部门知识空间 only rendered once the user actually had one, while 我创建的 and
我加入的 always render and show 暂无数据 when empty. That made the category look
like a search result rather than part of how the page is organised — a user with
no department space had no way to tell the grouping existed at all.

It now behaves like its two siblings, in both the sidebar and the file-page title
dropdown. Auto-selecting the first department space still requires there to be
one, and the empty-section stretch stays with 我创建的 / 我加入的 so the existing
layout is unchanged.
2026-07-30 17:28:46 +08:00
dolphin 4da02b8789 feat(chat): group the knowledge space dropdown by where a space comes from
The picker merged 部门 / 我创建的 / 我加入的 into one flat A–Z list, so a user
could not tell where a space came from. It now shows the same three sections,
in the same order, as the 知识空间 page — grouping rules are not re-derived: a
new useCategorizedKnowledgeSpaces hook reuses that page's endpoints, query keys
and department-subtraction dedup, so the two surfaces cannot drift and opening
the picker after visiting the page serves from cache. A category with nothing to
show hides its title, which also covers search: hits stay under their own
section and sections with no hit disappear.

The list panel moves out of ChatKnowledge into its own file and grows a grouped
mode; organization knowledge bases keep the flat, server-paginated one.

Reaches daily chat and the task-mode landing (both render AiChatInput).
Selection, the 50-per-type cap and the chips above the input are untouched.
2026-07-30 15:38:06 +08:00
dolphin d371add282 fix(subscription): let an unchanged link be submitted again
Pressing Enter only set the search keyword, so re-submitting the same link left
every dependency identical and the effect that resolves it never re-ran: the
user was told to fix the link and press Enter, and pressing Enter did nothing
unless they actually edited it. The in-flight guard compounded it by keying on
the link itself rather than the attempt.

Enter now goes through a submit that counts attempts, and the guard keys on the
attempt — a resubmission is a retry, a re-render mid-flight still isn't. The
failure copy also clears on the keypress instead of when the debounced request
finally starts.

Both hint sentences stay on one line.
2026-07-29 21:50:45 +08:00
dolphin 0c6f87a8a9 feat(subscription): point an empty search at the WeChat link tip too
Searching for an unindexed account left the user with a two-line explanation
and nowhere to go, even though the very next thing they need — where to copy an
article link from — was already one hover away on the failure screen.

The hint component now takes its own copy, so both moments share the tooltip
while wording the advice for their own situation, and the empty-search copy
collapses to a single line naming the article link as the way forward.

Drops source_not_indexed_try_full_url, the fallback the old copy read from,
which no longer has a caller.
2026-07-29 21:36:39 +08:00
LineWalker 8f47b999c6 fix(chat): restore task mode when returning to a task conversation
Navigating to any existing conversation reset the task-mode toggle to
off, so leaving a task conversation and coming back dropped it (the
execution panel survives via its own by-SV hydration; only the toggle
was lost). Detect a task conversation from its loaded history — guarded
by the message's own conversationId so the stale previous-conversation
message list that lingers for one commit during the navigation
transition can't misfire — and re-enter task mode; a daily chat stays
off, and an explicit manual toggle-off within a visit is preserved.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
(cherry picked from commit 0327a05ebe88fbda261d689c1dcbdf7118e999d7)
2026-07-29 19:43:35 +08:00
Kinyoo b477f60b3e feat(subscription): guide the user back to a fixable WeChat link
When a pasted WeChat article URL cannot be resolved, the old flow popped a
hand-rolled "channel add failed / try again" dialog on top of the generic
red error toast, and left the panel with no next step.

Now a single acknowledge-only confirm ("back to edit") explains the failure,
and the processing panel swaps its copy for retry guidance: the highlighted
"WeChat link" phrase carries a tooltip with a screenshot of where to copy a
valid link from. The sentence ships as one i18n key with a {{link}}
placeholder so translators keep control of word order.

Supporting changes:
- ConfirmContext: hideCancel option for dead-end notices, plus a fixed h-8
  button height instead of h-auto + py.
- Tooltip2: arrowClassName so the arrow can follow a recoloured panel.
- addWechatSourceApi: showError off, the caller owns the error UI now.
- packages/ui docs: document the hideCancel variant.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 19:09:05 +08:00
dolphin a72125aab3 fix(chat): render an image from the link the client already has
A message the user just sent isn't in the database yet, so asking the backend
to re-issue a link came back empty — workflow chat showed "no longer
available", task mode sat in its skeleton — and both only came right after a
refresh. The upload already handed the client a working link, so that renders
immediately and the re-issued one takes over when it arrives, which is what an
older conversation needs.

Stored links carry the internal storage host, so they get the same rewrite the
download card does before being handed to the browser.
2026-07-29 17:32:33 +08:00
dolphin 223d72d2c0 fix(workflow-chat): stop the history mapper from dropping attachment fields
History rebuilt each attachment as just a name and a url, so the id an image
is resolved by — and the object name behind it — never reached the bubble: a
refreshed conversation showed the picture stuck loading forever. It now spreads
the stored entry instead of listing fields, which also keeps whatever the
backend adds next.
2026-07-29 17:32:33 +08:00
dolphin 4a661b114e fix(workflow-chat): parse the attachments the history API returns as a string
Live sends hand the bubble an array, but history returns what the message row
holds — a JSON string — so every field read off it came back undefined and the
image sat in its skeleton after a refresh. Both shapes are accepted now.
2026-07-29 17:32:33 +08:00
dolphin 688ea3fc8a fix(chat): actually promote workflow-chat attachments
Two reasons a workflow-chat image resolved to nothing, both silent:

The promotion ran under `if not chat_response.is_bot`, but that field defaults
to True, so the user's own question — the only message this was meant for —
was the one case it skipped. Keyed on the category instead.

And it looked for the upload link under `filepath`/`file_path` only, while
workflow chat carries it as `file_url`, so even when it did run there was
nothing to copy. All three spellings are accepted now.

Refs: features/v2.6.0/043-chat-file-permanent-storage
2026-07-29 17:32:33 +08:00
Kinyoo ffc79496f5 style(chat): restyle message image thumbnails and their expired placeholder
Square the attached-image thumbnail at 100x100 (was 160x120) and give it a
fixed light-gray backdrop so a transparent PNG still shows its artwork —
fixed rather than a neutral token, since the artwork inside the file does
not invert with the theme.

The expired placeholder follows the thumbnail it stands in for: same size,
same tint, no border, lighter icon and label, and the Chinese copy breaks
where it reads best instead of wherever 100px happens to fall.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-29 16:11:22 +08:00
dolphin 1584a79365 feat(workflow-chat): keep uploaded attachments as data on the message
Workflow chat never carried its attachments as data — filenames were glued
onto the question text and the message's files array stayed empty, which is
why images rendered as a line of text no matter which component drew them.

Attachments now travel alongside the existing dialog_files_content the input
node reads: the client sends them structured, the callback stores them on the
question message, and the bubble renders images as images. Because the message
holds them, they also survive a refresh and go through attachment promotion
like the other chat modes.

The names stay prepended on the way to the workflow node, which still relies on
them; the bubble drops them from the displayed text so they aren't shown twice.
User attachments also stop being routed to MessageFile, which is for files a
run produced.
2026-07-29 12:23:46 +08:00
dolphin 3a9f6fc324 fix(chat): stop flashing the expired-image placeholder, and keep file ids
A just-sent image showed "image is no longer available" for a moment before
appearing: the component treated a missing conversation id as a dead file, but
a message that hasn't been persisted yet simply doesn't have one. Absent ids
now mean "still waiting" and hold the skeleton.

Workflow chat rendered every attachment as a plain filename. Its socket handler
rebuilds each file with only name and url, dropping the id — which is what an
image is looked up by when its link is re-issued. The id is carried through now.

The resolved link and the ids it came from are also exposed as data attributes,
so a picture that fails to render can be traced from devtools.
2026-07-29 12:09:05 +08:00
dolphin f545bea5cb Merge branch 'feat/2.6.0-ui' into feat/2.6.0 2026-07-29 10:48:21 +08:00
Kinyoo 84e7950723 style(knowledge): render transcript timestamps as their own labels
Parse the transcript into timestamp/text cues so the time reads as a caption-sized label above each line instead of running inline with the sentence; lines without a leading timestamp continue the cue above, and text with no cue at all falls back to plain markdown. Also align the segmented control with the transcript inset, clear the AI dock with bottom padding, and round the player to 12px.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-29 09:44:10 +08:00
dolphin a93519cf75 merge: F043 会话上传文件永久化 + 对话图片展示 2026-07-28 23:26:47 +08:00
dolphin ae362a32ad feat(chat): show uploaded images in the conversation
Attachments that are pictures now render as thumbnails that open full screen,
in daily, task and workflow chats alike — until now every attachment was a
filename or a download card, so users could not tell what they had sent.

The link is fetched fresh at render rather than reusing the one stored on the
message, which was signed at upload time and has expired on any older
conversation. When the file genuinely can't be retrieved — cleared storage, or
a message from before attachments were kept — a placeholder says so, instead
of a broken image the user would take for a loading failure.

Whether an attachment is an image is decided from its filename: older messages
carry no MIME type and the upload endpoints disagree on where they put it,
while the name is always there and already drives the file icon.

Refs: features/v2.6.0/043-chat-file-permanent-storage (T010-T013)
2026-07-28 23:25:30 +08:00
dolphin abb08bc71d feat(chat): hand out fresh attachment links and clean up on delete
Links issued at upload time expire, so the client now asks for a new one when
it renders an attachment. Which object gets signed is decided from the
conversation's own messages and never from anything the caller sends — an
endpoint that signed a caller-supplied object name would hand out the whole
bucket to anyone with an account. Ownership of the conversation is the entire
authorization story; attachments recorded before this feature have no object
name and read as gone rather than falling back to a guess.

Deleting a conversation now drops its files. The delete is soft with no way
back, so nothing is left to read them. Cleanup is best-effort: a storage
outage must not leave the user with a conversation that refuses to disappear.

Refs: features/v2.6.0/043-chat-file-permanent-storage (T007-T009)
2026-07-28 23:13:47 +08:00
dolphin 605a5787ad feat(chat): make conversation attachments permanent when the message is sent
Wires promotion into the four places a chat message is persisted, so files
stop vanishing with the temp bucket's 3-day sweep.

Daily chat now uploads through the shared endpoint instead of its own: that one
already names objects by uuid, which removes the collision where two users
uploading "1.png" overwrote each other and could see each other's file. The old
endpoint stays in place for any caller we don't know about.

Workflow chat promotes only the user's own attachments — what the workflow
itself produced is a separate concern. Task mode turned out to be a third
architecture entirely (uploads land on local disk and only reach object storage
at execution time), but its ingestion already persists original image bytes for
the workspace preview, so the conversation just records that object name.

Promotion is best-effort throughout: unreachable storage or a failed copy costs
a thumbnail, never the message.

Refs: features/v2.6.0/043-chat-file-permanent-storage (T003-T006)
2026-07-28 23:05:24 +08:00
dolphin 06dcf919f1 feat(storage): promote chat attachments on send instead of on upload
Chat uploads land in a temp bucket that wipes itself every 3 days, so a
conversation's files quietly disappeared. Rather than making every upload
endpoint write somewhere permanent, the message-send path now copies its
attachments into the main bucket and records the permanent object name.

Promoting on send keeps the shared /knowledge/upload endpoint — used by the
knowledge base, dataset creation and the API examples — completely untouched,
and abandoned uploads need no cleanup machinery of their own: a file that is
never sent stays in temp and expires there.

The copy is server-side, files that are already permanent are skipped so
task-mode uploads flow through unchanged, and one failed attachment neither
blocks the message nor the other files.

Refs: features/v2.6.0/043-chat-file-permanent-storage (T001-T002)
2026-07-28 22:33:29 +08:00
dolphin c7d2ef6b14 fix(storage): name attachments by uploader, not by conversation
The design had attachments under chat/<chat_id>/ so deleting a conversation
could sweep one prefix. That can't work: the first upload of a new chat happens
before the chat exists — the client still holds "new" and the backend assigns
the id when the message is sent — so there is no conversation id at naming
time. Group by uploader instead and let deletion collect object names from the
conversation's messages, which already carry them.

Also records that /knowledge/upload is shared with the knowledge base, QA
import and dataset flows, so the workflow-chat path needs its own endpoint
rather than a change to that one.

Refs: features/v2.6.0/043-chat-file-permanent-storage design §3 decision 1, §5 pits 8-9
2026-07-28 22:08:28 +08:00
dolphin fd7bff2341 feat(storage): conversation-scoped object names and prefix deletion
Groundwork for making chat attachments outlive the 3-day temp bucket. Two
pieces, both with the same motivation: files are about to stop expiring, so
naming and cleanup have to be right before that happens.

Object names become chat/<chat_id>/<uuid><ext>. Daily-mode uploads were stored
under the raw filename, so two users uploading "1.png" overwrote each other —
invisible while the bucket was wiped every 3 days, a permanent data leak once
it isn't. Only the suffix is ever read from the client-supplied name, so a
filename can't steer the object elsewhere in the bucket.

Prefix deletion is what lets deleting a conversation drop its files in one
sweep. It keeps going when an individual object fails — one unreachable file
must not strand the rest — and refuses a prefix that would match the whole
bucket, since no caller wants that and the blast radius is everything.

Refs: features/v2.6.0/043-chat-file-permanent-storage (T001-T002)
2026-07-28 22:03:54 +08:00
Kinyoo 5af8ba1dd3 docs(ui): add the dash-scaling figure and a no-mask drawing rule
Icon spec: new FigDash figure showing why dash/gap must be set at the 24px display size instead of reusing large-size values; add the 'no mask' drawing rule (mask/clipPath need unique ids that collide when icons are inlined together, and they interfere with currentColor); tidy FigRhythm shapes and the §3 wording.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 17:47:36 +08:00
Kinyoo 4ea84ff240 feat(knowledge): custom-skinned media player for file preview
Replace the browser-native audio/video controls (whose icons and overflow menu can't be styled) with a hand-rolled control bar over the standard HTMLMediaElement API: play/pause, seek, volume, playback rate, PiP, fullscreen and download. Ships zh-Hans/en/ja copy for every control, and bumps bisheng-icons to 0.2.28 for the Filled/Outlined glyphs it uses.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 17:47:36 +08:00
Kinyoo dfbd390638 fix(knowledge): drop duplicate MEDIA_FILE_EXTENSIONS from the merge
The feat/2.6.0 merge brought in its own MEDIA_FILE_EXTENSIONS while 146c7c5ab had added an equivalent one lower in the file; git took both, so esbuild failed with 'Multiple exports with the same name'. Keep the original position (ALLOWED_EXTENSIONS uses it above) and move AUDIO_/VIDEO_FILE_EXTENSIONS up beside it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 11:58:01 +08:00
dolphin 6fc45fc13a Merge branch 'feat/2.6.0' into feat/2.6.0-ui 2026-07-28 11:50:13 +08:00
Kinyoo 146c7c5ab2 feat(knowledge): audio/video line-art icons in table + move dialog
Bump bisheng-icons to 0.2.24 for Outlined.FileAudio / FileVideo, then use them: FileTable's name cell gains a renderRowFileIcon helper (audio / video / image / generic) and MoveToDialog's right-panel glyph matches. Extract AUDIO_FILE_EXTENSIONS / VIDEO_FILE_EXTENSIONS in knowledgeUtils so both call sites and MEDIA_FILE_EXTENSIONS share one list.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 11:45:14 +08:00
Kinyoo 949c591d80 feat(knowledge): dedicated file icons for html / audio / video
Split the catch-all 'media' key into 'audio' (FileAudio, orange #FF9908) and 'video' (FileVideo, purple #A140FF), and add 'html' (FileHtml, sharing the txt slate-grey palette) — each with its own gradient backdrop and extension mapping. Web-link files now render FileHtml instead of the md icon, and html joins the no-thumbnail text formats.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 11:26:43 +08:00
Kinyoo ab35880750 build(client): bump bisheng-icons to 0.2.23
Bump both declarations (client package.json + the pnpm-workspace catalog used by @bisheng/ui) and the lockfile. Remember to clear node_modules/.vite after pulling — its git-source exports defeat Vite's dep-change detection.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-28 11:18:17 +08:00
Kinyoo 815c19ce21 feat(knowledge): enable media & web-link preview + revamp media layout
Flip the media / webLink upload capability switches on; RichKnowledgePreview media view is now side-by-side (player left, transcript right, single divider), the transcript tabs become a blue-500 segmented control, and the shared MarkdownBody is extracted from MarkdownBlock.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 18:26:09 +08:00
Kinyoo e187ac265a style(client): narrow sidebar tabs use icon over label, active state blue-500 2026-07-27 15:37:50 +08:00
Kinyoo 96c526c55b style(client): align ChatKnowledge active state to blue-500
Org-KB / task-mode / add-skill / feedback-check active colors switch from blue-600 to the blue-500 brand token, matching the other input-bar selectors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 14:44:45 +08:00
Kinyoo 35c5937a76 style(client): swap task-mode icon from Binoculars to ListSuccess
Task-mode entry points (PlusMenu, TaskModeToggle, NewChat new-task button, ChatKnowledge) now use Outlined.ListSuccess instead of Outlined.Binoculars; colors/classes unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 14:29:21 +08:00
Kinyoo fef1d28c54 fix(client): don't JSON.parse the plain-string fontSize (crashes ThemeProvider)
localStorage 'fontSize' is written as a plain class string ('text-base'), but startup read it back with JSON.parse — JSON.parse('text-base') throws 'Unexpected token e', crashing ThemeProvider and blanking the whole app on any load where the value is present (StrictMode double-invoke in dev, or any second visit in prod). Read defensively: accept a plain string and tolerate a legacy JSON-encoded value, never throw. Pre-existing bug (2025-04), also on feat/2.6.0-beta4.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-27 13:48:25 +08:00
dolphin 36beaa00f0 refactor(frontend)!: remove the legacy skill module
Skill (flow_type=1) creation was already disabled and backend list APIs
hard-exclude the type; only deep links remained. Removed:

- platform: skill editor (BuildPage/skills, 16 files), legacy node library
  (CustomNodes), version-diff page (DiffFlowPage + diffFlowStore), skill chat
  page family (ChatAppPage index/share/webview + ChatPanne etc.), all skill
  routes (/build/skill, /skill/:id, /chat, /diff) and app-list branches
  (edit/copy/online/create/typeMaps/SKILL enum members)
- client: dead skill branches + useBuild SSE in appChat; 404 guard moved
  before permission/cache short-circuits so cached deep links redirect too
- shared components rescued out of deleted dirs: CreateTemp (save-as-template,
  + null-flow guard), CardSelectVersion (typed), FileViewPanne (kept for
  ResouceModal); their 10 legacy lint violations fixed, 2 hardcoded Chinese
  strings extracted to i18n (3 languages)
- 42 dead i18n keys deleted x3 languages (skills.* + superseded ExcelPreview
  keys); scanner now excludes generated shared.json artifacts
- ws-protocol skill branches in client chat internals stay for the #22 chat
  consolidation; backend skill APIs untouched (separate scheduling)

Net: ~11k lines removed. Both apps lint/typecheck/build green.
2026-07-24 18:30:33 +08:00
dolphin 4bf778002a fix(platform): declare phantom deps exposed by clean install
- react-resizable declared (imported directly, was riding react-grid-layout)
- speechToText: drop redundant PropTypes runtime validation (undeclared
  prop-types, same fix as client twin)
2026-07-24 16:13:22 +08:00
dolphin 6137f641fe refactor(platform): stores no longer call HTTP — C7 compliance (ledger #23)
- orgSyncStore deleted (single-page state, moved into the OrgSync component)
- assistantStore: loadAssistantState (fetch+set) -> pure setAssistantDetail;
  4 call sites fetch via getAssistantDetailApi, bare calls now wrapped with
  captureAndAlertRequestErrorHoc
- diffFlowStore: version fetching + SSE compare orchestration extracted to
  pages/DiffFlowPage/useDiffFlowRun; store keeps pure state transitions
- DiffFlowPage component renamed from lowercase `index` (clears 8 suppressed
  rules-of-hooks violations); editAssistant imports useLocation from
  react-router-dom (transitive react-router copy broke Router context)
2026-07-24 16:13:22 +08:00
dolphin ed97ddeeaf perf(client): route-level code splitting (ledger #27)
Only the primary landing path (login + main layout + chat home) stays in the
entry chunk; 13 pages (knowledge, subscription, linsight, apps, share,
standalone chat, previews, ...) now lazy-load on first navigation with a
route-level Suspense fallback. Entry main.js: 1.6MB -> 959KB.
2026-07-24 14:25:09 +08:00
dolphin 3790392dff fix(frontend): generic fallback at every error-copy exit (ledger #11)
- new api_errors `fallback` key (3 languages); naked i18n keys can no longer
  reach users when a code has no translation (gateway codes, pre-CI legacy)
- client (6 exits): SSE/WS/chat-input/route/request-layer fall back to generic
  Chinese; t(key, vars) kept so {{xxx}} templates still interpolate
- platform (5 exits): fall back status_message -> generic copy (admin keeps
  backend diagnostics); chatErrorMessage's literal "error" fallback removed
2026-07-23 22:37:51 +08:00
dolphin 8f41e6a017 refactor(frontend): extract ExcelPreview into shared @bisheng/file-viewers (ledger #16)
- new strict-TS zero-suppression package; xlsx/xlsx-populate consolidated here;
  based on the newer client variant (dead screen-size code + debug logs removed)
- copy moves to packages/locales new `shared` domain (15 keys x 3 languages),
  addressed uniformly as shared:knowledge.excelPreview.* on both apps; fixes
  client's broken t() stub (labels rendered empty) and platform's missing
  excelParseFailed translation
- both call sites switched; old duplicates deleted; suppressions pruned
- main/types added to workspace packages (client tsc uses node10 resolution
  and ignores exports; @bisheng/ui was silently unresolved)
- platform knowledge preview layout: bounded-height chain fixed on upload
  step 3 (flex fill instead of hardcoded 100vh offsets), padding aligned
  between preview and paragraph columns, fixed footer clearance
2026-07-23 22:12:43 +08:00
Kinyoo 967b0f0f12 chore(docs): even 50/50 columns for / comparison tables
Auto table layout sized comparison columns by content, so the longer example took the wider column and the two peers read lopsided. Force table-layout:fixed for two-column tables whose body holds plain prose only — label+spec tables (which put code/strong in a long right column) still need content-based sizing and are excluded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 17:46:13 +08:00
Kinyoo 680191e77f docs(ui): add the audience/hidden-zone model to the authoring spec + conform docs
元-文档撰写规范 gains §0 (who each doc is for: designers/PMs, collaborating devs and their agents, the designer's own agent — hence a reader-facing part and an agent-facing hidden zone; technical detail, ledgers and decision history belong in the hidden zone). Conform the affected docs: 文案规范 drops the half-width-digits rule and renumbers, 滚动条规范 switches to Arabic section numbers and marks its leftover-exceptions/acceptance sections site-hide.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 17:46:13 +08:00