Skip to content

Commit 18bb067

Browse files
authored
Merge pull request #637 from Yumiue/codex/fix-sqlite-session-concurrency
fix(web): 优化供应商配置模型展示与减少不必要的 Todo 冲突提示
2 parents b193d8f + abb96a3 commit 18bb067

4 files changed

Lines changed: 90 additions & 3 deletions

File tree

web/src/components/layout/Sidebar.test.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { beforeEach, describe, expect, it, vi } from 'vitest'
22
import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react'
3+
import { readFileSync } from 'node:fs'
34
import Sidebar from './Sidebar'
45
import { useChatStore } from '@/stores/useChatStore'
56
import { useGatewayStore } from '@/stores/useGatewayStore'
@@ -8,6 +9,7 @@ import { useUIStore } from '@/stores/useUIStore'
89
import { useWorkspaceStore } from '@/stores/useWorkspaceStore'
910

1011
let mockGatewayAPI: any = null
12+
const appCss = readFileSync('src/index.css', 'utf-8')
1113

1214
vi.mock('@/context/RuntimeProvider', () => ({
1315
useGatewayAPI: () => mockGatewayAPI,
@@ -199,6 +201,48 @@ describe('Sidebar ProviderModal', () => {
199201
expect(screen.getByText('switch failed')).toBeInTheDocument()
200202
})
201203

204+
it('keeps provider models in a single scrollable row when there are many models', async () => {
205+
mockGatewayAPI.listProviders = vi.fn().mockResolvedValue({
206+
payload: {
207+
providers: [
208+
{
209+
id: 'ark',
210+
name: 'Ark',
211+
source: 'custom',
212+
selected: false,
213+
models: Array.from({ length: 16 }, (_, index) => ({
214+
id: `ark-code-${index + 1}`,
215+
name: `ark-code-${index + 1}`,
216+
})),
217+
},
218+
],
219+
},
220+
})
221+
mockGatewayAPI.getSessionModel = vi.fn().mockResolvedValue({
222+
payload: {
223+
provider_id: 'openai',
224+
model_id: 'gpt-5.4',
225+
model_name: 'GPT-5.4',
226+
provider: 'openai',
227+
},
228+
})
229+
230+
render(<Sidebar />)
231+
fireEvent.click(screen.getByRole('button', { name: //i }))
232+
await screen.findByText('Ark')
233+
234+
const arkCard = providerCard('Ark')
235+
const models = arkCard.querySelector('.config-card-models')
236+
expect(models).toBeInstanceOf(HTMLElement)
237+
const modelRule = appCss.match(/\.config-card-models\s*{(?<body>[^}]*)}/)?.groups?.body ?? ''
238+
expect(modelRule).toContain('flex-wrap: nowrap')
239+
expect(modelRule).toContain('overflow-x: auto')
240+
expect(modelRule).toContain('overflow-y: hidden')
241+
expect(models?.querySelectorAll('.config-card-model-tag')).toHaveLength(16)
242+
expect(within(arkCard).getByRole('button', { name: //i })).toBeTruthy()
243+
expect(within(arkCard).getByRole('button', { name: //i })).toBeTruthy()
244+
})
245+
202246
it('only shows the expanded workspace style on the current workspace', async () => {
203247
const switchWorkspace = vi.fn().mockResolvedValue(undefined)
204248
useWorkspaceStore.setState({

web/src/index.css

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1475,13 +1475,29 @@ html, body, #root {
14751475
.config-card-info { flex: 1; min-width: 0; }
14761476
.config-card-name { font-size: 13px; font-weight: 600; color: var(--text-primary); }
14771477
.config-card-meta { font-size: 11px; color: var(--text-tertiary); margin-top: 2px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1478-
.config-card-models { display: flex; gap: 4px; flex-wrap: wrap; margin-top: 4px; }
1478+
.config-card-models {
1479+
display: flex;
1480+
gap: 4px;
1481+
flex-wrap: nowrap;
1482+
margin-top: 4px;
1483+
max-width: 100%;
1484+
overflow-x: auto;
1485+
overflow-y: hidden;
1486+
scrollbar-width: thin;
1487+
}
1488+
.config-card-models::-webkit-scrollbar { height: 4px; }
1489+
.config-card-models::-webkit-scrollbar-thumb {
1490+
background: var(--bg-active);
1491+
border-radius: var(--radius-full);
1492+
}
14791493
.config-card-model-tag {
14801494
font-size: 10px; padding: 1px 6px;
14811495
border-radius: var(--radius-sm);
14821496
background: var(--bg-active);
14831497
color: var(--text-secondary);
14841498
font-family: var(--font-mono);
1499+
flex: 0 0 auto;
1500+
white-space: nowrap;
14851501
}
14861502

14871503
/* ── Provider Card (in modals) ── */

web/src/utils/eventBridge.test.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -925,6 +925,29 @@ describe("eventBridge", () => {
925925
expect(useUIStore.getState().toasts).toHaveLength(0);
926926
});
927927

928+
it("TodoConflict invalid_transition does NOT show toast", () => {
929+
const api = createMockGatewayAPI();
930+
handleGatewayEvent(
931+
{
932+
type: EventType.TodoConflict,
933+
payload: {
934+
payload: {
935+
runtime_event_type: EventType.TodoConflict,
936+
payload: { action: "update", reason: "invalid_transition" },
937+
},
938+
},
939+
session_id: "sess-1",
940+
run_id: "run-1",
941+
},
942+
api,
943+
);
944+
945+
expect(useRuntimeInsightStore.getState().todoConflict?.reason).toBe(
946+
"invalid_transition",
947+
);
948+
expect(useUIStore.getState().toasts).toHaveLength(0);
949+
});
950+
928951
it("TodoConflict invalid_arguments shows info toast", () => {
929952
const api = createMockGatewayAPI();
930953
handleGatewayEvent(

web/src/utils/eventBridge.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -982,9 +982,13 @@ export function handleGatewayEvent(
982982
const payload = eventPayload as TodoEventPayload | undefined;
983983
if (payload) insightStore.setTodoConflict(payload);
984984
const reason = strField(eventPayload, "reason");
985-
// revision_conflict 是可恢复冲突,仅在面板显示,不弹全局 toast;
985+
// revision_conflict 与 invalid_transition 是可恢复冲突,仅在面板显示,不弹全局 toast;
986986
// 其余冲突降级为 info 避免打断聊天体验。
987-
if (reason && reason !== "revision_conflict") {
987+
if (
988+
reason &&
989+
reason !== "revision_conflict" &&
990+
reason !== "invalid_transition"
991+
) {
988992
uiStore.showToast(`Todo conflict: ${reason}`, "info");
989993
}
990994
break;

0 commit comments

Comments
 (0)