diff --git a/.gitignore b/.gitignore index 1572481b..9e8e9143 100644 --- a/.gitignore +++ b/.gitignore @@ -84,6 +84,13 @@ offline-backend-20260512/backend/tests/* deploy .history +# Repo-root offline dependency pack (local only) +/dist/ + +# Frontend offline pack working directories +frontend-web/.offline-pack-build/ +frontend-web/.offline-extract-test/ + # Frontend skills components must be tracked (excluded from the broad skills/ ignore above) !frontend-web/src/components/workspace/skills/ diff --git a/frontend-web/.env.example b/frontend-web/.env.example index adb14fb8..80a2b0f7 100644 --- a/frontend-web/.env.example +++ b/frontend-web/.env.example @@ -5,13 +5,14 @@ # ├─────────────────┼──────────────────────────────────────────────────┤ # │ pnpm dev │ .env.development + .env.development.local │ # │ pnpm build │ .env.production + .env.production.local │ +# │ pnpm build:network │ .env.network + .env.network.local(内网地址) │ # │ scripts/start-* │ 脚本注入的环境变量会覆盖上述文件(见下方说明) │ # └─────────────────┴──────────────────────────────────────────────────┘ # # 推荐: # - 日常本地开发:改 .env.development(已提交,团队默认 UAT 地址) # - 本机连 127.0.0.1:8001:复制 .env.development.local.example → .env.development.local -# - 内网打包部署:改 .env.production 后再执行 pnpm build +# - 内网打包部署:改 .env.network 后执行 pnpm build:network(或改 .env.production 后 pnpm build) # # 若浏览器直连后端报 CORS,请在 Gateway 配置 CORS_ORIGINS,包含前端源站,例如: # CORS_ORIGINS=http://127.0.0.1:5173,http://localhost:5173,http://127.0.0.1:5174 diff --git a/frontend-web/.env.network b/frontend-web/.env.network new file mode 100644 index 00000000..f8962ea6 --- /dev/null +++ b/frontend-web/.env.network @@ -0,0 +1,30 @@ +# 内网 / 局域网生产打包(npm run build:network 或 pnpm build:network) +# 打包前请将 192.168.1.100 改为实际服务器 IP;变量会写入 dist,运行时不可再改 + +# Gateway / DeerFlow API(与 offline-backend DEPLOY 文档一致) +VITE_BACKEND_BASE_URL=http://47.88.25.99 +VITE_LANGGRAPH_BASE_URL=http://47.88.25.99/api + +# 轻应用「新建笔记」系列接口 +VITE_NOTE_API_BASE_URL=http://192.168.1.100:8000 +# CMZS 后端接口 +VITE_API_BASE_URL=http://192.168.1.100/zncm +# 佳来模型对话接口 +VITE_AIPI_CHAT_URL=http://192.168.1.100/gpt +# 多轮检索接口 +VITE_AIPI_GENERATE_CHAT_URL=http://192.168.1.100/jialai_b1_chat +VITE_AIPI_TASK_CHAT_URL=http://192.168.1.100/qq_b1_chat +VITE_AIPI_KNOWLEDGE_URL=http://192.168.1.100/knowlibrary +VITE_AIPI_B1COH_URL=http://192.168.1.100/web +VITE_AIPI_B2ACTOR_URL=http://192.168.1.100 +VITE_AIPI_B2ACTOR_XWT_PATH=/fromB/toXWT?isFrame=1 +VITE_AIPI_KNOWLEDGE_IFRAME_API_URL=http://192.168.1.100/knowes/api/iframeKnowledgeUrl +VITE_AIPI_KNOWLEDGE_IFRAME_SKIN=red +VITE_AIPI_MOPECHAT_URL=http://192.168.1.100/likun_b1_mope +VITE_AIPI_BACKENP_URL=http://192.168.1.100 +VITE_AIPI_CONSUMER_URL=http://192.168.1.100/consumer +VITE_AIPI_PGLINKTO_URL=http://192.168.1.100 +VITE_AIPI_XKLINKTO_URL=http://192.168.1.100 + +# 可选:Canvas 独立 LangGraph 地址 +# VITE_CANVAS_LANGGRAPH_BASE_URL= diff --git a/frontend-web/.env.sit b/frontend-web/.env.sit new file mode 100644 index 00000000..5ae77457 --- /dev/null +++ b/frontend-web/.env.sit @@ -0,0 +1,28 @@ +# SIT 环境打包(pnpm build:sit / npm run build:sit)— 接口地址为 UAT 的 sit 版本 +# 打包后变量会写入 dist,运行时不可再改 + +# Gateway / DeerFlow API +VITE_BACKEND_BASE_URL=https://ch1.b.sit.4.cn/deerflow +VITE_LANGGRAPH_BASE_URL=https://ch1.b.sit.4.cn/deerflow/api + +# 轻应用「新建笔记」系列接口 +VITE_NOTE_API_BASE_URL=https://your-intranet-host/note-api +# 佳来模型对话接口 +VITE_AIPI_CHAT_URL=https://ch1.b.sit.4.cn/gpt +# 多轮检索接口 +VITE_AIPI_GENERATE_CHAT_URL=https://ch1.b.sit.4.cn/jialai_b1_chat +VITE_AIPI_TASK_CHAT_URL=https://ch1.b.sit.4.cn/qq_b1_chat +VITE_AIPI_KNOWLEDGE_URL=https://chlib.b.sit.4.cn/knowlibrary +VITE_AIPI_B1COH_URL=https://ch1.b.sit.4.cn/web +VITE_AIPI_B2ACTOR_URL=https://ty.b.sit.4.cn +VITE_AIPI_B2ACTOR_XWT_PATH=/fromB/toXWT?isFrame=1 +VITE_AIPI_KNOWLEDGE_IFRAME_API_URL=https://ch1.b.sit.4.cn/knowes/api/iframeKnowledgeUrl +VITE_AIPI_KNOWLEDGE_IFRAME_SKIN=red +VITE_AIPI_MOPECHAT_URL=https://ch1.b.sit.4.cn/likun_b1_mope +VITE_AIPI_BACKENP_URL=https://knowledgetoolserver.b.sit.4.cn +VITE_AIPI_CONSUMER_URL=https://ch1.b.sit.4.cn/consumer +VITE_AIPI_PGLINKTO_URL=https://pg.b.sit.4.cn +VITE_AIPI_XKLINKTO_URL=https://kz.b.sit.4.cn + +# 可选:Canvas 独立 LangGraph 地址 +# VITE_CANVAS_LANGGRAPH_BASE_URL= diff --git a/frontend-web/docs/multi-agent-backend-dev.md b/frontend-web/docs/multi-agent-backend-dev.md index 4492f3e5..3e5bf522 100644 --- a/frontend-web/docs/multi-agent-backend-dev.md +++ b/frontend-web/docs/multi-agent-backend-dev.md @@ -20,6 +20,7 @@ - **leader 共识 = `status: []` 空数组**;澄清 = `status: "clarification"`(字符串);派活 = `status: [[name, task], ...]`。这三者**互斥不混用**。 - **后端 picks clamp 2–6,前端用户可手动加到 8**。 - **不写死任何具体模型名**:`intent / recommend / multi_agent` 三个 router 的 `model_name = str(payload.get("model") or "")`,空串会被下游 `lead_agent._resolve_model_name()` 自动回退到 `config.yaml` 的 `models[0]`(与 `/api/ai-writing` 行为一致)。内网部署只需配 `config.yaml`,前后端均无需改硬编码。 +- **草稿/推荐历史持久化(2026-06)**:圆桌页的「历史草稿」从浏览器 `localStorage` 迁到**后端 MySQL**(`/api/roundtable-drafts`,按登录用户隔离);新增**会话级推荐历史**让推荐弹窗回显上次结果。两张表 `roundtable_drafts` / `roundtable_recommend_history`,启动 `create_all` 自动建表,详见 §7A。 --- @@ -73,7 +74,12 @@ offline-backend-20260512/backend/ │ ├── multi_agent.py # Step 2:init(SSE) + run/stream + 共享的 loopback 工具 │ ├── intent.py # Step 1:init + stream(import multi_agent 工具) │ ├── recommend.py # Step 1→2:推荐 stream(import multi_agent 工具) -│ └── artifacts.py # Step 3:列出/下载/编辑 thread 产出(通用,非圆桌专用) +│ ├── artifacts.py # Step 3:列出/下载/编辑 thread 产出(通用,非圆桌专用) +│ └── roundtable_drafts.py # 持久化:草稿 CRUD + 推荐历史(§7A,2026-06 新增) +├── packages/harness/deerflow/persistence/roundtable_drafts/ +│ ├── model.py # ORM:RoundtableDraftRow + RoundtableRecommendHistoryRow +│ ├── sql.py # RoundtableDraftRepository(CRUD + 历史) +│ └── __init__.py # make_roundtable_draft_store(sf) ├── packages/harness/deerflow/tools/builtins/ │ └── clarification_utils.py # resolve_allow_multiple + coerce_bool(Step1/Step2 共享) └── .deer-flow/agents/ @@ -110,8 +116,15 @@ offline-backend-20260512/backend/ | GET | `/api/threads/{thread_id}/artifacts` | 3 | 列出该 thread 的 outputs 文件 | | GET | `/api/threads/{thread_id}/artifacts/{path}` | 3 | 预览/下载单个产出(含 `.skill/` 内文件提取) | | PATCH | `/api/threads/{thread_id}/artifacts/{path}` | 3 | 编辑文本类产物(白名单后缀) | +| GET | `/api/roundtable-drafts` | 持久化 | 列出当前用户草稿(轻量元数据) | +| POST | `/api/roundtable-drafts` | 持久化 | 新建草稿 | +| GET | `/api/roundtable-drafts/{id}` | 持久化 | 取草稿全量(含 step1/step2 快照) | +| PUT | `/api/roundtable-drafts/{id}` | 持久化 | 局部更新(title / furthest_step / step1 / step2) | +| DELETE | `/api/roundtable-drafts/{id}` | 持久化 | 删除草稿 | +| GET | `/api/roundtable-drafts/{id}/recommendations` | 持久化 | 列出该草稿的推荐历史(倒序) | +| POST | `/api/roundtable-drafts/{id}/recommendations` | 持久化 | 追加一次推荐记录 | -字段级请求/响应见各 step 的 API 文档;下文只写**后端实现语义**。 +字段级请求/响应见各 step 的 API 文档;下文只写**后端实现语义**。圆桌草稿/推荐历史持久化见 §7A。 --- @@ -589,6 +602,53 @@ Step 3 **没有** 单独的 `multi_agent` 路由;使用既有 artifacts 能力 --- +## 7A. 圆桌草稿与推荐历史持久化(`roundtable_drafts.py`,2026-06) + +圆桌规划页的「历史记录」此前只存浏览器 `localStorage`(前端 `utils/drafts.ts`),换设备/清缓存即丢、大对话还会撞 5MB 配额。2026-06 迁到**后端 MySQL**,按登录用户隔离、跨设备保留;并新增**会话级推荐历史**,让推荐弹窗能回显上次推荐结果。 + +### 7A.1 数据模型(`deerflow.persistence.roundtable_drafts`) + +仿 `user_prompts`(最简 CRUD)+ `ai_writing_sessions`(大 JSON 用 `PortableLongText` 存)两个现有模式。两张表: + +- **`roundtable_drafts`**(会话主表,一条 = 一次完整圆桌会话) + - `id`(PK, str64) / `user_id`(idx) / `title`(512) / `furthest_step`(int, 1|2|3) + - `step1` / `step2`:前端 `DraftStep{1,2}Snapshot` 的 JSON,列类型 **`PortableLongText`**(MySQL `LONGTEXT`),避免 Step 2 长对话 + `seatStubMessages` 撞 `TEXT` 64KB 上限 + - `created_at` / `updated_at`:**`BeijingDateTime`**(项目规范,存北京时间且 tz-aware) +- **`roundtable_recommend_history`**(推荐历史,按 `draft_id` 归属) + - `id`(PK) / `user_id`(idx) / `draft_id`(idx) / `objective`(512) / `status`(32: done|fallback|asking|error) / `model`(128) + - `rationale` / `picks`(JSON) / `candidates`(JSON 候选池快照):均 `PortableLongText` + - `created_at`:`BeijingDateTime` + +JSON 列在 repo(`sql.py`)里用 `json.dumps/loads` 手动序列化(与 `ai_writing_sessions.transcript` 同法)。两个 Row 已登记进 `persistence/models/__init__.py`,**启动时 `Base.metadata.create_all` 自动建表**——新部署无需手动建表(需 mysql 账号有建库/表权限)。 + +### 7A.2 路由(`app/gateway/routers/roundtable_drafts.py`,前缀 `/api/roundtable-drafts`) + +| 方法 | 路径 | 说明 | +|------|------|------| +| GET | `` | `list_drafts` → 轻量元数据列表(不含 step 大 blob),按 `updated_at` 倒序 | +| POST | `` | `create_draft`,可带客户端 `id`(稳定 id 跨 create+update) | +| GET | `/{id}` | `get_draft` 全量;非属主 → 404 | +| PUT | `/{id}` | `update_draft`,`model_dump(exclude_unset=True)` → 只更新客户端实际发的字段 | +| DELETE | `/{id}` | 204;非属主 → 404 | +| GET | `/{id}/recommendations` | 推荐历史倒序 | +| POST | `/{id}/recommendations` | 追加一次推荐(先校验草稿属主,404 拦截越权) | + +鉴权/属主:用 `_current_user_id(request)`(登录态取 `request.state.user.id`,无登录回退 `get_effective_user_id()` 的 `"default"`),与 `user_prompts` 一致;所有 repo 读写按 `user_id` 过滤,跨用户访问一律 404。store 在 `deps.py` 接为 `app.state.roundtable_draft_store`(`make_roundtable_draft_store(sf)`),`app.py` `include_router`。 + +### 7A.3 与前端的契约要点 + +- **PUT 不带 `title` 时不改标题**:前端自动保存只发 `furthest_step`/`step1`/`step2`,不发 `title`,因此**不会覆盖用户手动重命名**;标题只在 create(派生)与显式重命名(只发 title)时改。 +- **推荐时机**:前端在打开推荐弹窗前先 `ensureDraft` 落地草稿拿到 `draft_id`,推荐完成后 POST 一条历史;重开弹窗 GET 历史回显最近一次,可「重新分析」再 POST。 +- **字段命名**:后端 snake_case(`furthest_step`/`created_at`),前端 `api/drafts.ts` 在边界映射成 camelCase。 + +### 7A.4 设计取舍 + +- **整存 JSON 而非拆表**:step1/step2 形状宽且常变(前端 hook 里是 loose 类型),整存 JSON 避免每次改前端快照都要迁库;代价是后端不校验嵌套字段(与 `ai_writing_sessions.transcript` 同取舍)。 +- **推荐历史绑定草稿而非全局**:语义是「这个会话上次推荐了什么」,故按 `draft_id` 归属;这也要求推荐前必有 `draft_id`(前端 `ensureDraft` 保证)。 +- **本地 dev 走 sqlite**:`config.yaml` `database.backend` 本地默认 sqlite,走同一套 SQLAlchemy 代码,`PortableLongText/JSON` 各自降级;生产切 mysql 用同一份代码。 + +--- + ## 8. 内置席位 SOUL 设计要点 每个 `roundtable-*`(除 intent / recommender)遵循统一结构:角色定位、核心职责、输出风格(emoji + 简短结论)、边界(只产出本席位、等总控派活)。 @@ -1195,6 +1255,9 @@ client = httpx.AsyncClient(transport=transport, base_url=...) | 推荐路由 | `offline-backend-20260512/backend/app/gateway/routers/recommend.py` | | Step 2 路由 | `offline-backend-20260512/backend/app/gateway/routers/multi_agent.py` | | Step 3 列出/下载/编辑 | `offline-backend-20260512/backend/app/gateway/routers/artifacts.py` | +| 草稿/推荐历史路由(§7A) | `offline-backend-20260512/backend/app/gateway/routers/roundtable_drafts.py` | +| 草稿/推荐历史持久化 | `offline-backend-20260512/backend/packages/harness/deerflow/persistence/roundtable_drafts/` | +| store 接线 | `offline-backend-20260512/backend/app/gateway/deps.py`(`app.state.roundtable_draft_store`) | | 路由注册 | `offline-backend-20260512/backend/app/gateway/app.py` | | 澄清单选/多选 | `offline-backend-20260512/backend/packages/harness/deerflow/tools/builtins/clarification_utils.py` | | 任务理解 Agent | `offline-backend-20260512/backend/.deer-flow/agents/roundtable-intent/` | diff --git a/frontend-web/docs/multi-agent-frontend-dev.md b/frontend-web/docs/multi-agent-frontend-dev.md index 89ee2963..a8d8e82c 100644 --- a/frontend-web/docs/multi-agent-frontend-dev.md +++ b/frontend-web/docs/multi-agent-frontend-dev.md @@ -14,6 +14,7 @@ - **步骤折叠卡与主聊天对齐**:思考过程 / 工具调用展示走 `MessageStepsCard`,**always-show-last-tool + 折叠 "查看其他 N 个步骤"** 的视觉与主聊天 `chats/new` 的 `MessageGroup` 一致。工具图标 / 文案映射在 `lib/step-display.tsx`,新增工具时与 `src/core/i18n/locales/zh-CN.ts` 的 `toolCalls` 字段对齐。 - **`` 标签自动提取为「思考过程」**:`lib/reasoning.ts::splitInlineReasoning(text)` 把 `...`(含未闭合的流式中段)抽离成 `reasoning`,正文走 markdown;由 `MessageBubble.tsx::ReasoningBlock` 渲染一个折叠卡。Step 1 / Step 2 气泡和推荐弹窗的 rationale 都走这一套,与主聊天 `Reasoning` 元素的视觉对齐。 - **不使用 LangGraph SDK**:所有 API 都是自研的 `apiFetch` + `ReadableStream` SSE 解析(见 `api/intent.ts`、`api/recommend.ts`、`api/multi-agent.ts`)。 +- **草稿/历史持久化在后端(2026-06)**:草稿不再存浏览器 `localStorage`,改走后端 MySQL(`/api/roundtable-drafts`,按登录用户隔离、跨设备保留)。`api/drafts.ts` 封装 CRUD,`useDraftPersistence` 异步对接、对外接口不变。**推荐弹窗带「会话级推荐历史」**:每个草稿一串历史,重开弹窗先显示上次推荐结果,可「重新分析」重跑(详见 §5.7 / §6.2)。后端实现见 backend doc 的「圆桌草稿持久化」一节。 - **不写死模型名**:与 `/page/canvas/ai-writing` 同样的策略 —— 全局选中模型由 `selectedModel` state 维护,首次 `useModels()` 完成后初始化为 `availableModels[0].name`,顶栏「模型」下拉可切换。所有 API 调用统一传 `model: selectedModel || undefined`;后端缺省由 `lead_agent._resolve_model_name()` 回退到 `config.yaml` 的 `models[0]`。**内网部署只需在 `config.yaml` 配自家模型即可,前后端均无需改动**。 - **席位区间硬约束**:推荐弹窗要求 **2 ≤ 选中数 ≤ 8**(`MIN_SELECTED` / `MAX_SELECTED`)。 - **协调智能体单例(2026-05)**:`COORDINATOR_AGENT_ID = "roundtable-coordinator"` 是固定常量,不再每会话生成新 id。后端 seeder 自动落地,「本次席位列表」改由后端通过 thread 历史 human 消息注入,前端无感。`newCoordinatorName()` 仅作为兼容性壳保留。 @@ -54,7 +55,8 @@ frontend-web/src/roundtable-planning/ │ └── RoundtablePlanningPage.tsx # 主页面:组合 hook + 顶栏/侧栏 + 三步路由(≈1 150 行) ├── api/ │ ├── intent.ts # Step 1:initIntent、streamIntent -│ ├── recommend.ts # 推荐:streamRecommend、filterRecommendCandidates、parseRecommendStreamContent +│ ├── recommend.ts # 推荐:streamRecommend、filterRecommendCandidates、parseRecommendStreamContent;**+ 推荐历史 listRecommendHistory / saveRecommendHistory** +│ ├── drafts.ts # **草稿后端 CRUD**:listDrafts / getDraft / createDraft / updateDraft / deleteDraft / deriveDraftName(替代旧 localStorage utils/drafts.ts) │ └── multi-agent.ts # Step 2/3:initMultiAgent、streamMultiAgent、listSessionArtifacts、buildArtifactDownloadUrl ├── components/ │ ├── Avatars.tsx # 所有 SVG 头像 + getAvatar 选择器 @@ -73,14 +75,13 @@ frontend-web/src/roundtable-planning/ │ ├── useAutoScroll.ts # 滚动跟随 + 用户上滑暂停跟随 │ ├── useStep1Intent.ts # Step 1 状态机:init、send、stream、abort、INTENT_READY 同步 │ ├── useStep2Orchestration.ts # Step 2 状态机:initMultiAgent + 编排循环 + 对话流管理 + Persona -│ └── useDraftPersistence.ts # 草稿:自动保存(前进切换)/手动保存/加载/重命名/删除 +│ └── useDraftPersistence.ts # 草稿(后端版):异步走 api/drafts;自动保存(前进切换)/手动保存/加载/重命名/删除/ensureDraft ├── lib/ │ ├── clarification.ts # resolveAllowMultiple(单选/多选解析),与主聊天 ClarificationCard 对齐 │ ├── reasoning.ts # splitInlineReasoning:从模型流式文本里提取 `...` 思考过程 │ ├── roundtable-constants.ts # 类型(RoleEntry / SelectedAgent) + 常量 + 纯工具函数(buildRoleTemplates / splitIntentReadyBlock 等) │ └── step-display.tsx # Step 2 ChainOfThought 折叠卡图标 + 文案 + 子节点渲染 -├── utils/ -│ └── drafts.ts # localStorage 草稿存档(list / upsert / get / delete / rename / deriveName) +├── utils/ # (旧 drafts.ts 已删除 —— 草稿改走后端 api/drafts.ts) └── styles/ └── roundtable-planning.css # 页面主题变量(浅红品牌色 + 深色覆盖) ``` @@ -183,6 +184,7 @@ hooks/useDraftPersistence → utils/drafts + step1/step2 的 Snapshot 类型 - `signal`:必填(页面通过 `intentAborterRef` 控制暂停 / 切步取消) - `onTextDelta(text, messageId)`:每个 AI 文本增量 - `onPhaseChange(phase, toolName?)`:`connecting → connected → streaming|tool_calling` +- **`onClarification(clarification, index)`(2026-06)**:每解析到一条 `ask_clarification` 工具结果帧就回调一次,让前端**实时**逐个渲染澄清卡。任务理解智能体**一轮可能问多个问题**,这是「实时接收显示协助过程」的关键入口。 **`StreamIntentResult`**: @@ -191,14 +193,16 @@ hooks/useDraftPersistence → utils/drafts + step1/step2 的 Snapshot 类型 | `status` | `"asking" \| "clarification" \| "done" \| "error"` | 终态分支 | | `content` | `string` | 完整 AI 文本(含 `[INTENT_READY]` 块) | | `summary` | `IntentSummary \| null` | 仅 `done` 有;前端 fallback 也会从 `content` 里用 `splitIntentReadyBlock` 解 | -| `clarification` | `ClarificationPayload \| null` | 仅 `clarification` 有 | +| `clarification` | `ClarificationPayload \| null` | **第一个**澄清(向后兼容字段) | +| `clarifications` | `ClarificationPayload[]` | **本轮全部澄清问题**(2026-06)。来源是流式 `type:"tool"` 帧而非终态帧——**终态帧只带第 1 个**,多问题必须靠它。每条带 `id`(= `tool_call_id`)供前端按题维护选择。 | **SSE 解析要点**: - `event: metadata` → phase `connected` - `event: messages` + `extractAiTextDelta` → `onTextDelta` +- `event: messages` + `type:"tool"` 且 `name==="ask_clarification"` → `extractClarificationToolMessage` 解出完整澄清(options 已是 `{id,label}`),按 `tool_call_id` 去重后 `onClarification` 实时回调并收进 `result.clarifications` - tool call 帧(`tool_calls` / `tool_call_chunks` 非空)→ phase `tool_calling`(含 `ask_clarification` 工具名) -- 终态帧识别:`isFinalIntentFrame`(必须有 `status` + `content`) +- 终态帧识别:`isFinalIntentFrame`(必须有 `status` + `content`);`status==="clarification"` 且**未**收到任何流式 tool 澄清帧时,才用终态帧字段兜底合成单条澄清 **Abort 行为**: @@ -430,19 +434,29 @@ isOrchestrating = false - **禁用态**:`availableModels.length === 0` 或 `modelsLoading` → `disabled`,placeholder 显示「加载中…」或「未配置模型」 - **可改换为 Radix Select**:当前用原生 `) { className={cn( "h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base outline-none transition-[color,box-shadow] md:text-sm", "border-input placeholder:text-muted-foreground", - "dark:border-[#1B477E] dark:bg-[#022248] dark:text-[#AACEFF] dark:placeholder:text-[#566B7F] rounded-[4px] text-[16px]", + "dark:border-[#1B477E] dark:bg-[#0D2D54] dark:text-[#AACEFF] dark:placeholder:text-[#566B7F] dark:rounded-[4px] dark:text-[16px] md:dark:text-[16px]", + usesDarkNativePicker && "dark:[color-scheme:dark]", "file:text-foreground selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50", "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] dark:focus-visible:border-[#3b82f6] dark:focus-visible:ring-[#3b82f6]/30", "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40", diff --git a/frontend-web/src/components/ui/select.tsx b/frontend-web/src/components/ui/select.tsx index 327e1350..5f3e26a9 100644 --- a/frontend-web/src/components/ui/select.tsx +++ b/frontend-web/src/components/ui/select.tsx @@ -24,69 +24,70 @@ function SelectValue({ return ; } -const SelectTrigger = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef & { - size?: "sm" | "default"; - } ->(({ className, size = "default", children, ...props }, ref) => { - return ( - - {children} - - - - - ); -}); - -SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + size?: "sm" | "default"; + } +>(({ className, size = "default", children, ...props }, ref) => { + return ( + + {children} + + + + + ); +}); -const SelectContent = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, position = "item-aligned", align = "center", ...props }, ref) => { - return ( - - - - - {children} - - - - - ); -}); - -SelectContent.displayName = SelectPrimitive.Content.displayName; +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, position = "item-aligned", align = "center", ...props }, ref) => { + return ( + + + + + {children} + + + + + ); +}); + +SelectContent.displayName = SelectPrimitive.Content.displayName; function SelectLabel({ className, @@ -95,40 +96,44 @@ function SelectLabel({ return ( ); } -const SelectItem = React.forwardRef< - React.ElementRef, - React.ComponentPropsWithoutRef ->(({ className, children, ...props }, ref) => { - return ( - - - - - - - {children} - - ); -}); - -SelectItem.displayName = SelectPrimitive.Item.displayName; +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => { + return ( + + + + + + + {children} + + ); +}); + +SelectItem.displayName = SelectPrimitive.Item.displayName; function SelectSeparator({ className, @@ -151,7 +156,7 @@ function SelectScrollUpButton({
- {label} + {label} {!editing && (