Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions frontends/ui/src/features/layout/components/ChatArea.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@ import userEvent from '@testing-library/user-event'
import { vi, describe, test, expect, beforeEach } from 'vitest'
import { ChatArea } from './ChatArea'

vi.mock('../store', () => ({
useLayoutStore: vi.fn((selector: (s: { newUiEnabled: boolean }) => unknown) =>
selector({ newUiEnabled: false })
),
}))

import { useLayoutStore } from '../store'

// Mock the chat store
const mockRespondToPrompt = vi.fn()
const mockDismissErrorCard = vi.fn()
Expand Down Expand Up @@ -41,6 +49,9 @@ import { useChatStore } from '@/features/chat'
describe('ChatArea', () => {
beforeEach(() => {
vi.clearAllMocks()
vi.mocked(useLayoutStore).mockImplementation((selector) =>
selector({ newUiEnabled: false } as never)
)
})

test('renders welcome state when not authenticated', () => {
Expand All @@ -58,6 +69,18 @@ describe('ChatArea', () => {
expect(screen.getByText(/AI-powered research companion/i)).toBeInTheDocument()
})

test('renders new UI welcome copy when newUiEnabled is true', () => {
vi.mocked(useLayoutStore).mockImplementation((selector) =>
selector({ newUiEnabled: true } as never)
)

render(<ChatArea isAuthenticated={true} />)

expect(screen.getByText('Welcome to AI-Q')).toBeInTheDocument()
expect(screen.getByText('Deep Research')).toBeInTheDocument()
expect(screen.getByText(/on-premise data/i)).toBeInTheDocument()
})

test('calls onSignIn when sign in button clicked', async () => {
const user = userEvent.setup()
const onSignIn = vi.fn()
Expand Down
18 changes: 14 additions & 4 deletions frontends/ui/src/features/layout/components/ChatArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { Document, Lock } from '@/adapters/ui/icons'
import { useChatStore, AgentPrompt, AgentResponse, ErrorBanner, FileUploadBanner, DeepResearchBanner, UserMessage, ChatThinking } from '@/features/chat'
import type { ChatMessage } from '@/features/chat'
import { StarfieldAnimation } from '@/shared/components/StarfieldAnimation'
import { useLayoutStore } from '../store'

interface ChatAreaProps {
/** Whether the user is authenticated */
Expand Down Expand Up @@ -339,6 +340,8 @@ interface WelcomeStateProps {
}

const WelcomeState: FC<WelcomeStateProps> = ({ isAuthenticated = false, onSignIn }) => {
const newUiEnabled = useLayoutStore((s) => s.newUiEnabled)

if (!isAuthenticated) {
// Logged out state - prompt to sign in
return (
Expand Down Expand Up @@ -393,10 +396,17 @@ const WelcomeState: FC<WelcomeStateProps> = ({ isAuthenticated = false, onSignIn
<Text kind="title/lg" className="text-primary">
Welcome to AI-Q
</Text>
<Text kind="body/regular/md" className="text-subtle">
Your AI-powered research companion for exploring technical documentation, market analysis,
and more.
</Text>
{newUiEnabled ? (
<Text kind="body/regular/md" className="text-primary">
Your AI-powered <span className="font-semibold">Deep Research</span> companion for exploring technical
documentation, market analysis, and insights from your on-premise data, web sources, and local files.
</Text>
) : (
<Text kind="body/regular/md" className="text-subtle">
Your AI-powered research companion for exploring technical documentation, market analysis,
and more.
</Text>
)}
</Flex>

</Flex>
Expand Down
19 changes: 19 additions & 0 deletions frontends/ui/src/features/layout/components/SettingsPanel.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SettingsPanel } from './SettingsPanel'
const mockCloseRightPanel = vi.fn()
const mockOpenRightPanel = vi.fn()
const mockSetTheme = vi.fn()
const mockSetNewUiEnabled = vi.fn()

vi.mock('../store', () => ({
useLayoutStore: vi.fn(() => ({
Expand All @@ -18,6 +19,8 @@ vi.mock('../store', () => ({
openRightPanel: mockOpenRightPanel,
theme: 'system',
setTheme: mockSetTheme,
newUiEnabled: false,
setNewUiEnabled: mockSetNewUiEnabled,
})),
}))

Expand All @@ -33,6 +36,8 @@ describe('SettingsPanel', () => {
openRightPanel: mockOpenRightPanel,
theme: 'system',
setTheme: mockSetTheme,
newUiEnabled: false,
setNewUiEnabled: mockSetNewUiEnabled,
})
})

Expand All @@ -56,6 +61,8 @@ describe('SettingsPanel', () => {
openRightPanel: mockOpenRightPanel,
theme: 'dark',
setTheme: mockSetTheme,
newUiEnabled: false,
setNewUiEnabled: mockSetNewUiEnabled,
})

render(<SettingsPanel />)
Expand All @@ -75,13 +82,25 @@ describe('SettingsPanel', () => {
expect(mockSetTheme).toHaveBeenCalledWith('dark')
})

test('calls setNewUiEnabled when New UI switch is toggled', async () => {
const user = userEvent.setup()

render(<SettingsPanel />)

await user.click(screen.getByTestId('nv-switch-track'))

expect(mockSetNewUiEnabled).toHaveBeenCalledWith(true)
})

test('does not render when panel is closed', () => {
vi.mocked(useLayoutStore).mockReturnValue({
rightPanel: null,
closeRightPanel: mockCloseRightPanel,
openRightPanel: mockOpenRightPanel,
theme: 'system',
setTheme: mockSetTheme,
newUiEnabled: false,
setNewUiEnabled: mockSetNewUiEnabled,
})

render(<SettingsPanel />)
Expand Down
23 changes: 21 additions & 2 deletions frontends/ui/src/features/layout/components/SettingsPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
'use client'

import { type FC, useCallback } from 'react'
import { Flex, Text, SidePanel, Select } from '@/adapters/ui'
import { Flex, Text, SidePanel, Select, Switch } from '@/adapters/ui'
import { Settings } from '@/adapters/ui/icons'
import { useLayoutStore } from '../store'
import type { ThemeMode } from '../types'
Expand All @@ -21,7 +21,15 @@ import type { ThemeMode } from '../types'
* Opens from the right side of the screen.
*/
export const SettingsPanel: FC = () => {
const { rightPanel, closeRightPanel, openRightPanel, theme, setTheme } = useLayoutStore()
const {
rightPanel,
closeRightPanel,
openRightPanel,
theme,
setTheme,
newUiEnabled,
setNewUiEnabled,
} = useLayoutStore()

const isOpen = rightPanel === 'settings'

Expand Down Expand Up @@ -79,6 +87,17 @@ export const SettingsPanel: FC = () => {
{ children: 'Dark', value: 'dark' },
]}
/>
<Text kind="label/semibold/xs" className="text-subtle uppercase">
New UI
</Text>
<Switch
size="small"
checked={newUiEnabled}
onCheckedChange={setNewUiEnabled}
attributes={{
SwitchTrack: { 'aria-label': 'Enable new UI' },
}}
/>
Comment on lines +90 to +100
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.

P2 Inconsistent indentation and missing section wrapper

The <Text> and <Switch> elements are indented with 10 spaces while their siblings (<Text kind="label/semibold/xs"> for "UI Theme Options" and <Select>) use 8 spaces. More importantly, wrapping the "New UI" label and switch in their own <Flex> would give the toggle a visually distinct section matching the theme section above it, and make future additions easier.

Suggested change
<Text kind="label/semibold/xs" className="text-subtle uppercase">
New UI
</Text>
<Switch
size="small"
checked={newUiEnabled}
onCheckedChange={setNewUiEnabled}
attributes={{
SwitchTrack: { 'aria-label': 'Enable new UI' },
}}
/>
<Flex direction="col" gap="3">
<Text kind="label/semibold/xs" className="text-subtle uppercase">
New UI
</Text>
<Switch
size="small"
checked={newUiEnabled}
onCheckedChange={setNewUiEnabled}
attributes={{
SwitchTrack: { 'aria-label': 'Enable new UI' },
}}
/>
</Flex>

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

</Flex>
</SidePanel>
)
Expand Down
18 changes: 18 additions & 0 deletions frontends/ui/src/features/layout/store.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('useLayoutStore', () => {
researchPanelTab: 'plan',
dataSourcesPanelTab: 'connections',
theme: 'system',
newUiEnabled: false,
})
})

Expand All @@ -24,6 +25,7 @@ describe('useLayoutStore', () => {
expect(state.rightPanel).toBeNull()
expect(state.researchPanelTab).toBe('plan')
expect(state.dataSourcesPanelTab).toBe('connections')
expect(state.newUiEnabled).toBe(false)
})
})

Expand Down Expand Up @@ -175,4 +177,20 @@ describe('useLayoutStore', () => {
expect(useLayoutStore.getState().theme).toBe('system')
})
})

describe('setNewUiEnabled', () => {
test('enables new UI', () => {
useLayoutStore.getState().setNewUiEnabled(true)

expect(useLayoutStore.getState().newUiEnabled).toBe(true)
})

test('disables new UI', () => {
useLayoutStore.setState({ newUiEnabled: true })

useLayoutStore.getState().setNewUiEnabled(false)

expect(useLayoutStore.getState().newUiEnabled).toBe(false)
})
})
})
4 changes: 4 additions & 0 deletions frontends/ui/src/features/layout/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const initialState: LayoutState = {
dataSourcesPanelTab: 'connections',
enabledDataSourceIds: [], // Start empty, populated when data sources are fetched
theme: 'system',
newUiEnabled: false,
availableDataSources: null,
knowledgeLayerAvailable: false, // Default to false until API confirms availability
dataSourcesLoading: false,
Expand Down Expand Up @@ -82,6 +83,9 @@ export const useLayoutStore = create<LayoutStore>()(

setTheme: (theme: ThemeMode) => set({ theme }, false, 'setTheme'),

setNewUiEnabled: (enabled: boolean) =>
set({ newUiEnabled: enabled }, false, 'setNewUiEnabled'),

fetchDataSources: async (authToken?: string) => {
set({ dataSourcesLoading: true, dataSourcesError: null }, false, 'fetchDataSources/start')

Expand Down
4 changes: 4 additions & 0 deletions frontends/ui/src/features/layout/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ export interface LayoutState {
enabledDataSourceIds: string[]
/** Current theme mode */
theme: ThemeMode
/** Whether the new UI experience is enabled */
newUiEnabled: boolean
/** Dynamic data sources from API (null = not loaded yet) */
availableDataSources: DataSourceFromAPI[] | null
/** Whether the knowledge layer (file upload) is available */
Expand Down Expand Up @@ -73,6 +75,8 @@ export interface LayoutActions {
setEnabledDataSources: (ids: string[]) => void
/** Set the theme mode */
setTheme: (theme: ThemeMode) => void
/** Enable or disable the new UI experience */
setNewUiEnabled: (enabled: boolean) => void
/** Fetch data sources from API. Only web_search is enabled by default */
fetchDataSources: (authToken?: string) => Promise<void>
/** Disable all non-web sources (keep only web_search enabled) */
Expand Down
Loading