Skip to content

feat(wework): persist scoped model selection - #1218

Merged
qdaxb merged 1 commit into
wecode-ai:mainfrom
icycrystal4:codex/wework-model-selector-ui
Jun 2, 2026
Merged

qdaxb merged 1 commit into
wecode-ai:mainfrom
icycrystal4:codex/wework-model-selector-ui

Conversation

@icycrystal4

@icycrystal4 icycrystal4 commented Jun 1, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • add project-level modelSelection config and restore it in WeWork project chats
  • expose task model type/options snapshots so existing conversations restore their actual model settings
  • refine the WeWork model selector UI: reasoning display, selected-button label, and viewport-aware submenu placement
  • avoid refresh-time model selector flicker by hiding bootstrap/restoring states until the project model selection is resolved
  • group unknown real model families under 其他 instead of using compatible provider protocol as Claude/GPT
  • add GLM, DeepSeek, and Qwen as first-class model family labels and auto-detect their model identities

Tests

  • cd wework && npm run lint
  • cd wework && npm test -- workbenchProjectChatHooks.test.tsx WorkbenchProvider.test.tsx ChatInput.test.tsx DesktopWorkbenchLayout.test.tsx MobileWorkbenchLayout.test.tsx
  • cd wework && npm test -- model-ui.test.ts workbenchProjectChatHooks.test.tsx ChatInput.test.tsx
  • cd backend && uv run pytest tests/schemas/test_project_config_schema.py

Integration

  • synced to integration environment
  • imported production Model rows into integration DB with backup table kinds_model_backup_20260601_174401
  • restarted wework on port 1420
  • integration wework lint passed
  • integration tests passed for targeted WeWork suites
  • service status healthy at http://wework_test:1420

Note: DingTalk notification intentionally skipped per request.

@coderabbitai

coderabbitai Bot commented Jun 1, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

<review_stack_artifact>

</review_stack_artifact>

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@icycrystal4
icycrystal4 force-pushed the codex/wework-model-selector-ui branch from d2349b4 to 1dde628 Compare June 1, 2026 06:49

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (7)
executor/agents/claude_code/standalone_chat_workspace.py (1)

48-52: ⚡ Quick win

Redundant wrapper function.

slugify_response is a simple pass-through to slugify_workspace_name with no additional logic. Consider removing this wrapper and updating callers to use slugify_workspace_name directly, or add a docstring explaining why both functions are needed.

♻️ Simplify by removing wrapper

Update line 70 to call slugify_workspace_name directly:

 def unique_chat_workspace_path(root: Path, response_text: str) -> Path:
     """Return an unused dated workspace path for the response text."""
     date_dir = root / datetime.now().strftime("%Y-%m-%d")
-    base_name = slugify_workspace_name(response_text)
+    base_name = slugify_workspace_name(response_text)

Then remove the wrapper:

-def slugify_response(response_text: str) -> str:
-    """Build a filesystem-safe slug from response words."""
-
-    return slugify_workspace_name(response_text)
-
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@executor/agents/claude_code/standalone_chat_workspace.py` around lines 48 -
52, The wrapper function slugify_response is redundant because it simply
forwards to slugify_workspace_name; remove slugify_response and update all
callers to call slugify_workspace_name directly (replace any uses of
slugify_response with slugify_workspace_name). Alternatively, if you prefer to
keep the wrapper for API clarity, add a short docstring explaining the
distinction and ensure tests/call sites still use slugify_response
intentionally; otherwise delete slugify_response and run the test suite to
confirm no references remain.
wework/src/api/projects.ts (1)

13-18: ⚡ Quick win

Consider sharing WEWORK_CLIENT_ORIGIN and withClientOrigin to avoid divergence.

WEWORK_CLIENT_ORIGIN is duplicated in tasks.ts (Line 11), which additionally re-implements the same query-appending logic by hand instead of reusing a helper. Extracting both into a shared module (e.g. a small clientOrigin.ts) keeps the marker and the append logic consistent across projects.ts and tasks.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/api/projects.ts` around lines 13 - 18, Extract the duplicated
WEWORK_CLIENT_ORIGIN constant and withClientOrigin function into a small shared
module (e.g., clientOrigin.ts) that exports named WEWORK_CLIENT_ORIGIN and
withClientOrigin; then remove the duplicate declaration and inline logic from
both projects.ts and tasks.ts and import { WEWORK_CLIENT_ORIGIN,
withClientOrigin } from the new module, making sure the helper preserves the
existing behavior of appending "client_origin=wework" with the correct '?'/'&'
separator.
wework/src/hooks/useEscapeKey.ts (1)

3-15: 💤 Low value

Consider using useEffectEvent to avoid re-subscribing the keydown listener when onEscape changes.

Since onEscape is in the Effect dependency array, callers passing inline callbacks will trigger listener detach/attach every render. React 19.2 exports useEffectEvent from 'react' (import { useEffectEvent } from 'react'), so you can wrap onEscape and keep the Effect dependencies minimal (e.g., just enabled).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/hooks/useEscapeKey.ts` around lines 3 - 15, The useEscapeKey hook
currently re-subscribes the keydown listener whenever the onEscape callback
changes; wrap onEscape with React's useEffectEvent (import { useEffectEvent }
from 'react') inside useEscapeKey to produce a stable callback used by
handleKeyDown, then make the useEffect dependency list only [enabled] so the
listener isn't torn down/recreated on every onEscape identity change; keep
handleKeyDown using the effect event-wrapped callback and retain the existing
add/remove listener logic.
wework/src/features/workbench/WorkbenchProvider.tsx (1)

536-581: 💤 Low value

Use detailTask consistently.

Line 539 creates detailTask but line 557 repeats the cast with detail as Task. For consistency and readability, use the detailTask variable throughout.

♻️ Suggested consistency fix
       dispatch({
         type: 'task_opened',
-        task: detail as Task,
+        task: detailTask,
         project,
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/workbench/WorkbenchProvider.tsx` around lines 536 - 581,
openTask creates detailTask but the function still uses `detail as Task` and
`detail.subtasks` in places — replace those with the already-casted `detailTask`
to be consistent and readable: use `detailTask` for the `task` field in the
`dispatch({ type: 'task_opened', task: ... })` call and for the subtasks passed
to `sortSubtasksForDisplay(...).map(subtaskToMessage)` (i.e., replace `detail as
Task` and `detail.subtasks` with `detailTask` and `detailTask.subtasks`),
keeping the rest of the logic (standaloneDeviceId logic, resolvedProjectId
handling, and `resolvedServices.chatStream.joinTask(taskId)`) unchanged.
backend/app/models/task.py (1)

