Conversation
- Add src/locales/zh-Hans/translation.json covering the full en key set (terminology aligned with the LibreChat main zh-Hans translation) - Register zh-Hans in i18n init with LibreChat-style locale normalization and detection (stored choice > navigator, alias mapping zh/zh-CN/zh-SG), replacing the browser-languagedetector whose nonExplicitSupportedLngs handling rejected zh-Hans - Render the SSR loading shell in the default locale deterministically and suppress the expected hydration text mismatch - Guard en/zh key-set and placeholder parity plus language switching in locales.test.ts
- Add a language row with a Select dropdown (English / 中文(简体)) below the theme row, reusing the existing click-ui Select component - Persist the choice via i18nextLng (the detection cache key) and keep document.documentElement.lang in sync
- CreateUserDialog role select rendered raw SystemRoles values (USER/ADMIN); use localized labels instead - Audit log target-type filter rendered raw PrincipalType enum values; map them to localized labels
New /tools route lists the full Tool Gateway catalog (disabled included) via the LibreChat proxy /api/terravox/tools/all: tool id, version, exposure fronts, allowed groups, renderer and enable state, with search and zh-Hans/en localization.
- Add GiteaImportDialog: owner/repo dropdowns from the anonymous repo listing, check report (error blocks, warn allows continuing), prefill into the edit dialog; manual-create secondary path - ToolEditDialog: desktop execution now registers owner/repo distribution (tool.json fields as optional fallback), prefill support - Desktop manifest assembly uses the distribution form (catalog_tool_id contract change); owner+repo required, sha256 format validated - Tool create/update toasts; com_tools_gitea_* / dist field locales
Misclicks on the overlay discarded filled-in forms. All admin-panel dialogs now ignore pointer-downs outside the content; explicit close, cancel buttons and Escape still close.
Pending updates governance (contracts 2.8.0): repo releases ahead of the approved manifest are hidden from users until an admin confirms them. - getPendingUpdatesFn + pendingUpdatesQueryOptions (60s poll, aligned with the gateway's resolver cache TTL) - ToolCatalogTab: amber pending section above the table; Review & confirm runs the existing Gitea check endpoint, prefills the edit dialog via buildPrefill with repo content fields while governance fields (tool_id/expose/allowed_groups/enabled) stay as approved — submitting the dialog is the existing PUT confirm - export buildPrefill from GiteaImportDialog (shared by import + update) - com_tools_pending_* locale keys (en/zh-Hans) - vitest: panel render/empty, confirm merge, check failure, query failure, catalog loading
…ds repo omits Confirming a pending update prefilled the dialog from tool.json@tag, so a stale tool.json version clobbered the release-tag version and the pending row looped forever. buildUpdateMerge now: - sets version (top-level + distribution) from the pending target (tag); the tag is the distribution identity resolver/Toolhost install by - keeps approved values for anything the repo does not provide (parameters/form/result/dangerous/description/display_name/sha256/ launcher/runtime) instead of overwriting with undefined/fallbacks - keeps governance fields (tool_id/expose/allowed_groups/enabled) Check results are now surfaced in the pending panel: hard errors block the dialog with a notice; warns (e.g. version_mismatch) show as a notice. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
…o error) Gateway check_repo returns version_mismatch as a hard error when tool.json@tag carries a version that differs from the tag, so the confirm flow blocks with the reason instead of opening a dialog for a version the Toolhost would refuse to install. Missing version stays a warn (manifest fallback at install). 🤖 Generated with [Claude Code](https://claude.com/claude-code)
- server fns: paged GET + full export (200/page, 10k cap) against the gateway admin run-reports endpoint, zod-parsed like the rest - /usage route, sidebar entry, en/zh-Hans locales (parity-tested) - AuditLogTab pattern reuse: DatePickerCell extracted to shared - CSV built client-side with UTF-8 BOM + RFC 4180 escaping
toolsQueryOptions is a static queryOptions object in the server module, but UsagePage invoked it as a factory (toolsQueryOptions()), throwing "is not a function" in the production bundle. The spec's @/server mock shaped it as a function, which is why tests stayed green — the mock now mirrors the real export shape.
- ToolEditDialog: exec kind extends to plugin/web; web gets a URL field, plugin gets host_launcher + host-root candidates + install/ uninstall script fields; desktop distribution block shared - GiteaImportDialog: import-kind selector — desktop/plugin run the existing check flow (plugin prefill carries host fields from tool.json), web skips the check and takes a required URL with the repo as optional name/description prefill - locales +13 keys en/zh-Hans (parity-checked); 859 tests green
…oup visibility); tool editor gains display_group/help_url/usage_stats; dashboard quick links
…alls -> IntegrityCheckFailed); bun's cache persists across loop rounds so retries converge
…ded); groups dimension unified on display_group — catalog rows get an inline group <select>, listing sections by group, edit dialog gains a datalist of existing groups; /usage redirects to /tools?tab=usage
…d by the tool's group, 2.17.0); group editor gains allowed-groups chips; catalog group options come from the registered group list; dashboard drops the usage quick link
…l style as the edit dialog; commit on blur/Enter)
…; sub stays as the secondary line
…ith a basemap badge
…services, per-datasource binding) and map_name field on map bindings
… shows tool names; binding edit = single full-URL field with list-maps/datasources buttons and multi-select basemap group
- ServicesPage: tab 系统(服务绑定/底图管理),底图管理挂 BasemapsManager - BasemapsManager(新):当前底图组列表(序/名/URL/移除)+ 从已绑定地图服务 多选地图加入组(组内原序保留、新增追加;搜索框筛选) - ServiceEditDialog: 移除底图设置区(底图归底图管理标签) - DiscoverDialog: 探测结果列表加搜索框(结果为空时不渲染) - ToolCatalogTab/ToolEditDialog: 暴露面(ui/mcp)改为工具清单行内即时开关, 编辑对话框移除「暴露给」区块 - locales: com_services_tab_*、com_basemaps_* 新增
搜索框此前只过滤显示,点绑定时仍遍历全部预勾选服务——过滤后绑定 会把被隐藏的服务一并绑入(2026-09-30 误绑 49 服务事故根因)。
替换原 select 下拉 + 单列表:grid 双列,左列服务行点击选中(选中态高亮, 切换清空本服务勾选),右列该服务地图多选勾选;两列各自搜索框。i18n 增 pick_service/pick_layer/pick_layer_of/search 四 key(en/zh-Hans)
表格行内分组单元格与编辑对话框展示分组均改为 SelectField 同款 click-ui Select;「未分组」以哨兵项表示(选回即清空分组);移除 datalist 自由文本入口(新分组经分组页管理)
- ParamBuilder:参数卡片(折叠行头:拖拽把手+名称+类型徽章+必填+删除; 展开态:参数名(可改名)/类型/标题/占位/描述/默认值(按类型)/控件类型/ 枚举选项/静态选项与动态 choices_from) - 双向绑定:可视化编辑即时 serialize 回 parametersJson+formJson; 外部 JSON 变化反向 parse;round-trip 保真(未知键 raw/rawHint 原样保留); JSON 语法错误锁定可视化并显示错误 - 添加参数 ▾ 预设菜单:字符串/数字/整数/布尔/多选数组/多行文本/日期/ 日期时间/文件/目录/云盘文件/隐藏 - 拖拽排序写 form.order;i18n 40 key(en/zh-Hans)
自定义 portal 菜单在弹窗内反复踩坑(裁剪/透明/坐标包含块),换组件库 官方 Dropdown:Trigger asChild 包 Button,Content 由 Radix 自动定位与 portal,Item onSelect 即添加;移除手写定位/配色状态
…加);菜单 hover 改 CSS 类
选中 chip 高亮(accent 边框+计数反色),列表按所选类型过滤; 新增 com_services_type_all locale key(en/zh-Hans)
- 新建绑定默认 iserver_map(不再默认数据服务) - 切换类型即时清空另一类型字段(地图清 datasource、数据清 mapName) - 提交按类型归一剥离无关字段(空串覆盖旧值),修复数据服务改地图服务 时旧数据源残留被网关 400 拒绝保存
- 手写方框 tab 栏换 click-ui Tabs(工具管理等设置页同款下划线风格) - 底图管理双列表搜索框换共享 SearchInput(SearchField 同款)
- 新增 shared/CheckIndicator:去重 BasemapsManager/DiscoverDialog 各自私有的勾选指示 - DiscoverDialog:补 modal-frost(唯一缺失的弹窗)、手写按钮→click-ui Button、原生搜索框→SearchInput - ServiceEditDialog:enabled 原生 checkbox→click-ui Switch(对齐 ToolEditDialog 标志位) - ToolEditDialog:4 处原生 select→SelectField(前缀/执行类型/handler/运行时,空值走 __none__ 哨兵) - ToolCatalogTab:暴露面原生 checkbox→click-ui Checkbox;修正 datalist 陈旧注释;去无用 useEffect 导入 - ParamBuilder:required checkbox→Checkbox、静态/动态选项 radio→RadioGroup、aria-label 本地化、inferWidget 死分支清理 - BasemapsManager:删除重复的 splitServiceUrl(复用 ServiceEditDialog 导出)、删除按钮→InlineAction - ServicesPage:类型筛选 chips 计数提升为普通常量,去掉 JSX 内 IIFE
- choices_from 解析 {tool,value_field?,label_field?} 对象(value/label 字段随行
保全);旧版产出的纯字符串救援为 tool 名,round-trip 后升格为合法对象
- accept 解析数组、序列化为带前导点扩展名数组(schema ^\.[A-Za-z0-9_-]+$,
逗号/中文逗号输入均归一)
- 修复「动态来源」单选把本地化占位文案当真实数据种子(改为 placeholder)
- TYPE_LABEL 类型文案与 accept 字段名走 locale(com_param_type_* / com_param_field_accept)
- SelectField 改 barrel 导入(admin-panel CLAUDE.md 规范)
- 导出 parseManifest/serialize 并新增 5 个 round-trip 单测
19 个领域类型(manifest/分组/Gitea 检查/用量/服务绑定/底图/发现)从 server/terravox.ts 迁入 src/types/ 并入 barrel;server 模块 type-only 再导出保持 '@/server' 导入方零改动(纯类型变更,无运行时影响)。
SelectField 外层只有 max-w 约束,在 flex/grid 里收缩到当前值的自然宽度 (前缀分组实测 77px,选项稍长即截断)。按 KeyValueField 惯例外包宽度 容器:前缀分组 w-44 shrink-0(原原生 select 为 w-40),exec-kind/handler/ distRuntime 恢复原生 w-full 语义。
- 工具分组:显式分组卡片拖拽调整先后,落库 sort_order(×10 间隔,仅改动项 逐个 PATCH);隐式分组(前缀自动合成)无把手固定沉底,增删后本地序回退 服务端序 - 工具清单:分组节内工具行拖拽排序,落库 manifest.display_order(乐观覆盖 保持拖拽视觉稳定,成功后 invalidate 清空);列表排序改为 display_order 升序、未设置沉底再按 tool_id - 编辑弹窗保存透传 display_order(不走表单,防重建 manifest 抹掉已设顺序) - 契约/网关侧见 terravox-contracts e03e660、terravox-gateway 43933ee
分组卡片新增 ☰ 入口:弹窗内上半区列组内工具(⠿ 拖拽定 display_order、 ✕ 移除=清 display_group+display_order 归未分组),下半区搜索点选添加 (设 display_group、顺序留空自然沉底)。改动即时落库并失效 ['terravox'] 缓存,分组卡的工具计数随 invalidate 刷新。隐式分组同样可管理组内工具。
- @dnd-kit/core+sortable(PointerSensor distance 约束,点击不受影响) - 分组工具详情弹窗重设计为双列表:左=未加入工具(可搜索)、右=组内工具 (display_order 序);跨列拖拽=添加/移除、右列内拖拽=排序,droppable 列 容器拖入高亮,提交时统一 diff 落库后失效缓存 - 分组卡/清单工具行换 useSortable(网格 rectSortingStrategy/表格 verticalListSortingStrategy),清单支持跨分组节拖动=直接改 display_group (拖入「未分组」节即清空分组);移除全部手写指针交换代码 - ToolCatalogTab.test 补 dnd-kit 桩(vitest CJS 互操作下命名导出为 undefined, 与 click-ui 白名单 mock 同款约定)
…t 挂载即抛错
sortableKeyboardCoordinates 是坐标函数,直接 useSensor(它) 产出坏传感器描述,
useCombineActivators 里 sensors.map 抛 TypeError,整页落错误边界(「出了点问题」)。
改为 useSensor(KeyboardSensor, { coordinateGetter }) 标准用法(分组详情弹窗同修);
新增真实 dnd-kit(importOriginal 不桩化)渲染回归测试,防止桩化测试掩盖此类崩溃。
…渲染重跑 弹窗打开瞬间 toolsQuery 尚在加载,data?.tools ?? [] 每轮渲染产生新数组引用, byId 派生失效,重建两列的 effect 无限 setState(Maximum update depth, React 整页崩到错误边界)。修复:模块级稳定 EMPTY_TOOLS 常量 + 初始列改 useMemo 派生、同步 effect 以派生数组身份为依赖(身份稳定即不重跑)。 分组详情弹窗纳入真实 dnd-kit 回归测试(渲染双列表断言行内容)。
分组详情双列表保留跨列拖拽语义(dragOver 换列判定),restrictToVerticalAxis 仅锁被拖项的视觉位移轴,跨列判定不受影响。分组卡网格排序暂不加锁 (网格两维都有意义)。
ToolCatalogTab 表格与 ToolGroupsTab 详情弹窗的 restrictToVerticalAxis 此前未传入 DndContext(replace 静默 no-op,prettier 折行所致),竖轴锁定 实际未生效。本次以 grep 断言接线完成。
…再度丢失(prettier 重排吞行),realDnd 测试补 modifiers 真模块 mock 防回归
closestCorners 按被拖项矩形算碰撞,锁轴后矩形永远到不了另一列, dragOver 跨列判定失效(实测无法双向拖)。移除弹窗的 modifiers 与 失效的导入;其他位置(清单表格/图层树)竖轴锁定保持不变。
- 表格新增来源列(此前仅 CSV 导出含 source),colSpan 6→7 - 值经 SOURCE_LABEL_KEY 本地化映射,未知值原样回显
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
with terminology aligned to the LibreChat main zh-Hans translation (shared
com_*strings reuse the official wording so the panel and chat UI read consistently)
Selectdropdown (English /中文(简体)) below the theme row, persisting the choice via the
i18nextLngcache key and keeping
document.documentElement.langin syncshowed
USER/ADMIN; audit-log target-type filter showed raw principal types)Locale detection
Detection is adapted from the LibreChat client implementation
(
client/src/locales/i18n.ts): explicit stored choice wins, then navigator language,normalized through an alias table (
zh,zh-CN,zh-SG→zh-Hans). This replacesi18next-browser-languagedetector, whosenonExplicitSupportedLngshandling stripszh-Hanstozhbefore the whitelist check — meaning a registeredzh-Hanslocalecould never actually be resolved.
SSR renders the pre-hydration loading shell in the default locale deterministically,
and the shell text suppresses the expected hydration mismatch (
<html lang>alreadyhad
suppressHydrationWarning).Tests
src/locales/locales.test.tsguards en/zh key-set parity, interpolationplaceholder parity, language switching (incl.
zh-CN/zhresolution), andlocale normalization
bun run test, 831 tests)Notes for reviewers
Three commits, intentionally split for reviewability:
🌐 feat: Add Simplified Chinese (zh-Hans) locale🌐 feat: Add language setting to settings dialog🐛 fix: Localize role and target type dropdown optionsHappy to adjust the translation wording or split/merge commits as preferred.