repoburg 1.3.188 → 1.3.196
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/agent-pane/.prettierignore +5 -0
- package/agent-pane/.prettierrc +8 -0
- package/agent-pane/backend/coverage/base.css +224 -0
- package/agent-pane/backend/coverage/block-navigation.js +87 -0
- package/agent-pane/backend/coverage/clover.xml +887 -0
- package/agent-pane/backend/coverage/coverage-final.json +10 -0
- package/agent-pane/backend/coverage/favicon.png +0 -0
- package/agent-pane/backend/coverage/index.html +176 -0
- package/agent-pane/backend/coverage/prettify.css +1 -0
- package/agent-pane/backend/coverage/prettify.js +2 -0
- package/agent-pane/backend/coverage/sort-arrow-sprite.png +0 -0
- package/agent-pane/backend/coverage/sorter.js +210 -0
- package/agent-pane/backend/coverage/src/codex/codex-provider.ts.html +1402 -0
- package/agent-pane/backend/coverage/src/codex/index.html +131 -0
- package/agent-pane/backend/coverage/src/codex/item-mapper.ts.html +331 -0
- package/agent-pane/backend/coverage/src/db/index.html +131 -0
- package/agent-pane/backend/coverage/src/db/index.ts.html +436 -0
- package/agent-pane/backend/coverage/src/db/schema.ts.html +175 -0
- package/agent-pane/backend/coverage/src/index.html +116 -0
- package/agent-pane/backend/coverage/src/index.ts.html +262 -0
- package/agent-pane/backend/coverage/src/routes/index.html +146 -0
- package/agent-pane/backend/coverage/src/routes/sessions.ts.html +991 -0
- package/agent-pane/backend/coverage/src/routes/turns.ts.html +256 -0
- package/agent-pane/backend/coverage/src/routes/workspaces.ts.html +406 -0
- package/agent-pane/backend/coverage/src/ws/index.html +116 -0
- package/agent-pane/backend/coverage/src/ws/index.ts.html +238 -0
- package/agent-pane/backend/data.db +0 -0
- package/agent-pane/backend/data.db-shm +0 -0
- package/agent-pane/backend/data.db-wal +0 -0
- package/agent-pane/backend/dist/index.js +70869 -0
- package/agent-pane/backend/package-lock.json +4529 -1013
- package/agent-pane/backend/package.json +10 -6
- package/agent-pane/backend/src/claude/claude-message-mapper.test.ts +163 -0
- package/agent-pane/backend/src/claude/claude-message-mapper.ts +154 -0
- package/agent-pane/backend/src/claude/claude-provider.test.ts +65 -0
- package/agent-pane/backend/src/claude/claude-provider.ts +482 -0
- package/agent-pane/backend/src/claude/claude-types.ts +33 -0
- package/agent-pane/backend/src/codex/app-server-client.test.ts +183 -0
- package/agent-pane/backend/src/codex/app-server-client.ts +225 -0
- package/agent-pane/backend/src/codex/app-server-types.ts +259 -0
- package/agent-pane/backend/src/codex/codex-provider-runtime-config.test.ts +56 -0
- package/agent-pane/backend/src/codex/codex-provider.test.ts +60 -0
- package/agent-pane/backend/src/codex/codex-provider.ts +521 -265
- package/agent-pane/backend/src/codex/item-mapper.test.ts +221 -0
- package/agent-pane/backend/src/codex/item-mapper.ts +35 -8
- package/agent-pane/backend/src/codex/shared-app-server.ts +25 -0
- package/agent-pane/backend/src/db/index.test.ts +125 -0
- package/agent-pane/backend/src/db/index.ts +39 -8
- package/agent-pane/backend/src/db/schema.ts +28 -74
- package/agent-pane/backend/src/index.ts +29 -2
- package/agent-pane/backend/src/integration/__fixtures__/cancel-turn.json +29 -0
- package/agent-pane/backend/src/integration/__fixtures__/delta-streaming.json +40 -0
- package/agent-pane/backend/src/integration/__fixtures__/failed-turn.json +16 -0
- package/agent-pane/backend/src/integration/__fixtures__/mcp-tool-call.json +60 -0
- package/agent-pane/backend/src/integration/__fixtures__/missing-item-started.json +34 -0
- package/agent-pane/backend/src/integration/__fixtures__/multi-item-turn.json +85 -0
- package/agent-pane/backend/src/integration/__fixtures__/multi-turn.json +48 -0
- package/agent-pane/backend/src/integration/__fixtures__/simple-turn.json +35 -0
- package/agent-pane/backend/src/integration/delta-streaming.test.ts +130 -0
- package/agent-pane/backend/src/integration/helpers/app-server-mock.ts +272 -0
- package/agent-pane/backend/src/integration/helpers/test-app.ts +108 -0
- package/agent-pane/backend/src/integration/helpers/ws-capture.ts +64 -0
- package/agent-pane/backend/src/integration/item-lifecycle.test.ts +133 -0
- package/agent-pane/backend/src/integration/mcp-tool-call.test.ts +102 -0
- package/agent-pane/backend/src/integration/missing-item-started.test.ts +91 -0
- package/agent-pane/backend/src/integration/multi-turn.test.ts +121 -0
- package/agent-pane/backend/src/integration/session-operations.test.ts +168 -0
- package/agent-pane/backend/src/integration/title-generation.test.ts +109 -0
- package/agent-pane/backend/src/integration/turn-cancel.test.ts +111 -0
- package/agent-pane/backend/src/integration/turn-failure.test.ts +97 -0
- package/agent-pane/backend/src/integration/turn-lifecycle.test.ts +141 -0
- package/agent-pane/backend/src/providers/index.ts +14 -0
- package/agent-pane/backend/src/providers/registry.test.ts +92 -0
- package/agent-pane/backend/src/providers/registry.ts +43 -0
- package/agent-pane/backend/src/providers/runtime-provider.ts +25 -0
- package/agent-pane/backend/src/routes/files.ts +31 -0
- package/agent-pane/backend/src/routes/mcp.ts +33 -0
- package/agent-pane/backend/src/routes/models.test.ts +92 -0
- package/agent-pane/backend/src/routes/models.ts +16 -0
- package/agent-pane/backend/src/routes/plugins.ts +40 -0
- package/agent-pane/backend/src/routes/providers.test.ts +69 -0
- package/agent-pane/backend/src/routes/providers.ts +57 -0
- package/agent-pane/backend/src/routes/sessions.test.ts +424 -0
- package/agent-pane/backend/src/routes/sessions.ts +295 -42
- package/agent-pane/backend/src/routes/skills.test.ts +114 -0
- package/agent-pane/backend/src/routes/skills.ts +70 -0
- package/agent-pane/backend/src/routes/turns.test.ts +152 -0
- package/agent-pane/backend/src/routes/turns.ts +36 -16
- package/agent-pane/backend/src/routes/workspaces.test.ts +197 -0
- package/agent-pane/backend/src/routes/workspaces.ts +73 -55
- package/agent-pane/backend/src/ws/index.test.ts +107 -0
- package/agent-pane/backend/src/ws/index.ts +19 -11
- package/agent-pane/backend/tsconfig.json +4 -2
- package/agent-pane/backend/vitest.config.ts +20 -0
- package/agent-pane/eslint.config.js +33 -0
- package/agent-pane/frontend/coverage/base.css +224 -0
- package/agent-pane/frontend/coverage/block-navigation.js +87 -0
- package/agent-pane/frontend/coverage/clover.xml +2962 -0
- package/agent-pane/frontend/coverage/coverage-final.json +37 -0
- package/agent-pane/frontend/coverage/favicon.png +0 -0
- package/agent-pane/frontend/coverage/index.html +251 -0
- package/agent-pane/frontend/coverage/prettify.css +1 -0
- package/agent-pane/frontend/coverage/prettify.js +2 -0
- package/agent-pane/frontend/coverage/sort-arrow-sprite.png +0 -0
- package/agent-pane/frontend/coverage/sorter.js +210 -0
- package/agent-pane/frontend/coverage/src/App.tsx.html +217 -0
- package/agent-pane/frontend/coverage/src/AppInitializer.tsx.html +223 -0
- package/agent-pane/frontend/coverage/src/api/client.ts.html +187 -0
- package/agent-pane/frontend/coverage/src/api/index.html +161 -0
- package/agent-pane/frontend/coverage/src/api/sessions.ts.html +316 -0
- package/agent-pane/frontend/coverage/src/api/websocket.ts.html +544 -0
- package/agent-pane/frontend/coverage/src/api/workspaces.ts.html +199 -0
- package/agent-pane/frontend/coverage/src/components/CollapsibleToolCalls.tsx.html +226 -0
- package/agent-pane/frontend/coverage/src/components/CommandInputBar.tsx.html +382 -0
- package/agent-pane/frontend/coverage/src/components/CommandPalette.tsx.html +715 -0
- package/agent-pane/frontend/coverage/src/components/IdeWorkspace.tsx.html +160 -0
- package/agent-pane/frontend/coverage/src/components/ModelSelector.tsx.html +478 -0
- package/agent-pane/frontend/coverage/src/components/NewSessionDialog.tsx.html +472 -0
- package/agent-pane/frontend/coverage/src/components/RenderArea.tsx.html +574 -0
- package/agent-pane/frontend/coverage/src/components/RightPanel.tsx.html +205 -0
- package/agent-pane/frontend/coverage/src/components/RuntimeIcon.tsx.html +133 -0
- package/agent-pane/frontend/coverage/src/components/SessionTabs.tsx.html +217 -0
- package/agent-pane/frontend/coverage/src/components/StatusBar.tsx.html +271 -0
- package/agent-pane/frontend/coverage/src/components/StatusDot.tsx.html +109 -0
- package/agent-pane/frontend/coverage/src/components/Toast.tsx.html +313 -0
- package/agent-pane/frontend/coverage/src/components/TurnContent.tsx.html +1216 -0
- package/agent-pane/frontend/coverage/src/components/WorkspaceSidebar.tsx.html +1138 -0
- package/agent-pane/frontend/coverage/src/components/index.html +326 -0
- package/agent-pane/frontend/coverage/src/components/panels/ChangesPanel.tsx.html +247 -0
- package/agent-pane/frontend/coverage/src/components/panels/LogsPanel.tsx.html +160 -0
- package/agent-pane/frontend/coverage/src/components/panels/MetricsPanel.tsx.html +427 -0
- package/agent-pane/frontend/coverage/src/components/panels/TurnsPanel.tsx.html +397 -0
- package/agent-pane/frontend/coverage/src/components/panels/index.html +161 -0
- package/agent-pane/frontend/coverage/src/constants/index.html +116 -0
- package/agent-pane/frontend/coverage/src/constants/models.ts.html +406 -0
- package/agent-pane/frontend/coverage/src/hooks/index.html +146 -0
- package/agent-pane/frontend/coverage/src/hooks/useEscapeKey.ts.html +124 -0
- package/agent-pane/frontend/coverage/src/hooks/useOutsideClick.ts.html +142 -0
- package/agent-pane/frontend/coverage/src/hooks/useSessionStatus.ts.html +187 -0
- package/agent-pane/frontend/coverage/src/index.html +146 -0
- package/agent-pane/frontend/coverage/src/main.tsx.html +115 -0
- package/agent-pane/frontend/coverage/src/store/index.html +116 -0
- package/agent-pane/frontend/coverage/src/store/store.ts.html +2404 -0
- package/agent-pane/frontend/coverage/src/test/component-helpers.ts.html +190 -0
- package/agent-pane/frontend/coverage/src/test/fixtures.ts.html +367 -0
- package/agent-pane/frontend/coverage/src/test/index.html +131 -0
- package/agent-pane/frontend/coverage/src/types/index.html +116 -0
- package/agent-pane/frontend/coverage/src/types/index.ts.html +466 -0
- package/agent-pane/frontend/coverage/src/utils/changes.ts.html +202 -0
- package/agent-pane/frontend/coverage/src/utils/duration.ts.html +127 -0
- package/agent-pane/frontend/coverage/src/utils/index.html +131 -0
- package/agent-pane/frontend/dist/assets/index-Bg0PF2aV.css +1 -0
- package/agent-pane/frontend/dist/assets/index-BlybeX5e.js +281 -0
- package/agent-pane/frontend/dist/index.html +19 -0
- package/agent-pane/frontend/package-lock.json +2434 -213
- package/agent-pane/frontend/package.json +10 -3
- package/agent-pane/frontend/src/App.test.tsx +74 -0
- package/agent-pane/frontend/src/App.tsx +1 -1
- package/agent-pane/frontend/src/AppInitializer.test.tsx +142 -0
- package/agent-pane/frontend/src/AppInitializer.tsx +1 -1
- package/agent-pane/frontend/src/api/client.test.ts +126 -0
- package/agent-pane/frontend/src/api/client.ts +22 -29
- package/agent-pane/frontend/src/api/models.ts +7 -0
- package/agent-pane/frontend/src/api/providers.ts +12 -0
- package/agent-pane/frontend/src/api/sessions.test.ts +241 -0
- package/agent-pane/frontend/src/api/sessions.ts +43 -1
- package/agent-pane/frontend/src/api/skills.ts +12 -0
- package/agent-pane/frontend/src/api/websocket.test.ts +288 -0
- package/agent-pane/frontend/src/api/websocket.ts +27 -2
- package/agent-pane/frontend/src/api/workspaces.test.ts +112 -0
- package/agent-pane/frontend/src/api/workspaces.ts +17 -3
- package/agent-pane/frontend/src/components/CollapsibleToolCalls.test.tsx +49 -0
- package/agent-pane/frontend/src/components/CollapsibleToolCalls.tsx +2 -5
- package/agent-pane/frontend/src/components/CommandInputBar.test.tsx +133 -0
- package/agent-pane/frontend/src/components/CommandInputBar.tsx +296 -17
- package/agent-pane/frontend/src/components/CommandPalette.test.tsx +230 -0
- package/agent-pane/frontend/src/components/CommandPalette.tsx +26 -23
- package/agent-pane/frontend/src/components/IdeWorkspace.tsx +1 -1
- package/agent-pane/frontend/src/components/ModelSelector.test.tsx +129 -0
- package/agent-pane/frontend/src/components/ModelSelector.tsx +191 -0
- package/agent-pane/frontend/src/components/NewSessionDialog.test.tsx +95 -0
- package/agent-pane/frontend/src/components/NewSessionDialog.tsx +58 -13
- package/agent-pane/frontend/src/components/RenderArea.test.tsx +160 -0
- package/agent-pane/frontend/src/components/RenderArea.tsx +18 -25
- package/agent-pane/frontend/src/components/RightPanel.test.tsx +43 -0
- package/agent-pane/frontend/src/components/RightPanel.tsx +1 -1
- package/agent-pane/frontend/src/components/RuntimeIcon.test.tsx +32 -0
- package/agent-pane/frontend/src/components/RuntimeIcon.tsx +1 -1
- package/agent-pane/frontend/src/components/SessionTabs.test.tsx +72 -0
- package/agent-pane/frontend/src/components/SessionTabs.tsx +12 -12
- package/agent-pane/frontend/src/components/StatusBar.test.tsx +56 -0
- package/agent-pane/frontend/src/components/StatusBar.tsx +25 -31
- package/agent-pane/frontend/src/components/StatusDot.test.tsx +25 -0
- package/agent-pane/frontend/src/components/StatusDot.tsx +2 -7
- package/agent-pane/frontend/src/components/Toast.test.tsx +79 -0
- package/agent-pane/frontend/src/components/Toast.tsx +2 -6
- package/agent-pane/frontend/src/components/TurnContent.test.tsx +287 -0
- package/agent-pane/frontend/src/components/TurnContent.tsx +107 -24
- package/agent-pane/frontend/src/components/WorkspaceSidebar.test.tsx +297 -0
- package/agent-pane/frontend/src/components/WorkspaceSidebar.tsx +33 -32
- package/agent-pane/frontend/src/components/input/OptionSubPopup.tsx +32 -0
- package/agent-pane/frontend/src/components/input/SkillMentionPopup.test.tsx +63 -0
- package/agent-pane/frontend/src/components/input/SkillMentionPopup.tsx +78 -0
- package/agent-pane/frontend/src/components/input/SlashPopup.test.tsx +96 -0
- package/agent-pane/frontend/src/components/input/SlashPopup.tsx +63 -0
- package/agent-pane/frontend/src/components/panels/ChangesPanel.test.tsx +61 -0
- package/agent-pane/frontend/src/components/panels/ChangesPanel.tsx +12 -39
- package/agent-pane/frontend/src/components/panels/LogsPanel.test.tsx +58 -0
- package/agent-pane/frontend/src/components/panels/LogsPanel.tsx +1 -1
- package/agent-pane/frontend/src/components/panels/MetricsPanel.test.tsx +71 -0
- package/agent-pane/frontend/src/components/panels/MetricsPanel.tsx +63 -31
- package/agent-pane/frontend/src/components/panels/TurnsPanel.test.tsx +83 -0
- package/agent-pane/frontend/src/components/panels/TurnsPanel.tsx +66 -7
- package/agent-pane/frontend/src/constants/models.test.ts +97 -0
- package/agent-pane/frontend/src/constants/models.ts +129 -0
- package/agent-pane/frontend/src/hooks/useEscapeKey.test.ts +42 -0
- package/agent-pane/frontend/src/hooks/useEscapeKey.ts +13 -0
- package/agent-pane/frontend/src/hooks/useOutsideClick.test.ts +92 -0
- package/agent-pane/frontend/src/hooks/useOutsideClick.ts +19 -0
- package/agent-pane/frontend/src/hooks/useProvider.ts +44 -0
- package/agent-pane/frontend/src/hooks/useSessionStatus.test.ts +114 -0
- package/agent-pane/frontend/src/hooks/useSessionStatus.ts +34 -0
- package/agent-pane/frontend/src/main.test.tsx +37 -0
- package/agent-pane/frontend/src/main.tsx +1 -1
- package/agent-pane/frontend/src/store/sessionSlice.ts +417 -0
- package/agent-pane/frontend/src/store/store.test.ts +1284 -0
- package/agent-pane/frontend/src/store/store.ts +16 -617
- package/agent-pane/frontend/src/store/uiSlice.ts +83 -0
- package/agent-pane/frontend/src/store/workspaceSlice.ts +77 -0
- package/agent-pane/frontend/src/store/wsSlice.ts +292 -0
- package/agent-pane/frontend/src/styles/app.css +113 -7
- package/agent-pane/frontend/src/test/component-helpers.ts +36 -0
- package/agent-pane/frontend/src/test/fixtures.ts +96 -0
- package/agent-pane/frontend/src/test-setup.ts +5 -0
- package/agent-pane/frontend/src/types/index.test.ts +128 -0
- package/agent-pane/frontend/src/types/index.ts +80 -104
- package/agent-pane/frontend/src/utils/changes.test.ts +81 -0
- package/agent-pane/frontend/src/utils/changes.ts +39 -0
- package/agent-pane/frontend/src/utils/duration.test.ts +71 -0
- package/agent-pane/frontend/src/utils/duration.ts +14 -0
- package/agent-pane/frontend/tsconfig.json +4 -1
- package/agent-pane/frontend/vite.config.ts +6 -0
- package/agent-pane/frontend/vitest.config.ts +29 -0
- package/agent-pane/package-lock.json +1527 -0
- package/agent-pane/package.json +19 -0
- package/agent-pane/shared/provider-types.ts +53 -0
- package/agent-pane/shared/types.ts +107 -0
- package/agentpane-branch-session.png +0 -0
- package/agentpane-expanded-all.png +0 -0
- package/agentpane-fixed-rendering.png +0 -0
- package/agentpane-focus-check.png +0 -0
- package/agentpane-initial.png +0 -0
- package/agentpane-mcp-detail.png +0 -0
- package/agentpane-no-focus-ring.png +0 -0
- package/agentpane-soft-focus.png +0 -0
- package/backend/dist/src/llm-provider/ollama-llm.provider.js +1 -0
- package/backend/dist/src/llm-provider/ollama-llm.provider.js.map +1 -1
- package/backend/dist/tsconfig.build.tsbuildinfo +1 -1
- package/data.db +0 -0
- package/empty-state.png +0 -0
- package/frontend-home.png +0 -0
- package/model-selector-open.png +0 -0
- package/package.json +1 -1
- package/slash-popup-working.png +0 -0
- package/agent-pane/frontend/src/components/DiffViewer.tsx +0 -27
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
|
|
3
|
+
import ModelSelector from './ModelSelector.tsx';
|
|
4
|
+
import { resetStore } from '../test/component-helpers.ts';
|
|
5
|
+
import { makeSession } from '../test/fixtures.ts';
|
|
6
|
+
|
|
7
|
+
describe('ModelSelector', () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
resetStore();
|
|
10
|
+
});
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
cleanup();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('returns null when no active session', () => {
|
|
16
|
+
const { container } = render(<ModelSelector />);
|
|
17
|
+
expect(container.firstChild).toBeNull();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('renders button with provider and model label', () => {
|
|
21
|
+
const session = makeSession('s1', { model: 'gpt-5.6-sol', model_reasoning_effort: 'low' });
|
|
22
|
+
resetStore({ activeSessionId: 's1', sessions: [session] });
|
|
23
|
+
render(<ModelSelector />);
|
|
24
|
+
const btn = document.querySelector('.model-selector-btn') as HTMLElement;
|
|
25
|
+
expect(btn.textContent).toContain('Codex');
|
|
26
|
+
expect(btn.textContent).toContain('GPT-5.6-Sol');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('opens popover with 3 columns on button click', () => {
|
|
30
|
+
const session = makeSession('s1', { model: 'gpt-5.6-sol', model_reasoning_effort: 'low' });
|
|
31
|
+
resetStore({ activeSessionId: 's1', sessions: [session] });
|
|
32
|
+
render(<ModelSelector />);
|
|
33
|
+
const btn = document.querySelector('.model-selector-btn') as HTMLElement;
|
|
34
|
+
fireEvent.click(btn);
|
|
35
|
+
expect(document.querySelector('.model-selector-popover')).toBeInTheDocument();
|
|
36
|
+
const headers = document.querySelectorAll('.model-selector-col-header');
|
|
37
|
+
expect(headers).toHaveLength(3);
|
|
38
|
+
expect(headers[0].textContent).toBe('Provider');
|
|
39
|
+
expect(headers[1].textContent).toBe('Model');
|
|
40
|
+
expect(headers[2].textContent).toBe('Thinking');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('selecting a model calls updateModelConfig and closes popover', async () => {
|
|
44
|
+
const session = makeSession('s1', { model: 'gpt-5.6-sol', model_reasoning_effort: 'low' });
|
|
45
|
+
const updateModelConfig = vi.fn().mockResolvedValue(undefined);
|
|
46
|
+
resetStore({ activeSessionId: 's1', sessions: [session], updateModelConfig });
|
|
47
|
+
render(<ModelSelector />);
|
|
48
|
+
fireEvent.click(document.querySelector('.model-selector-btn')!);
|
|
49
|
+
// Click on "GPT-5.6-Terra" model
|
|
50
|
+
const terraItem = Array.from(document.querySelectorAll('.model-selector-item')).find((el) =>
|
|
51
|
+
el.textContent?.includes('GPT-5.6-Terra'),
|
|
52
|
+
)!;
|
|
53
|
+
fireEvent.click(terraItem);
|
|
54
|
+
expect(updateModelConfig).toHaveBeenCalledWith('gpt-5.6-terra', expect.any(String));
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('selecting reasoning calls updateModelConfig with current model', async () => {
|
|
58
|
+
const session = makeSession('s1', { model: 'gpt-5.6-sol', model_reasoning_effort: 'low' });
|
|
59
|
+
const updateModelConfig = vi.fn().mockResolvedValue(undefined);
|
|
60
|
+
resetStore({ activeSessionId: 's1', sessions: [session], updateModelConfig });
|
|
61
|
+
render(<ModelSelector />);
|
|
62
|
+
fireEvent.click(document.querySelector('.model-selector-btn')!);
|
|
63
|
+
// Click on "High" reasoning level
|
|
64
|
+
const highItem = Array.from(document.querySelectorAll('.model-selector-item')).find((el) =>
|
|
65
|
+
el.textContent?.trim() === 'High',
|
|
66
|
+
)!;
|
|
67
|
+
fireEvent.click(highItem);
|
|
68
|
+
expect(updateModelConfig).toHaveBeenCalledWith('gpt-5.6-sol', 'high');
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('reasoning cascade: switching to model without current reasoning level switches to default', async () => {
|
|
72
|
+
// gpt-5.5 has levels [low, medium, high, xhigh] — no "max" or "ultra"
|
|
73
|
+
// gpt-5.6-sol has levels [low, medium, high, xhigh, max, ultra]
|
|
74
|
+
// Switch from gpt-5.6-sol (reasoning=ultra) to gpt-5.5 → ultra not supported → default for gpt-5.5 is "medium"
|
|
75
|
+
const session = makeSession('s1', { model: 'gpt-5.6-sol', model_reasoning_effort: 'ultra' });
|
|
76
|
+
const updateModelConfig = vi.fn().mockResolvedValue(undefined);
|
|
77
|
+
resetStore({ activeSessionId: 's1', sessions: [session], updateModelConfig });
|
|
78
|
+
render(<ModelSelector />);
|
|
79
|
+
fireEvent.click(document.querySelector('.model-selector-btn')!);
|
|
80
|
+
// Click on "GPT-5.5" model
|
|
81
|
+
const gpt55Item = Array.from(document.querySelectorAll('.model-selector-item')).find((el) =>
|
|
82
|
+
el.textContent?.includes('GPT-5.5'),
|
|
83
|
+
)!;
|
|
84
|
+
fireEvent.click(gpt55Item);
|
|
85
|
+
// gpt-5.5 defaultReasoning is "medium"
|
|
86
|
+
expect(updateModelConfig).toHaveBeenCalledWith('gpt-5.5', 'medium');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it('clicking a different provider calls updateModelConfig with runtimeType and keeps popover open', async () => {
|
|
90
|
+
const session = makeSession('s1', { runtime_type: 'codex', model: 'gpt-5.6-sol', model_reasoning_effort: 'low' });
|
|
91
|
+
const updateModelConfig = vi.fn().mockResolvedValue(undefined);
|
|
92
|
+
resetStore({ activeSessionId: 's1', sessions: [session], updateModelConfig });
|
|
93
|
+
render(<ModelSelector />);
|
|
94
|
+
fireEvent.click(document.querySelector('.model-selector-btn')!);
|
|
95
|
+
// Click on "Claude" provider
|
|
96
|
+
const claudeItem = Array.from(document.querySelectorAll('.model-selector-item')).find((el) =>
|
|
97
|
+
el.textContent?.includes('Claude'),
|
|
98
|
+
)!;
|
|
99
|
+
fireEvent.click(claudeItem);
|
|
100
|
+
expect(updateModelConfig).toHaveBeenCalledWith('', '', 'claude');
|
|
101
|
+
// Popover should stay open
|
|
102
|
+
expect(document.querySelector('.model-selector-popover')).toBeInTheDocument();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('outside click closes popover', () => {
|
|
106
|
+
const session = makeSession('s1', { model: 'gpt-5.6-sol', model_reasoning_effort: 'low' });
|
|
107
|
+
resetStore({ activeSessionId: 's1', sessions: [session] });
|
|
108
|
+
render(
|
|
109
|
+
<div>
|
|
110
|
+
<div data-testid="outside">Outside</div>
|
|
111
|
+
<ModelSelector />
|
|
112
|
+
</div>,
|
|
113
|
+
);
|
|
114
|
+
fireEvent.click(document.querySelector('.model-selector-btn')!);
|
|
115
|
+
expect(document.querySelector('.model-selector-popover')).toBeInTheDocument();
|
|
116
|
+
fireEvent.mouseDown(screen.getByTestId('outside'));
|
|
117
|
+
expect(document.querySelector('.model-selector-popover')).toBeNull();
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it('ESC closes popover', () => {
|
|
121
|
+
const session = makeSession('s1', { model: 'gpt-5.6-sol', model_reasoning_effort: 'low' });
|
|
122
|
+
resetStore({ activeSessionId: 's1', sessions: [session] });
|
|
123
|
+
render(<ModelSelector />);
|
|
124
|
+
fireEvent.click(document.querySelector('.model-selector-btn')!);
|
|
125
|
+
expect(document.querySelector('.model-selector-popover')).toBeInTheDocument();
|
|
126
|
+
fireEvent.keyDown(document, { key: 'Escape' });
|
|
127
|
+
expect(document.querySelector('.model-selector-popover')).toBeNull();
|
|
128
|
+
});
|
|
129
|
+
});
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { useStore } from '../store/store.ts';
|
|
3
|
+
import { PROVIDERS, getModels, getReasoningLevels, getDefaultReasoning } from '../constants/models.ts';
|
|
4
|
+
import { useOutsideClick } from '../hooks/useOutsideClick.ts';
|
|
5
|
+
import { useEscapeKey } from '../hooks/useEscapeKey.ts';
|
|
6
|
+
import { ChevronDown, Check, Lock } from 'lucide-react';
|
|
7
|
+
import type { ModelInfo } from '../types/index.ts';
|
|
8
|
+
|
|
9
|
+
export default function ModelSelector() {
|
|
10
|
+
const { activeSessionId, sessions, updateModelConfig } = useStore();
|
|
11
|
+
const showModelSelector = useStore((s) => s.showModelSelector);
|
|
12
|
+
const setShowModelSelector = useStore((s) => s.setShowModelSelector);
|
|
13
|
+
const session = sessions.find((s) => s.id === activeSessionId);
|
|
14
|
+
const storeModels = useStore((s) => s.models);
|
|
15
|
+
const modelsLoading = useStore((s) => s.modelsLoading);
|
|
16
|
+
const loadModels = useStore((s) => s.loadModels);
|
|
17
|
+
|
|
18
|
+
const [open, setOpen] = useState(false);
|
|
19
|
+
const ref = useRef<HTMLDivElement>(null);
|
|
20
|
+
|
|
21
|
+
useOutsideClick(ref, () => setOpen(false), open);
|
|
22
|
+
useEscapeKey(() => setOpen(false), open);
|
|
23
|
+
|
|
24
|
+
// Open when triggered by /model command
|
|
25
|
+
useEffect(() => {
|
|
26
|
+
if (showModelSelector) {
|
|
27
|
+
setOpen(true);
|
|
28
|
+
setShowModelSelector(false);
|
|
29
|
+
}
|
|
30
|
+
}, [showModelSelector, setShowModelSelector]);
|
|
31
|
+
|
|
32
|
+
// Load dynamic models on mount
|
|
33
|
+
useEffect(() => {
|
|
34
|
+
if (session?.runtime_type) {
|
|
35
|
+
loadModels(session.runtime_type);
|
|
36
|
+
}
|
|
37
|
+
}, [session?.runtime_type, loadModels]);
|
|
38
|
+
|
|
39
|
+
if (!session) return null;
|
|
40
|
+
|
|
41
|
+
const providerId = session.runtime_type;
|
|
42
|
+
const currentModel = session.model ?? '';
|
|
43
|
+
const currentReasoning = session.model_reasoning_effort ?? '';
|
|
44
|
+
|
|
45
|
+
// Use dynamic models from store, fallback to hardcoded
|
|
46
|
+
const dynamicModels: ModelInfo[] = storeModels.length > 0 ? storeModels : [];
|
|
47
|
+
const fallbackModels = getModels(providerId);
|
|
48
|
+
const baseModels = dynamicModels.length > 0 ? dynamicModels : fallbackModels;
|
|
49
|
+
|
|
50
|
+
// Prepend current model if not in the list (e.g. custom or Ollama models)
|
|
51
|
+
const models: ModelInfo[] = [...baseModels];
|
|
52
|
+
if (currentModel && !models.some((m) => m.slug === currentModel)) {
|
|
53
|
+
const ref = fallbackModels[0];
|
|
54
|
+
models.unshift({
|
|
55
|
+
slug: currentModel,
|
|
56
|
+
displayName: currentModel,
|
|
57
|
+
description: 'Custom model',
|
|
58
|
+
defaultReasoning: ref?.defaultReasoning ?? 'medium',
|
|
59
|
+
reasoningLevels: ref?.reasoningLevels ?? [],
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Find a model by slug
|
|
64
|
+
function findModel(slug: string): ModelInfo | undefined {
|
|
65
|
+
return models.find((m) => m.slug === slug);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Get reasoning levels for a model
|
|
69
|
+
function levelsFor(slug: string): { effort: string; label: string }[] {
|
|
70
|
+
if (dynamicModels.length > 0) {
|
|
71
|
+
return findModel(slug)?.reasoningLevels ?? [];
|
|
72
|
+
}
|
|
73
|
+
return getReasoningLevels(providerId, slug);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Get default reasoning for a model
|
|
77
|
+
function defaultFor(slug: string): string | undefined {
|
|
78
|
+
if (dynamicModels.length > 0) {
|
|
79
|
+
return findModel(slug)?.defaultReasoning;
|
|
80
|
+
}
|
|
81
|
+
return getDefaultReasoning(providerId, slug);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const provider = PROVIDERS.find((p) => p.id === providerId);
|
|
85
|
+
const modelDef = findModel(currentModel);
|
|
86
|
+
const reasoningLevels = currentModel ? levelsFor(currentModel) : [];
|
|
87
|
+
|
|
88
|
+
const providerLabel = provider?.label ?? providerId;
|
|
89
|
+
const modelLabel = modelDef?.displayName ?? (currentModel || 'Default');
|
|
90
|
+
const reasoningDef = reasoningLevels.find((r) => r.effort === currentReasoning);
|
|
91
|
+
const reasoningLabel = reasoningDef?.label ?? (currentReasoning || 'Default');
|
|
92
|
+
|
|
93
|
+
function handleSelectModel(modelSlug: string) {
|
|
94
|
+
const levels = levelsFor(modelSlug);
|
|
95
|
+
const stillSupported = levels.some((r) => r.effort === currentReasoning);
|
|
96
|
+
const newReasoning = stillSupported
|
|
97
|
+
? currentReasoning
|
|
98
|
+
: (defaultFor(modelSlug) ?? levels[0]?.effort ?? '');
|
|
99
|
+
updateModelConfig(modelSlug, newReasoning);
|
|
100
|
+
setOpen(false);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function handleSelectReasoning(effort: string) {
|
|
104
|
+
updateModelConfig(currentModel, effort);
|
|
105
|
+
setOpen(false);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<div className="model-selector" ref={ref}>
|
|
110
|
+
<button className="model-selector-btn" onClick={() => setOpen(!open)} title="Change model and thinking level">
|
|
111
|
+
<span className="model-selector-provider">{providerLabel}</span>
|
|
112
|
+
<span className="model-selector-sep">›</span>
|
|
113
|
+
<span className="model-selector-model">{modelLabel}</span>
|
|
114
|
+
{reasoningLabel !== 'Default' && (
|
|
115
|
+
<>
|
|
116
|
+
<span className="model-selector-sep">›</span>
|
|
117
|
+
<span className="model-selector-reasoning">{reasoningLabel}</span>
|
|
118
|
+
</>
|
|
119
|
+
)}
|
|
120
|
+
<ChevronDown size={12} className="model-selector-chevron" />
|
|
121
|
+
</button>
|
|
122
|
+
|
|
123
|
+
{open && (
|
|
124
|
+
<div className="model-selector-popover">
|
|
125
|
+
{/* Column 1: Provider */}
|
|
126
|
+
<div className="model-selector-column">
|
|
127
|
+
<div className="model-selector-col-header">Provider</div>
|
|
128
|
+
{PROVIDERS.map((p) => (
|
|
129
|
+
<div
|
|
130
|
+
key={p.id}
|
|
131
|
+
className={`model-selector-item ${p.id === providerId ? 'selected' : ''} ${!p.available ? 'disabled' : ''}`}
|
|
132
|
+
onClick={() => {
|
|
133
|
+
if (!p.available) return;
|
|
134
|
+
if (p.id === providerId) { setOpen(false); return; }
|
|
135
|
+
// Switch provider — clear model and reasoning, keep popover open
|
|
136
|
+
updateModelConfig('', '', p.id);
|
|
137
|
+
}}
|
|
138
|
+
>
|
|
139
|
+
<span>{p.label}</span>
|
|
140
|
+
{!p.available && (
|
|
141
|
+
<span className="model-selector-coming-soon">
|
|
142
|
+
<Lock size={10} /> Soon
|
|
143
|
+
</span>
|
|
144
|
+
)}
|
|
145
|
+
{p.id === providerId && <Check size={12} />}
|
|
146
|
+
</div>
|
|
147
|
+
))}
|
|
148
|
+
</div>
|
|
149
|
+
|
|
150
|
+
{/* Column 2: Model */}
|
|
151
|
+
<div className="model-selector-column">
|
|
152
|
+
<div className="model-selector-col-header">Model</div>
|
|
153
|
+
{modelsLoading && <div className="model-selector-empty">Loading models…</div>}
|
|
154
|
+
{!modelsLoading && models.length === 0 && (
|
|
155
|
+
<div className="model-selector-empty">No models available</div>
|
|
156
|
+
)}
|
|
157
|
+
{models.map((m) => (
|
|
158
|
+
<div
|
|
159
|
+
key={m.slug}
|
|
160
|
+
className={`model-selector-item ${m.slug === currentModel ? 'selected' : ''}`}
|
|
161
|
+
onClick={() => handleSelectModel(m.slug)}
|
|
162
|
+
>
|
|
163
|
+
<div className="model-selector-item-main">
|
|
164
|
+
<span className="model-selector-item-name">{m.displayName}</span>
|
|
165
|
+
<span className="model-selector-item-desc">{m.description}</span>
|
|
166
|
+
</div>
|
|
167
|
+
{m.slug === currentModel && <Check size={12} />}
|
|
168
|
+
</div>
|
|
169
|
+
))}
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
{/* Column 3: Thinking Level */}
|
|
173
|
+
<div className="model-selector-column">
|
|
174
|
+
<div className="model-selector-col-header">Thinking</div>
|
|
175
|
+
{reasoningLevels.length === 0 && <div className="model-selector-empty">Select a model first</div>}
|
|
176
|
+
{reasoningLevels.map((r) => (
|
|
177
|
+
<div
|
|
178
|
+
key={r.effort}
|
|
179
|
+
className={`model-selector-item ${r.effort === currentReasoning ? 'selected' : ''}`}
|
|
180
|
+
onClick={() => handleSelectReasoning(r.effort)}
|
|
181
|
+
>
|
|
182
|
+
<span>{r.label}</span>
|
|
183
|
+
{r.effort === currentReasoning && <Check size={12} />}
|
|
184
|
+
</div>
|
|
185
|
+
))}
|
|
186
|
+
</div>
|
|
187
|
+
</div>
|
|
188
|
+
)}
|
|
189
|
+
</div>
|
|
190
|
+
);
|
|
191
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
2
|
+
import { render, screen, fireEvent, cleanup } from '@testing-library/react';
|
|
3
|
+
import NewSessionDialog from './NewSessionDialog.tsx';
|
|
4
|
+
import { resetStore } from '../test/component-helpers.ts';
|
|
5
|
+
|
|
6
|
+
describe('NewSessionDialog', () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
resetStore();
|
|
9
|
+
});
|
|
10
|
+
afterEach(() => {
|
|
11
|
+
cleanup();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
it('returns null when showNewSessionDialog is false', () => {
|
|
15
|
+
const { container } = render(<NewSessionDialog />);
|
|
16
|
+
expect(container.firstChild).toBeNull();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('renders form with title, model, and cwd inputs when visible', () => {
|
|
20
|
+
resetStore({ showNewSessionDialog: true });
|
|
21
|
+
render(<NewSessionDialog />);
|
|
22
|
+
expect(screen.getByPlaceholderText('e.g. refactor auth module')).toBeInTheDocument();
|
|
23
|
+
expect(screen.getByPlaceholderText('e.g. /Users/me/projects/my-app')).toBeInTheDocument();
|
|
24
|
+
expect(screen.getByText('Create')).toBeInTheDocument();
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it('model select shows codex models', () => {
|
|
28
|
+
resetStore({ showNewSessionDialog: true });
|
|
29
|
+
render(<NewSessionDialog />);
|
|
30
|
+
const modelSelect = document.querySelector('select') as HTMLSelectElement;
|
|
31
|
+
// Default option + 4 codex models
|
|
32
|
+
expect(modelSelect.options.length).toBe(5);
|
|
33
|
+
expect(Array.from(modelSelect.options).map((o) => o.textContent)).toEqual(
|
|
34
|
+
expect.arrayContaining(['Default (Codex picks)', 'GPT-5.6-Sol — Flagship — complex, open-ended work']),
|
|
35
|
+
);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('selecting a model shows reasoning levels', () => {
|
|
39
|
+
resetStore({ showNewSessionDialog: true });
|
|
40
|
+
render(<NewSessionDialog />);
|
|
41
|
+
const modelSelect = document.querySelector('select') as HTMLSelectElement;
|
|
42
|
+
fireEvent.change(modelSelect, { target: { value: 'gpt-5.6-sol' } });
|
|
43
|
+
// Reasoning level select should appear (second select)
|
|
44
|
+
const reasoningSelect = document.querySelectorAll('select')[1];
|
|
45
|
+
expect(reasoningSelect).toBeTruthy();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it('changing model to one without current reasoning level switches to default', () => {
|
|
49
|
+
resetStore({
|
|
50
|
+
showNewSessionDialog: true,
|
|
51
|
+
lastModelConfig: { provider: 'codex', model: 'gpt-5.6-sol', reasoning: 'ultra' },
|
|
52
|
+
});
|
|
53
|
+
render(<NewSessionDialog />);
|
|
54
|
+
// gpt-5.6-sol has reasoning 'ultra', but gpt-5.5 only goes up to 'xhigh'
|
|
55
|
+
// First select gpt-5.6-sol to get reasoning levels visible
|
|
56
|
+
const selects = document.querySelectorAll('select');
|
|
57
|
+
const modelSelect = selects[0];
|
|
58
|
+
fireEvent.change(modelSelect, { target: { value: 'gpt-5.6-sol' } });
|
|
59
|
+
// Now select gpt-5.5
|
|
60
|
+
fireEvent.change(modelSelect, { target: { value: 'gpt-5.5' } });
|
|
61
|
+
// gpt-5.5 defaultReasoning is 'medium'
|
|
62
|
+
const reasoningSelect = document.querySelectorAll('select')[1] as HTMLSelectElement;
|
|
63
|
+
expect(reasoningSelect.value).toBe('medium');
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it('Create button calls createSession with form values', async () => {
|
|
67
|
+
const createSession = vi.fn().mockResolvedValue(undefined);
|
|
68
|
+
resetStore({ showNewSessionDialog: true, createSession });
|
|
69
|
+
render(<NewSessionDialog />);
|
|
70
|
+
const titleInput = screen.getByPlaceholderText('e.g. refactor auth module');
|
|
71
|
+
fireEvent.change(titleInput, { target: { value: 'My Session' } });
|
|
72
|
+
fireEvent.click(screen.getByText('Create'));
|
|
73
|
+
expect(createSession).toHaveBeenCalledWith(expect.objectContaining({
|
|
74
|
+
title: 'My Session',
|
|
75
|
+
runtime_type: 'codex',
|
|
76
|
+
}));
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('ESC closes dialog', () => {
|
|
80
|
+
const setShowNewSessionDialog = vi.fn();
|
|
81
|
+
resetStore({ showNewSessionDialog: true, setShowNewSessionDialog });
|
|
82
|
+
render(<NewSessionDialog />);
|
|
83
|
+
fireEvent.keyDown(document.querySelector('.dialog')!, { key: 'Escape' });
|
|
84
|
+
expect(setShowNewSessionDialog).toHaveBeenCalledWith(false);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('overlay click closes dialog', () => {
|
|
88
|
+
const setShowNewSessionDialog = vi.fn();
|
|
89
|
+
resetStore({ showNewSessionDialog: true, setShowNewSessionDialog });
|
|
90
|
+
render(<NewSessionDialog />);
|
|
91
|
+
const overlay = document.querySelector('.dialog-overlay')!;
|
|
92
|
+
fireEvent.click(overlay);
|
|
93
|
+
expect(setShowNewSessionDialog).toHaveBeenCalledWith(false);
|
|
94
|
+
});
|
|
95
|
+
});
|
|
@@ -1,24 +1,34 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
2
|
import { useStore } from '../store/store.ts';
|
|
3
3
|
import { X } from 'lucide-react';
|
|
4
|
+
import { getModels, getReasoningLevels, getDefaultReasoning } from '../constants/models.ts';
|
|
4
5
|
|
|
5
6
|
export default function NewSessionDialog() {
|
|
6
|
-
const { showNewSessionDialog, setShowNewSessionDialog, createSession } = useStore();
|
|
7
|
+
const { showNewSessionDialog, setShowNewSessionDialog, createSession, lastModelConfig } = useStore();
|
|
8
|
+
|
|
9
|
+
const initialProvider = lastModelConfig?.provider ?? 'codex';
|
|
10
|
+
const initialModel = lastModelConfig?.model ?? '';
|
|
11
|
+
const initialReasoning = lastModelConfig?.reasoning ?? '';
|
|
12
|
+
|
|
7
13
|
const [title, setTitle] = useState('');
|
|
8
|
-
const [model, setModel] = useState(
|
|
14
|
+
const [model, setModel] = useState(initialModel);
|
|
15
|
+
const [reasoning, setReasoning] = useState(initialReasoning);
|
|
9
16
|
const [cwd, setCwd] = useState('');
|
|
10
17
|
|
|
11
18
|
if (!showNewSessionDialog) return null;
|
|
12
19
|
|
|
20
|
+
const models = getModels(initialProvider);
|
|
21
|
+
const reasoningLevels = model ? getReasoningLevels(initialProvider, model) : [];
|
|
22
|
+
|
|
13
23
|
function handleSubmit() {
|
|
14
24
|
createSession({
|
|
15
25
|
title: title.trim() || undefined,
|
|
16
|
-
model: model
|
|
26
|
+
model: model || undefined,
|
|
27
|
+
model_reasoning_effort: reasoning || undefined,
|
|
17
28
|
cwd: cwd.trim() || undefined,
|
|
18
|
-
runtime_type:
|
|
29
|
+
runtime_type: initialProvider,
|
|
19
30
|
});
|
|
20
31
|
setTitle('');
|
|
21
|
-
setModel('');
|
|
22
32
|
setCwd('');
|
|
23
33
|
}
|
|
24
34
|
|
|
@@ -28,6 +38,16 @@ export default function NewSessionDialog() {
|
|
|
28
38
|
}
|
|
29
39
|
}
|
|
30
40
|
|
|
41
|
+
function handleModelChange(slug: string) {
|
|
42
|
+
setModel(slug);
|
|
43
|
+
// If current reasoning is not supported by new model, switch to default
|
|
44
|
+
const levels = getReasoningLevels(initialProvider, slug);
|
|
45
|
+
const stillSupported = levels.some((r) => r.effort === reasoning);
|
|
46
|
+
if (!stillSupported) {
|
|
47
|
+
setReasoning(getDefaultReasoning(initialProvider, slug) ?? levels[0]?.effort ?? '');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
31
51
|
return (
|
|
32
52
|
<div className="dialog-overlay" onClick={() => setShowNewSessionDialog(false)}>
|
|
33
53
|
<div className="dialog" onClick={(e) => e.stopPropagation()} onKeyDown={handleKeyDown}>
|
|
@@ -49,16 +69,41 @@ export default function NewSessionDialog() {
|
|
|
49
69
|
autoFocus
|
|
50
70
|
/>
|
|
51
71
|
</label>
|
|
72
|
+
|
|
52
73
|
<label className="dialog-label">
|
|
53
|
-
Model
|
|
54
|
-
<
|
|
55
|
-
className="dialog-input"
|
|
56
|
-
type="text"
|
|
74
|
+
Model
|
|
75
|
+
<select
|
|
76
|
+
className="dialog-input dialog-select"
|
|
57
77
|
value={model}
|
|
58
|
-
onChange={(e) =>
|
|
59
|
-
|
|
60
|
-
|
|
78
|
+
onChange={(e) => handleModelChange(e.target.value)}
|
|
79
|
+
>
|
|
80
|
+
<option value="">Default (Codex picks)</option>
|
|
81
|
+
{models.map((m) => (
|
|
82
|
+
<option key={m.slug} value={m.slug}>
|
|
83
|
+
{m.displayName} — {m.description}
|
|
84
|
+
</option>
|
|
85
|
+
))}
|
|
86
|
+
</select>
|
|
61
87
|
</label>
|
|
88
|
+
|
|
89
|
+
{reasoningLevels.length > 0 && (
|
|
90
|
+
<label className="dialog-label">
|
|
91
|
+
Thinking Level
|
|
92
|
+
<select
|
|
93
|
+
className="dialog-input dialog-select"
|
|
94
|
+
value={reasoning}
|
|
95
|
+
onChange={(e) => setReasoning(e.target.value)}
|
|
96
|
+
>
|
|
97
|
+
<option value="">Default ({getDefaultReasoning(initialProvider, model)})</option>
|
|
98
|
+
{reasoningLevels.map((r) => (
|
|
99
|
+
<option key={r.effort} value={r.effort}>
|
|
100
|
+
{r.label}
|
|
101
|
+
</option>
|
|
102
|
+
))}
|
|
103
|
+
</select>
|
|
104
|
+
</label>
|
|
105
|
+
)}
|
|
106
|
+
|
|
62
107
|
<label className="dialog-label">
|
|
63
108
|
Working Directory (optional)
|
|
64
109
|
<input
|
|
@@ -81,4 +126,4 @@ export default function NewSessionDialog() {
|
|
|
81
126
|
</div>
|
|
82
127
|
</div>
|
|
83
128
|
);
|
|
84
|
-
}
|
|
129
|
+
}
|