97-103: ⚡ Quick win

Refine the client_origin indexing suggestion for tasks

  • tasks.client_origin has no column-level index=True, but backend/alembic/versions/20260529_e6f7a8b9c012_add_client_origin.py already adds a composite index ix_tasks_user_origin_active_project (user_id, client_origin, is_active, project_id).
  • Some queries filter by TaskResource.client_origin with project_id/is_active but omit user_id, so that composite index may not help; if this path is hot, consider an additional index that matches the actual predicates (e.g., (client_origin, is_active, project_id) or (client_origin, is_active)) based on query/explain output.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/models/task.py` around lines 97 - 103, The Task model's
client_origin column lacks a standalone/indexed column and the existing
migration only creates a composite index (ix_tasks_user_origin_active_project on
user_id, client_origin, is_active, project_id), which won't support queries that
filter by client_origin with project_id/is_active but omit user_id; add an index
that matches common query predicates by either adding index=True to the
client_origin Column in the Task model or (preferably) creating a dedicated
Index (e.g., on (client_origin, is_active, project_id) or (client_origin,
is_active)) in the model and adding a corresponding alembic migration
(referencing the Task model's client_origin and the existing migration file
20260529_e6f7a8b9c012_add_client_origin.py) so the DB has an index that matches
the actual query patterns observed.
backend/app/services/project_service.py (1)

171-175: 💤 Low value

Consider consistent filtering pattern across the codebase.

This code uses splat unpacking to conditionally add a filter:

*([TaskResource.client_origin == client_origin] if client_origin else [])

While operations.py uses the if-check pattern:

if client_origin:
    query = query.filter(TaskResource.client_origin == client_origin)

Both approaches are functionally correct, but using one pattern consistently across the codebase improves readability and maintainability.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/services/project_service.py` around lines 171 - 175, The use of
splat/unpacking to conditionally add TaskResource.client_origin == client_origin
in the filter is inconsistent with the codebase; replace the expression
"*([TaskResource.client_origin == client_origin] if client_origin else [])" with
the explicit conditional filter pattern used elsewhere: check if client_origin
and then call query = query.filter(TaskResource.client_origin == client_origin)
(or equivalent on the QueryBuilder in project_service.py) so filtering is
applied only when client_origin is truthy and code style matches operations.py.
backend/app/schemas/task.py (1)

107-108: 💤 Low value

Consider making the validation error message more user-friendly.

The error message could list the supported client origins to help API consumers understand what values are valid.

💡 Suggested improvement
         if self.client_origin not in SUPPORTED_CLIENT_ORIGINS:
-            raise ValueError("Unsupported client_origin")
+            raise ValueError(
+                f"Unsupported client_origin: {self.client_origin}. "
+                f"Supported values: {', '.join(SUPPORTED_CLIENT_ORIGINS)}"
+            )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/schemas/task.py` around lines 107 - 108, The ValueError raised in
the validation for self.client_origin is too generic; update the check in the
Task schema (where self.client_origin is validated against
SUPPORTED_CLIENT_ORIGINS) to raise a clearer message that includes the allowed
values (e.g., format SUPPORTED_CLIENT_ORIGINS as a comma-separated string or
repr and include it in the ValueError) so API consumers see which client_origin
values are valid.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/app/services/adapters/task_kinds/operations.py`:
- Around line 323-327: The stored model_options JSON string is serialized into
labels but never parsed back; modify the retrieval path that builds task dicts
(e.g., convert_to_task_dict_optimized and any other task conversion helpers) to
check for a "modelOptions" label, JSON.parse/deserialise it and populate the
task's model_options field (or modelOptions key) with the resulting dict/object;
ensure you handle missing or invalid JSON by falling back to None/empty dict and
keep the label-based behavior symmetric with the write-side that uses
json_lib.dumps(obj_in.model_options).

In `@backend/app/services/project_device_session_service.py`:
- Around line 37-44: The filter currently uses an explicit boolean comparison
Project.is_active == True; change this to use the truthiness check directly by
replacing that predicate with Project.is_active in the query.filter call (i.e.,
use Project.is_active instead of Project.is_active == True) so the code follows
PEP 8/Black formatting and avoids the explicit True comparison while keeping the
rest of the query (query, Project, project_id, user_id, client_origin)
unchanged.

In `@executor/agents/claude_code/standalone_chat_workspace.py`:
- Around line 79-97: The function prompt_text_for_workspace may append both
"text" and "content" from a single block causing duplicated output; change the
block extraction to use a single prioritized field (e.g., prefer block["text"]
if it's a str, otherwise use block["content"] if it's a str) instead of
appending both. Update the loop over prompt blocks (referencing variables block
and text_parts in prompt_text_for_workspace) to check for "text" first and only
fall back to "content", then append that single chosen string to text_parts
before continuing and join as before.

In `@wework/src/components/chat/composer/ProjectWorkBar.tsx`:
- Line 67: The null-check using loose equality should be changed: update the
isStandaloneMode assignment (symbol: isStandaloneMode, variable:
currentProjectId) to use strict comparisons; replace "currentProjectId == null"
with either "currentProjectId === null" if you only want to detect null, or with
"currentProjectId === null || currentProjectId === undefined" if you intend to
treat undefined the same as null to satisfy TypeScript strict-mode rules.

In `@wework/src/features/workbench/WorkbenchProvider.tsx`:
- Line 43: Duplicate WEWORK_CLIENT_ORIGIN constants exist; create a single
exported constant (WEWORK_CLIENT_ORIGIN) in a new shared module (e.g.,
api/constants) and replace the local definitions by importing that constant into
WorkbenchProvider and the API modules (projects.ts, tasks.ts); remove the
duplicate local declarations so all uses (including the client_origin
assignments in WorkbenchProvider) reference the single exported symbol.

