Skip to content

Commit 3893537

Browse files
committed
feat: add BackgroundToggle component with state management and tooltip handling
1 parent 56e7c49 commit 3893537

File tree

4 files changed

+191
-0
lines changed

4 files changed

+191
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { useState } from 'react'
2+
import { BackgroundToggle } from './BackgroundToggle'
3+
import type { Meta, StoryObj } from '@storybook/react-vite'
4+
5+
const meta: Meta<typeof BackgroundToggle> = {
6+
title: 'UI/BackgroundToggle',
7+
component: BackgroundToggle,
8+
tags: ['autodocs'],
9+
argTypes: {
10+
selectedModel: {
11+
control: 'select',
12+
options: ['gpt-4o', 'gpt-4.1', 'gpt-3.5-turbo', 'claude-3-opus'],
13+
defaultValue: 'gpt-4o',
14+
},
15+
disabled: {
16+
control: 'boolean',
17+
defaultValue: false,
18+
},
19+
maxJobsReached: {
20+
control: 'boolean',
21+
defaultValue: false,
22+
},
23+
},
24+
}
25+
export default meta
26+
27+
type Story = StoryObj<typeof BackgroundToggle>
28+
29+
const Template = (args: any) => {
30+
const [useBackground, setUseBackground] = useState(args.useBackground ?? false)
31+
return (
32+
<BackgroundToggle
33+
{...args}
34+
useBackground={useBackground}
35+
onToggle={setUseBackground}
36+
/>
37+
)
38+
}
39+
40+
export const Default: Story = {
41+
render: Template,
42+
args: {
43+
useBackground: false,
44+
selectedModel: 'gpt-4o',
45+
disabled: false,
46+
maxJobsReached: false,
47+
},
48+
}
49+
50+
export const Enabled: Story = {
51+
render: Template,
52+
args: {
53+
useBackground: true,
54+
selectedModel: 'gpt-4o',
55+
disabled: false,
56+
maxJobsReached: false,
57+
},
58+
}
59+
60+
export const Disabled: Story = {
61+
render: Template,
62+
args: {
63+
useBackground: false,
64+
selectedModel: 'gpt-4o',
65+
disabled: true,
66+
maxJobsReached: false,
67+
},
68+
}
69+
70+
export const UnsupportedModel: Story = {
71+
render: Template,
72+
args: {
73+
useBackground: false,
74+
selectedModel: 'gpt-3.5-turbo',
75+
disabled: false,
76+
maxJobsReached: false,
77+
},
78+
}
79+
80+
export const MaxJobsReached: Story = {
81+
render: Template,
82+
args: {
83+
useBackground: false,
84+
selectedModel: 'gpt-4o',
85+
disabled: false,
86+
maxJobsReached: true,
87+
},
88+
}
89+
90+
export const UnsupportedModelWithMaxJobs: Story = {
91+
render: Template,
92+
args: {
93+
useBackground: false,
94+
selectedModel: 'claude-3-opus',
95+
disabled: false,
96+
maxJobsReached: true,
97+
},
98+
}

src/components/BackgroundToggle.tsx

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Clock } from 'lucide-react'
2+
import { ToolToggle } from './ToolToggle'
3+
import { isBackgroundSupported } from '@/lib/background-supported-models'
4+
5+
interface BackgroundToggleProps {
6+
useBackground: boolean
7+
onToggle: (enabled: boolean) => void
8+
selectedModel: string
9+
disabled?: boolean
10+
maxJobsReached?: boolean
11+
}
12+
13+
export function BackgroundToggle({
14+
useBackground,
15+
onToggle,
16+
selectedModel,
17+
disabled = false,
18+
maxJobsReached = false,
19+
}: BackgroundToggleProps) {
20+
const isSupported = isBackgroundSupported(selectedModel)
21+
22+
let tooltip = `Run requests in the background to continue using the chat interface.${!isSupported ? ` Not supported by ${selectedModel}` : ''}`
23+
24+
if (maxJobsReached) {
25+
tooltip =
26+
'Maximum number of concurrent background jobs reached. Please wait for existing jobs to complete.'
27+
}
28+
29+
return (
30+
<ToolToggle
31+
isSelected={useBackground}
32+
onToggle={onToggle}
33+
isSupported={isSupported && !maxJobsReached}
34+
icon={<Clock className="h-4 w-4" />}
35+
label="Run in background"
36+
tooltip={tooltip}
37+
disabled={disabled || !isSupported || maxJobsReached}
38+
/>
39+
)
40+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { isBackgroundSupported } from './background-supported-models'
3+
4+
describe('isBackgroundSupported', () => {
5+
it('returns true for exact model matches', () => {
6+
expect(isBackgroundSupported('gpt-4o')).toBe(true)
7+
expect(isBackgroundSupported('gpt-4.1')).toBe(true)
8+
expect(isBackgroundSupported('o3')).toBe(true)
9+
})
10+
11+
it('returns true for models that start with supported prefixes', () => {
12+
expect(isBackgroundSupported('gpt-4o-2024-05-13')).toBe(true)
13+
expect(isBackgroundSupported('gpt-4.1-preview')).toBe(true)
14+
expect(isBackgroundSupported('o3-mini')).toBe(true)
15+
})
16+
17+
it('is case insensitive', () => {
18+
expect(isBackgroundSupported('GPT-4O')).toBe(true)
19+
expect(isBackgroundSupported('Gpt-4.1')).toBe(true)
20+
expect(isBackgroundSupported('O3')).toBe(true)
21+
})
22+
23+
it('returns false for unsupported models', () => {
24+
expect(isBackgroundSupported('gpt-3.5-turbo')).toBe(false)
25+
expect(isBackgroundSupported('claude-3-sonnet')).toBe(false)
26+
expect(isBackgroundSupported('llama-2')).toBe(false)
27+
expect(isBackgroundSupported('random-model')).toBe(false)
28+
})
29+
30+
it('returns false for empty string', () => {
31+
expect(isBackgroundSupported('')).toBe(false)
32+
})
33+
34+
it('handles partial matches correctly', () => {
35+
expect(isBackgroundSupported('gpt-4')).toBe(false)
36+
expect(isBackgroundSupported('gpt')).toBe(false)
37+
expect(isBackgroundSupported('o')).toBe(false)
38+
})
39+
})
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const BACKGROUND_SUPPORTED_MODELS = [
2+
'gpt-4o',
3+
'gpt-4.1',
4+
'gpt-4.1-mini',
5+
'gpt-4.1-nano',
6+
'o3',
7+
'o4-mini',
8+
]
9+
10+
export function isBackgroundSupported(model: string): boolean {
11+
return BACKGROUND_SUPPORTED_MODELS.some((m) =>
12+
model.toLowerCase().startsWith(m.toLowerCase()),
13+
)
14+
}

0 commit comments

Comments
 (0)