---

Nitpick comments:
In `@backend/app/models/task.py`:
- Around line 97-103: The Task model's client_origin column lacks a
standalone/indexed column and the existing migration only creates a composite
index (ix_tasks_user_origin_active_project on user_id, client_origin, is_active,
project_id), which won't support queries that filter by client_origin with
project_id/is_active but omit user_id; add an index that matches common query
predicates by either adding index=True to the client_origin Column in the Task
model or (preferably) creating a dedicated Index (e.g., on (client_origin,
is_active, project_id) or (client_origin, is_active)) in the model and adding a
corresponding alembic migration (referencing the Task model's client_origin and
the existing migration file 20260529_e6f7a8b9c012_add_client_origin.py) so the
DB has an index that matches the actual query patterns observed.

In `@backend/app/schemas/task.py`:
- Around line 107-108: The ValueError raised in the validation for
self.client_origin is too generic; update the check in the Task schema (where
self.client_origin is validated against SUPPORTED_CLIENT_ORIGINS) to raise a
clearer message that includes the allowed values (e.g., format
SUPPORTED_CLIENT_ORIGINS as a comma-separated string or repr and include it in
the ValueError) so API consumers see which client_origin values are valid.

In `@backend/app/services/project_service.py`:
- Around line 171-175: The use of splat/unpacking to conditionally add
TaskResource.client_origin == client_origin in the filter is inconsistent with
the codebase; replace the expression "*([TaskResource.client_origin ==
client_origin] if client_origin else [])" with the explicit conditional filter
pattern used elsewhere: check if client_origin and then call query =
query.filter(TaskResource.client_origin == client_origin) (or equivalent on the
QueryBuilder in project_service.py) so filtering is applied only when
client_origin is truthy and code style matches operations.py.

In `@executor/agents/claude_code/standalone_chat_workspace.py`:
- Around line 48-52: The wrapper function slugify_response is redundant because
it simply forwards to slugify_workspace_name; remove slugify_response and update
all callers to call slugify_workspace_name directly (replace any uses of
slugify_response with slugify_workspace_name). Alternatively, if you prefer to
keep the wrapper for API clarity, add a short docstring explaining the
distinction and ensure tests/call sites still use slugify_response
intentionally; otherwise delete slugify_response and run the test suite to
confirm no references remain.

In `@wework/src/api/projects.ts`:
- Around line 13-18: Extract the duplicated WEWORK_CLIENT_ORIGIN constant and
withClientOrigin function into a small shared module (e.g., clientOrigin.ts)
that exports named WEWORK_CLIENT_ORIGIN and withClientOrigin; then remove the
duplicate declaration and inline logic from both projects.ts and tasks.ts and
import { WEWORK_CLIENT_ORIGIN, withClientOrigin } from the new module, making
sure the helper preserves the existing behavior of appending
"client_origin=wework" with the correct '?'/'&' separator.

In `@wework/src/features/workbench/WorkbenchProvider.tsx`:
- Around line 536-581: openTask creates detailTask but the function still uses
`detail as Task` and `detail.subtasks` in places — replace those with the
already-casted `detailTask` to be consistent and readable: use `detailTask` for
the `task` field in the `dispatch({ type: 'task_opened', task: ... })` call and
for the subtasks passed to `sortSubtasksForDisplay(...).map(subtaskToMessage)`
(i.e., replace `detail as Task` and `detail.subtasks` with `detailTask` and
`detailTask.subtasks`), keeping the rest of the logic (standaloneDeviceId logic,
resolvedProjectId handling, and `resolvedServices.chatStream.joinTask(taskId)`)
unchanged.

In `@wework/src/hooks/useEscapeKey.ts`:
- Around line 3-15: The useEscapeKey hook currently re-subscribes the keydown
listener whenever the onEscape callback changes; wrap onEscape with React's
useEffectEvent (import { useEffectEvent } from 'react') inside useEscapeKey to
produce a stable callback used by handleKeyDown, then make the useEffect
dependency list only [enabled] so the listener isn't torn down/recreated on
every onEscape identity change; keep handleKeyDown using the effect
event-wrapped callback and retain the existing add/remove listener logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8c0f0a1e-17f2-4619-af4f-b9be1baad0c4

📥 Commits

Reviewing files that changed from the base of the PR and between 7b52df7 and d2349b4.

📒 Files selected for processing (55)
  • backend/alembic/versions/20260529_e6f7a8b9c012_add_client_origin.py
  • backend/app/api/endpoints/adapter/tasks.py
  • backend/app/api/endpoints/projects.py
  • backend/app/api/ws/chat_namespace.py
  • backend/app/api/ws/events.py
  • backend/app/core/constants.py
  • backend/app/models/project.py
  • backend/app/models/task.py
  • backend/app/schemas/project.py
  • backend/app/schemas/task.py
  • backend/app/services/adapters/task_kinds/converters.py
  • backend/app/services/adapters/task_kinds/helpers.py
  • backend/app/services/adapters/task_kinds/operations.py
  • backend/app/services/adapters/task_kinds/queries.py
  • backend/app/services/adapters/task_kinds/query_utils.py
  • backend/app/services/chat/storage/task_manager.py
  • backend/app/services/chat/trigger/lifecycle.py
  • backend/app/services/project_device_session_service.py
  • backend/app/services/project_service.py
  • backend/tests/services/adapters/test_task_archive_operations.py
  • executor/agents/claude_code/claude_code_agent.py
  • executor/agents/claude_code/standalone_chat_workspace.py
  • executor/tests/agents/test_project_workspace_paths.py
  • executor/tests/agents/test_standalone_chat_workspace.py
  • wework/src/App.plugins.test.tsx
  • wework/src/api/adapters.test.ts
  • wework/src/api/projects.ts
  • wework/src/api/tasks.ts
  • wework/src/api/users.ts
  • wework/src/components/chat/ChatInput.test.tsx
  • wework/src/components/chat/ChatInput.tsx
  • wework/src/components/chat/composer/ComposerToolbar.tsx
  • wework/src/components/chat/composer/ModelSelector.tsx
  • wework/src/components/chat/composer/ProjectChatComposer.tsx
  • wework/src/components/chat/composer/ProjectWorkBar.tsx
  • wework/src/components/common/TextInputDialog.test.tsx
  • wework/src/components/common/TextInputDialog.tsx
  • wework/src/components/layout/DesktopSidebar.tsx
  • wework/src/components/layout/DesktopWorkbenchLayout.test.tsx
  • wework/src/components/layout/DesktopWorkbenchLayout.tsx
  • wework/src/components/layout/MobileWorkbenchLayout.test.tsx
  • wework/src/components/layout/MobileWorkbenchLayout.tsx
  • wework/src/components/projects/ProjectCreateDialog.test.tsx
  • wework/src/components/projects/ProjectCreateDialog.tsx
  • wework/src/features/workbench/WorkbenchProvider.test.tsx
  • wework/src/features/workbench/WorkbenchProvider.tsx
  • wework/src/features/workbench/useWorkbenchModels.ts
  • wework/src/features/workbench/workbenchProjectChatHooks.test.tsx
  • wework/src/features/workbench/workbenchReducer.ts
  • wework/src/hooks/useEscapeKey.ts
  • wework/src/i18n/locales/en/common.json
  • wework/src/i18n/locales/zh-CN/common.json
  • wework/src/lib/model-ui.ts
  • wework/src/pages/WorkbenchPage.tsx
  • wework/src/types/api.ts

Comment thread backend/app/services/adapters/task_kinds/operations.py
Comment on lines +37 to +44
query = db.query(Project).filter(
Project.id == project_id,
Project.user_id == user_id,
Project.is_active == True,
)
if client_origin:
query = query.filter(Project.client_origin == client_origin)
project = query.first()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the boolean comparison.

Line 40 uses Project.is_active == True which violates PEP 8 style. Use the truthiness check directly instead.

✨ Proposed fix
     query = db.query(Project).filter(
         Project.id == project_id,
         Project.user_id == user_id,
-        Project.is_active == True,
+        Project.is_active,
     )

As per coding guidelines, follow PEP 8, Black formatter (line length: 88), isort, and type hints required for Python.

🧰 Tools
🪛 Ruff (0.15.14)

[error] 40-40: Avoid equality comparisons to True; use Project.is_active: for truth checks

Replace with Project.is_active

(E712)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/services/project_device_session_service.py` around lines 37 - 44,
The filter currently uses an explicit boolean comparison Project.is_active ==
True; change this to use the truthiness check directly by replacing that
predicate with Project.is_active in the query.filter call (i.e., use
Project.is_active instead of Project.is_active == True) so the code follows PEP
8/Black formatting and avoids the explicit True comparison while keeping the
rest of the query (query, Project, project_id, user_id, client_origin)
unchanged.

Comment on lines +79 to +97
def prompt_text_for_workspace(prompt: Any) -> str:
"""Extract user-visible text from an execution prompt."""

if isinstance(prompt, str):
return prompt
if isinstance(prompt, list):
text_parts: list[str] = []
for block in prompt:
if not isinstance(block, dict):
continue
text = block.get("text")
if isinstance(text, str):
text_parts.append(text)
content = block.get("content")
if isinstance(content, str):
text_parts.append(content)
return "\n".join(text_parts)
return str(prompt or "")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Potential double-extraction of text from prompt blocks.

If a prompt block contains both "text" and "content" keys, the function will append both values to text_parts, potentially duplicating content. Consider using an explicit priority (extract "text" OR "content", not both).

♻️ Proposed fix to prioritize one field
     if isinstance(prompt, list):
         text_parts: list[str] = []
         for block in prompt:
             if not isinstance(block, dict):
                 continue
-            text = block.get("text")
-            if isinstance(text, str):
-                text_parts.append(text)
-            content = block.get("content")
-            if isinstance(content, str):
-                text_parts.append(content)
+            # Extract either "text" or "content", with "text" taking precedence
+            text = block.get("text") or block.get("content")
+            if isinstance(text, str):
+                text_parts.append(text)
         return "\n".join(text_parts)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@executor/agents/claude_code/standalone_chat_workspace.py` around lines 79 -
97, The function prompt_text_for_workspace may append both "text" and "content"
from a single block causing duplicated output; change the block extraction to
use a single prioritized field (e.g., prefer block["text"] if it's a str,
otherwise use block["content"] if it's a str) instead of appending both. Update
the loop over prompt blocks (referencing variables block and text_parts in
prompt_text_for_workspace) to check for "text" first and only fall back to
"content", then append that single chosen string to text_parts before continuing
and join as before.

})
}, [projects, getDeviceForProject])
const standaloneDevices = useMemo(() => sortStandaloneDevices(devices), [devices])
const isStandaloneMode = currentProjectId == null

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use strict equality operator === instead of ==.

The guideline requires TypeScript strict mode for all TypeScript files. Replace == with === for null checks to maintain type safety and comply with strict mode best practices.

🔧 Proposed fix
-  const isStandaloneMode = currentProjectId == null
+  const isStandaloneMode = currentProjectId === null || currentProjectId === undefined

Or more concisely:

-  const isStandaloneMode = currentProjectId == null
+  const isStandaloneMode = !currentProjectId

As per coding guidelines: Use TypeScript strict mode for all TypeScript files

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/components/chat/composer/ProjectWorkBar.tsx` at line 67, The
null-check using loose equality should be changed: update the isStandaloneMode
assignment (symbol: isStandaloneMode, variable: currentProjectId) to use strict
comparisons; replace "currentProjectId == null" with either "currentProjectId
=== null" if you only want to detect null, or with "currentProjectId === null ||
currentProjectId === undefined" if you intend to treat undefined the same as
null to satisfy TypeScript strict-mode rules.

} from './workbenchReducer'
import { WorkbenchContext } from './useWorkbench'

const WEWORK_CLIENT_ORIGIN = 'wework'

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Find all definitions of WEWORK_CLIENT_ORIGIN to ensure single source of truth

rg -n "WEWORK_CLIENT_ORIGIN\s*=" --type=ts

Repository: wecode-ai/Wegent

Length of output: 286


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate definitions and see surrounding context / exports
for f in wework/src/api/projects.ts wework/src/api/tasks.ts wework/src/features/workbench/WorkbenchProvider.tsx; do
  echo "---- $f (first 80 lines) ----"
  sed -n '1,80p' "$f"
done

# Find usages in the repo to see what layer owns the value
rg -n "WEWORK_CLIENT_ORIGIN" --type=ts --type=tsx wework/src | head -n 200

# Check for withClientOrigin usage patterns
rg -n "withClientOrigin" --type=ts --type=tsx wework/src | head -n 200

Repository: wecode-ai/Wegent

Length of output: 7695


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file="wework/src/features/workbench/WorkbenchProvider.tsx"

echo "== Occurrences of WEWORK_CLIENT_ORIGIN in $file =="
rg -n "WEWORK_CLIENT_ORIGIN" "$file"

echo
echo "== Context around each occurrence (lines 20 before/after) =="
# rg -n gives line numbers; use awk to extract them and show local context via sed
lines=$(rg -n "WEWORK_CLIENT_ORIGIN" "$file" | cut -d: -f1 | sort -n | uniq)
for ln in $lines; do
  start=$((ln-20))
  end=$((ln+20))
  echo "---- $file:$ln ----"
  sed -n "${start},${end}p" "$file"
done

echo
echo "== Also check whether WorkbenchProvider calls any API helpers that already embed client_origin =="
rg -n "client_origin|withClientOrigin" "$file"

Repository: wecode-ai/Wegent

Length of output: 4709


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Search for any export/re-export of WEWORK_CLIENT_ORIGIN
rg -n "export (const|{).*WEWORK_CLIENT_ORIGIN|WEWORK_CLIENT_ORIGIN" wework/src/api --type=ts --type=tsx

# Look for shared constants modules
fd -t f -a constants wework/src/api || true
fd -t f -a constants wework/src || true

# Find how chatStream/sendMessage uses client_origin
rg -n "sendMessage\\(|client_origin" wework/src/stream wework/src/features/workbench -S --type=ts --type=tsx | head -n 200

# Check whether createTaskApi or createProjectApi are used for the chat flow
rg -n "createProjectApi\\(|createTaskApi\\(|createProjectApi|createTaskApi" wework/src/features/workbench/WorkbenchProvider.tsx

Repository: wecode-ai/Wegent

Length of output: 209


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# 1) Find all definitions (already known, but ensure no other files)
echo "== Definitions =="
rg -n "const WEWORK_CLIENT_ORIGIN\s*=\s*['\"][^'\"]+['\"]" wework/src --type=ts --type=tsx || true

# 2) Check for exports of WEWORK_CLIENT_ORIGIN
echo
echo "== Exports / re-exports mentioning WEWORK_CLIENT_ORIGIN =="
rg -n "export (const|function|\\{[^}]*\\})|re-export|from .*WEWORK_CLIENT_ORIGIN" wework/src/api wework/src --type=ts --type=tsx || true

# 3) Look for any shared 'client_origin' helper or constants module
echo
echo "== client_origin helper/constants =="
rg -n "withClientOrigin|client_origin" wework/src/api --type=ts --type=tsx || true

# 4) Inspect stream usage (chat payload)
echo
echo "== stream/chatStream sendMessage payload usage =="
rg -n "sendMessage\\(|client_origin" wework/src/stream wework/src/features/workbench --type=ts --type=tsx | head -n 200

Repository: wecode-ai/Wegent

Length of output: 357


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# definitions in TS/TSX
echo "== Definitions =="
rg -n "const WEWORK_CLIENT_ORIGIN\s*=\s*['\"][^'\"]+['\"]" wework/src -g'*.ts' -g'*.tsx' || true

echo
echo "== Exports/re-exports involving WEWORK_CLIENT_ORIGIN =="
rg -n "export .*WEWORK_CLIENT_ORIGIN|re-export|from .*WEWORK_CLIENT_ORIGIN" wework/src/api wework/src -g'*.ts' -g'*.tsx' || true

echo
echo "== Search for withClientOrigin helper =="
rg -n "withClientOrigin" wework/src/api -g'*.ts' -g'*.tsx' || true

echo
echo "== Search for chat/task payload usage of client_origin in stream/chatStream =="
# Find sendMessage implementation(s)
rg -n "sendMessage\\s*\\(" wework/src/stream -g'*.ts' -g'*.tsx' || true

# Then inspect the chatStream file(s) likely involved
fd -t f -e ts -e tsx chatStream wework/src/stream wework/src -a || true
fd -t f -n chatStream wework/src/stream wework/src -a || true

# Show relevant portions of any chatStream files
for f in $(fd -a -t f -e ts -e tsx -e js -e jsx "chatStream" wework/src/stream wework/src | head -n 20); do
  echo "---- $f (first 200 lines) ----"
  sed -n '1,200p' "$f"
done

Repository: wecode-ai/Wegent

Length of output: 13001


Deduplicate WEWORK_CLIENT_ORIGIN into a single exported constant.

WEWORK_CLIENT_ORIGIN is defined separately in wework/src/features/workbench/WorkbenchProvider.tsx (line 43) and duplicated in wework/src/api/projects.ts (line 13) and wework/src/api/tasks.ts (line 11), while WorkbenchProvider uses it for client_origin in the chat/task payloads (lines 751, 792). Centralize it in a shared module (e.g., wework/src/api/constants) and import it in both the API helpers and WorkbenchProvider to avoid drift.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/features/workbench/WorkbenchProvider.tsx` at line 43, Duplicate
WEWORK_CLIENT_ORIGIN constants exist; create a single exported constant
(WEWORK_CLIENT_ORIGIN) in a new shared module (e.g., api/constants) and replace
the local definitions by importing that constant into WorkbenchProvider and the
API modules (projects.ts, tasks.ts); remove the duplicate local declarations so
all uses (including the client_origin assignments in WorkbenchProvider)
reference the single exported symbol.

@icycrystal4
icycrystal4 force-pushed the codex/wework-model-selector-ui branch from 1dde628 to ad1e3d5 Compare June 1, 2026 06:56

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
backend/app/services/chat/trigger/lifecycle.py (1)

195-210: 💤 Low value

Log line omits modelOptions.

The metadata update now writes modelOptions, but the info log at lines 205-210 only reports modelId and autoDeleteExecutor, so persisted model options won't be observable in logs. Optional, but cheap to include.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/app/services/chat/trigger/lifecycle.py` around lines 195 - 210, The
info log after updating task metadata omits the newly-set modelOptions; update
the logger.info call in prepare_execution_session to include modelOptions (e.g.,
serialize resolved_task_params.model_options with json.dumps or str) alongside
model_id and auto_delete_executor so the persisted modelOptions written to
task_crd.metadata.labels["modelOptions"] are visible in logs; reference the
existing symbols task_crd, resolved_task_params, and logger.info to locate and
modify the log invocation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@backend/app/services/chat/trigger/lifecycle.py`:
- Around line 195-210: The info log after updating task metadata omits the
newly-set modelOptions; update the logger.info call in prepare_execution_session
to include modelOptions (e.g., serialize resolved_task_params.model_options with
json.dumps or str) alongside model_id and auto_delete_executor so the persisted
modelOptions written to task_crd.metadata.labels["modelOptions"] are visible in
logs; reference the existing symbols task_crd, resolved_task_params, and
logger.info to locate and modify the log invocation.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a2256983-c0cf-4b5d-a2d7-b9ef869ccdb5

📥 Commits

Reviewing files that changed from the base of the PR and between d2349b4 and 1dde628.

📒 Files selected for processing (23)
  • backend/app/api/ws/chat_namespace.py
  • backend/app/api/ws/events.py
  • backend/app/schemas/task.py
  • backend/app/services/adapters/task_kinds/operations.py
  • backend/app/services/chat/storage/task_manager.py
  • backend/app/services/chat/trigger/lifecycle.py
  • wework/src/App.plugins.test.tsx
  • wework/src/components/chat/ChatInput.test.tsx
  • wework/src/components/chat/ChatInput.tsx
  • wework/src/components/chat/composer/ComposerToolbar.tsx
  • wework/src/components/chat/composer/ModelSelector.tsx
  • wework/src/components/chat/composer/ProjectChatComposer.tsx
  • wework/src/components/layout/DesktopWorkbenchLayout.test.tsx
  • wework/src/components/layout/MobileWorkbenchLayout.test.tsx
  • wework/src/components/layout/MobileWorkbenchLayout.tsx
  • wework/src/features/workbench/WorkbenchProvider.test.tsx
  • wework/src/features/workbench/WorkbenchProvider.tsx
  • wework/src/features/workbench/useWorkbenchModels.ts
  • wework/src/features/workbench/workbenchProjectChatHooks.test.tsx
  • wework/src/i18n/locales/en/common.json
  • wework/src/i18n/locales/zh-CN/common.json
  • wework/src/lib/model-ui.ts
  • wework/src/types/api.ts
✅ Files skipped from review due to trivial changes (1)
  • wework/src/features/workbench/workbenchProjectChatHooks.test.tsx
🚧 Files skipped from review as they are similar to previous changes (19)
  • wework/src/i18n/locales/en/common.json
  • backend/app/api/ws/chat_namespace.py
  • wework/src/App.plugins.test.tsx
  • wework/src/components/chat/composer/ComposerToolbar.tsx
  • wework/src/i18n/locales/zh-CN/common.json
  • wework/src/components/layout/MobileWorkbenchLayout.tsx
  • wework/src/features/workbench/WorkbenchProvider.test.tsx
  • backend/app/schemas/task.py
  • wework/src/components/chat/ChatInput.tsx
  • wework/src/features/workbench/useWorkbenchModels.ts
  • wework/src/lib/model-ui.ts
  • wework/src/components/layout/DesktopWorkbenchLayout.test.tsx
  • backend/app/services/chat/storage/task_manager.py
  • backend/app/api/ws/events.py
  • wework/src/types/api.ts
  • wework/src/components/chat/ChatInput.test.tsx
  • wework/src/components/chat/composer/ModelSelector.tsx
  • wework/src/features/workbench/WorkbenchProvider.tsx
  • backend/app/services/adapters/task_kinds/operations.py

@icycrystal4
icycrystal4 force-pushed the codex/wework-model-selector-ui branch from ad1e3d5 to c8663b7 Compare June 1, 2026 07:05

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
wework/src/components/chat/composer/ModelSelector.tsx (1)

26-237: ⚖️ Poor tradeoff

Consider extracting the menu content into subcomponents.

The ModelSelector component body is ~210 lines, well above the preferred per-function limit. The two-pane menu (family sidebar + right panel) and renderControlSection are good candidates to extract into smaller presentational components to improve readability and testability. As per coding guidelines, "Function length: Max 50 lines per function (preferred)".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/components/chat/composer/ModelSelector.tsx` around lines 26 - 237,
The ModelSelector component is too large; extract the menu parts into smaller
presentational components: create a ModelSelectorMenu (rendering the outer menu
container and grid), a FamilyList component (rendering the left column using
familyGroups, activeGroup, activeFamilyId and handlers like setActiveFamilyId),
a ModelList component (rendering the right column using activeGroup,
selectedModel, onSelectModel and controlsBelowModels), and a ControlSection
component to replace renderControlSection (accepting a ModelControlConfig and
onSelectModelOption, selectedModelOptions). Move related helpers/props
(controlsAboveFamilies, controlsBelowModels, selectedModelControls) into
ModelSelector and pass only needed data/callbacks to these new components to
keep ModelSelector focused and under the function-length guideline.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@wework/src/components/chat/composer/ModelSelector.tsx`:
- Around line 147-149: Hover-driven changes to activeFamilyId via the
onMouseEnter handler cause activeControlModel to toggle and recompute
controlsAboveFamilies (which for the 'gpt' family adds a family-scoped control),
producing layout shifts and hover jitter; remove the onMouseEnter call that sets
activeFamilyId in ModelSelector.tsx and retain only onFocus and onClick to
change activeFamilyId, or alternatively move rendering of controlsAboveFamilies
to a non-shifting region (e.g., right panel) so toggling family-specific
controls (from wework/src/lib/model-ui.ts) does not shift the family list.
- Around line 214-223: The setOpen updater in ModelSelector currently performs a
side effect by calling setActiveFamilyId inside the state updater; remove that
impure behavior by computing the nextOpen value outside the updater and invoking
setActiveFamilyId only when nextOpen is true (and only if activeFamilyId is not
already set) before calling setOpen(nextOpen). Specifically, change the onClick
handler to determine const nextOpen = !open (or use the current value read from
state), call setActiveFamilyId(selectedFamily ?? familyGroups[0]?.config.id ??
'') if opening and activeFamilyId is empty, then call setOpen(nextOpen); this
avoids running setActiveFamilyId inside the setOpen(state => ...) updater and
prevents duplicate runs that cause displayedFamilyId → activeGroup →
controlsAboveFamilies layout jitter when hovering.

---

Nitpick comments:
In `@wework/src/components/chat/composer/ModelSelector.tsx`:
- Around line 26-237: The ModelSelector component is too large; extract the menu
parts into smaller presentational components: create a ModelSelectorMenu
(rendering the outer menu container and grid), a FamilyList component (rendering
the left column using familyGroups, activeGroup, activeFamilyId and handlers
like setActiveFamilyId), a ModelList component (rendering the right column using
activeGroup, selectedModel, onSelectModel and controlsBelowModels), and a
ControlSection component to replace renderControlSection (accepting a
ModelControlConfig and onSelectModelOption, selectedModelOptions). Move related
helpers/props (controlsAboveFamilies, controlsBelowModels,
selectedModelControls) into ModelSelector and pass only needed data/callbacks to
these new components to keep ModelSelector focused and under the function-length
guideline.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 59642e5d-b98a-4389-92e6-33a532101ab5

📥 Commits

Reviewing files that changed from the base of the PR and between ad1e3d5 and c8663b7.

📒 Files selected for processing (23)
  • backend/app/api/ws/chat_namespace.py
  • backend/app/api/ws/events.py
  • backend/app/schemas/task.py
  • backend/app/services/adapters/task_kinds/operations.py
  • backend/app/services/chat/storage/task_manager.py
  • backend/app/services/chat/trigger/lifecycle.py
  • wework/src/App.plugins.test.tsx
  • wework/src/components/chat/ChatInput.test.tsx
  • wework/src/components/chat/ChatInput.tsx
  • wework/src/components/chat/composer/ComposerToolbar.tsx
  • wework/src/components/chat/composer/ModelSelector.tsx
  • wework/src/components/chat/composer/ProjectChatComposer.tsx
  • wework/src/components/layout/DesktopWorkbenchLayout.test.tsx
  • wework/src/components/layout/MobileWorkbenchLayout.test.tsx
  • wework/src/components/layout/MobileWorkbenchLayout.tsx
  • wework/src/features/workbench/WorkbenchProvider.test.tsx
  • wework/src/features/workbench/WorkbenchProvider.tsx
  • wework/src/features/workbench/useWorkbenchModels.ts
  • wework/src/features/workbench/workbenchProjectChatHooks.test.tsx
  • wework/src/i18n/locales/en/common.json
  • wework/src/i18n/locales/zh-CN/common.json
  • wework/src/lib/model-ui.ts
  • wework/src/types/api.ts
✅ Files skipped from review due to trivial changes (2)
  • wework/src/i18n/locales/zh-CN/common.json
  • wework/src/App.plugins.test.tsx
🚧 Files skipped from review as they are similar to previous changes (18)
  • wework/src/i18n/locales/en/common.json
  • backend/app/api/ws/chat_namespace.py
  • wework/src/components/layout/MobileWorkbenchLayout.test.tsx
  • wework/src/components/layout/MobileWorkbenchLayout.tsx
  • backend/app/services/chat/trigger/lifecycle.py
  • wework/src/types/api.ts
  • wework/src/components/chat/composer/ProjectChatComposer.tsx
  • wework/src/components/chat/ChatInput.tsx
  • wework/src/lib/model-ui.ts
  • wework/src/features/workbench/WorkbenchProvider.test.tsx
  • backend/app/services/chat/storage/task_manager.py
  • backend/app/schemas/task.py
  • wework/src/features/workbench/useWorkbenchModels.ts
  • wework/src/components/chat/ChatInput.test.tsx
  • wework/src/components/layout/DesktopWorkbenchLayout.test.tsx
  • backend/app/api/ws/events.py
  • backend/app/services/adapters/task_kinds/operations.py
  • wework/src/features/workbench/WorkbenchProvider.tsx

Comment on lines +147 to +149
onMouseEnter={() => setActiveFamilyId(group.config.id)}
onFocus={() => setActiveFamilyId(group.config.id)}
onClick={() => setActiveFamilyId(group.config.id)}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Hover-driven activeFamilyId changes can shift the family sidebar

onMouseEnter/onFocus/onClick all set activeFamilyId, which changes activeControlModel and therefore recomputes controlsAboveFamilies (rendered above the family list). In wework/src/lib/model-ui.ts, only the gpt family adds a family-scoped control (reasoning with scope: 'family'); claude, gemini, and kimi have controls: []. Switching families on hover can show/hide that above-controls section (length toggling), shifting the family buttons and potentially causing hover jitter. Consider removing onMouseEnter (keep onFocus/onClick) or moving the controls to a stable layout region (e.g., right panel).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/components/chat/composer/ModelSelector.tsx` around lines 147 -
149, Hover-driven changes to activeFamilyId via the onMouseEnter handler cause
activeControlModel to toggle and recompute controlsAboveFamilies (which for the
'gpt' family adds a family-scoped control), producing layout shifts and hover
jitter; remove the onMouseEnter call that sets activeFamilyId in
ModelSelector.tsx and retain only onFocus and onClick to change activeFamilyId,
or alternatively move rendering of controlsAboveFamilies to a non-shifting
region (e.g., right panel) so toggling family-specific controls (from
wework/src/lib/model-ui.ts) does not shift the family list.

Comment on lines +214 to +223
onClick={() => {
if (disabled) return
setOpen(current => {
const nextOpen = !current
if (nextOpen) {
setActiveFamilyId(selectedFamily ?? familyGroups[0]?.config.id ?? '')
}
return nextOpen
})
}}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

React 19 must state updater functions passed to setState be pure?

💡 Result:

Yes, updater functions passed to state setters (such as those returned by useState) must be pure [1]. In React 19, as in previous versions, updater functions run during the rendering process [1]. Because React may invoke these functions multiple times—for instance, to calculate state transitions within a batch or during Strict Mode development checks—they must be pure to ensure predictable behavior [1]. Key requirements for updater functions include: - Pureness: They should only calculate and return the next state based on the previous state provided as an argument [1]. - No Side Effects: They must not perform side effects, such as modifying other variables, triggering network requests, or calling other state setters [1]. React enforces this behavior during development in Strict Mode by invoking updater functions twice; the first result is discarded, and the second is used, which helps expose non-pure logic or unintended side effects [1]. Failing to keep these functions pure can lead to bugs, inconsistent UI states, or unnecessary re-renders [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="wework/src/components/chat/composer/ModelSelector.tsx"
# Print the relevant section with line numbers for context (around 214-223).
nl -ba "$FILE" | sed -n '180,250p'

Repository: wecode-ai/Wegent

Length of output: 103


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="wework/src/components/chat/composer/ModelSelector.tsx"

# Show a wider window around the reported lines, with line numbers
cat -n "$FILE" | sed -n '190,245p'

Repository: wecode-ai/Wegent

Length of output: 2071


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="wework/src/components/chat/composer/ModelSelector.tsx"

# 1) Locate open/setOpen + activeFamilyId definitions
rg -n "const \\[open\\]|setOpen\\b|aria-expanded=\\{open\\}" "$FILE"
rg -n "setActiveFamilyId\\b|activeFamilyId\\b" "$FILE"

# 2) Look for hover/focus/click handlers that setActiveFamilyId
rg -n "(onMouseEnter|onMouseLeave|onPointerEnter|onPointerLeave|onFocus|onBlur|onClick).*setActiveFamilyId|setActiveFamilyId.*(onMouseEnter|onMouseLeave|onPointerEnter|onPointerLeave|onFocus|onBlur|onClick)" "$FILE" || true

Repository: wecode-ai/Wegent

Length of output: 999


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="wework/src/components/chat/composer/ModelSelector.tsx"

# Inspect derived state / filtering logic around where activeFamilyId is used (near the earlier line numbers)
cat -n "$FILE" | sed -n '60,190p'

Repository: wecode-ai/Wegent

Length of output: 6836


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="wework/src/components/chat/composer/ModelSelector.tsx"

wc -l "$FILE"
echo "---- top of file ----"
cat -n "$FILE" | sed -n '1,90p'

echo "---- renderControlSection function length (rough) ----"
# Print the full function renderControlSection block for line range context
cat -n "$FILE" | sed -n '70,140p'

Repository: wecode-ai/Wegent

Length of output: 7521


Avoid side effects inside the setOpen state updater (and reduce menu jitter on hover)

  • In wework/src/components/chat/composer/ModelSelector.tsx (lines ~214-223), calling setActiveFamilyId(...) inside setOpen(current => ...) makes the updater impure; React may invoke updater functions multiple times in development, so this can run twice and cause inconsistent state.
  • activeFamilyId is also updated on onMouseEnter / onFocus for family buttons; since activeFamilyId feeds displayedFamilyId → activeGroup → controlsAboveFamilies, this can change the left column’s height and visually jitter while hovering near the family list.
♻️ Proposed refactor
-        onClick={() => {
-          if (disabled) return
-          setOpen(current => {
-            const nextOpen = !current
-            if (nextOpen) {
-              setActiveFamilyId(selectedFamily ?? familyGroups[0]?.config.id ?? '')
-            }
-            return nextOpen
-          })
-        }}
+        onClick={() => {
+          if (disabled) return
+          const nextOpen = !open
+          if (nextOpen) {
+            setActiveFamilyId(selectedFamily ?? familyGroups[0]?.config.id ?? '')
+          }
+          setOpen(nextOpen)
+        }}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onClick={() => {
if (disabled) return
setOpen(current => {
const nextOpen = !current
if (nextOpen) {
setActiveFamilyId(selectedFamily ?? familyGroups[0]?.config.id ?? '')
}
return nextOpen
})
}}
onClick={() => {
if (disabled) return
const nextOpen = !open
if (nextOpen) {
setActiveFamilyId(selectedFamily ?? familyGroups[0]?.config.id ?? '')
}
setOpen(nextOpen)
}}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wework/src/components/chat/composer/ModelSelector.tsx` around lines 214 -
223, The setOpen updater in ModelSelector currently performs a side effect by
calling setActiveFamilyId inside the state updater; remove that impure behavior
by computing the nextOpen value outside the updater and invoking
setActiveFamilyId only when nextOpen is true (and only if activeFamilyId is not
already set) before calling setOpen(nextOpen). Specifically, change the onClick
handler to determine const nextOpen = !open (or use the current value read from
state), call setActiveFamilyId(selectedFamily ?? familyGroups[0]?.config.id ??
'') if opening and activeFamilyId is empty, then call setOpen(nextOpen); this
avoids running setActiveFamilyId inside the setOpen(state => ...) updater and
prevents duplicate runs that cause displayedFamilyId → activeGroup →
controlsAboveFamilies layout jitter when hovering.

@icycrystal4
icycrystal4 force-pushed the codex/wework-model-selector-ui branch from c8663b7 to a2f7a9a Compare June 1, 2026 07:17
@icycrystal4 icycrystal4 changed the title feat(wework): add layered model selector feat(wework): persist scoped model selection Jun 1, 2026
@icycrystal4
icycrystal4 force-pushed the codex/wework-model-selector-ui branch 2 times, most recently from 1ba316e to 0ad0529 Compare June 2, 2026 03:18
@icycrystal4
icycrystal4 force-pushed the codex/wework-model-selector-ui branch from 0ad0529 to 9536876 Compare June 2, 2026 03:34
@qdaxb
qdaxb merged commit 8383014 into wecode-ai:main Jun 2, 2026
22 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants