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
6 changes: 4 additions & 2 deletions application/ui/src/components/toolbar/toolbar.component.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { CSSProperties } from 'react';
import { useGetSources } from '@/hooks';
import { Button, Flex, StatusLight, View } from '@geti/ui';

import { DeviceSelector } from '../../features/device-selector/device-selector.component';
import { SourcesSinks } from '../../features/sources-sinks/sources-sinks.component';
import { useWebRTCConnection } from '../../features/stream/web-rtc/web-rtc-connection-provider';

Expand Down Expand Up @@ -86,9 +87,10 @@ export const Toolbar = () => {
>
<Flex justifyContent={'space-between'} alignItems={'center'} height={'100%'} width={'100%'}>
<StreamStatus />
<View marginStart={'auto'}>
<Flex alignItems={'center'} gap={'size-100'} marginStart={'auto'}>
<DeviceSelector />
<SourcesSinks />
</View>
</Flex>
</Flex>
</View>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Copyright (C) 2025 Intel Corporation
* SPDX-License-Identifier: Apache-2.0
*/

import { DeviceOption } from '../device-selector.types';

// TODO: Replace with $api.useSuspenseQuery('get', '/api/v1/system/devices', ...) once the endpoint is available.
const MOCK_DEVICES: DeviceOption[] = [
{ id: 'cpu', name: 'CPU' },
{ id: 'xpu', name: 'Intel GPU' },
{ id: 'cuda', name: 'NVIDIA GPU' },
];

export const useGetDevices = (): DeviceOption[] => {
return MOCK_DEVICES;
Comment on lines +8 to +16
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

This hook currently returns a hard-coded MOCK_DEVICES list. The issue/PR acceptance criteria requires fetching available devices from GET /system/devices and always including an Auto option, so this should be implemented against the real endpoint (and the UI should handle loading/error states accordingly) rather than shipping a mock.

Copilot uses AI. Check for mistakes.
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Copyright (C) 2025 Intel Corporation
* SPDX-License-Identifier: Apache-2.0
*/

import { $api } from '@/api';
import { useProjectIdentifier } from '@/hooks';
import { getQueryKey } from '@/query-client';
import { useQueryClient } from '@tanstack/react-query';

export const useUpdateProjectDevice = () => {
const { projectId } = useProjectIdentifier();
const queryClient = useQueryClient();

// TODO: Replace with proper data type once the API is ready.
const mutation = $api.useMutation('put', '/api/v1/projects/{project_id}', {
meta: {
error: { notify: true },
},
});

const updateDevice = (device: string): void => {
mutation.mutate(
{
// eslint-disable-next-line @typescript-eslint/no-explicit-any
body: { device } as any,
params: { path: { project_id: projectId } },
},
Comment on lines +15 to +28
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

body: { device } as any defeats the OpenAPI typing and can mask mismatches with the backend contract. Since the backend PUT /projects/{project_id} update schema supports device, prefer reusing the existing typed useUpdateProject hook (or use ProjectUpdateType directly) so the payload is type-checked and you don’t need an any cast.

Copilot uses AI. Check for mistakes.
{
onSuccess: async () => {
await queryClient.invalidateQueries({
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.

instead of invalidating queries here in onSuccess we could also do it in mutation's meta property (the only benefit would be that queryClient and getQueryKey would not be necessary).

queryKey: getQueryKey([
'get',
'/api/v1/projects/{project_id}',
{ params: { path: { project_id: projectId } } },
]),
});
},
}
);
};

return {
mutate: updateDevice,
isPending: mutation.isPending,
};
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Copyright (C) 2025 Intel Corporation
* SPDX-License-Identifier: Apache-2.0
*/

import { Key, Suspense } from 'react';

import { useCurrentProject } from '@/hooks';
import { Item, Loading, Picker } from '@geti/ui';

import { useGetDevices } from './api/use-get-devices';
import { useUpdateProjectDevice } from './api/use-update-project-device';
import { DeviceOption } from './device-selector.types';

const AUTO_DEVICE: DeviceOption = { id: 'auto', name: 'Auto' };

export const DeviceSelector = () => {
return (
<Suspense fallback={<Loading size={'S'} />}>
<DeviceSelectorContent />
</Suspense>
);
};

const DeviceSelectorContent = () => {
const devices = useGetDevices();
const { data: _project } = useCurrentProject();
const updateDevice = useUpdateProjectDevice();

const items: DeviceOption[] = [AUTO_DEVICE, ...devices];

// TODO: Replace with actual project device when API is ready
const selectedDevice = 'auto';
Comment on lines +27 to +33
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

useCurrentProject() result is currently unused, and selectedDevice is hard-coded to 'auto' while Picker is controlled via selectedKey. As a result, the picker selection will never visually change (it will always show Auto) and it won’t reflect the project’s persisted device value. Derive the selected key from project.device (fallback to auto) and/or keep local state that updates on selection / mutation success, and remove the unused _project alias if not needed.

Suggested change
const { data: _project } = useCurrentProject();
const updateDevice = useUpdateProjectDevice();
const items: DeviceOption[] = [AUTO_DEVICE, ...devices];
// TODO: Replace with actual project device when API is ready
const selectedDevice = 'auto';
const { data: project } = useCurrentProject();
const updateDevice = useUpdateProjectDevice();
const items: DeviceOption[] = [AUTO_DEVICE, ...devices];
const selectedDevice = project?.device ?? 'auto';

Copilot uses AI. Check for mistakes.

const handleSelectionChange = (key: Key | null): void => {
const next = String(key);

if (key !== null && next !== selectedDevice) {
updateDevice.mutate(next);
}
};
Comment on lines +17 to +41
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

There are existing UI tests for similar toolbar pickers, but this new selector has no tests covering its core behavior (rendering Auto + device options, reflecting project.device, and issuing the PUT update + invalidating the project query on change). Adding a device-selector.component.test.tsx (MSW-mocked) would prevent regressions.

Copilot generated this review using guidance from repository custom instructions.

return (
<Picker
label={'Device'}
aria-label={'device'}
labelPosition={'side'}
labelAlign={'end'}
selectedKey={selectedDevice}
onSelectionChange={handleSelectionChange}
Comment on lines +44 to +50
Copy link

Copilot AI Mar 31, 2026

Choose a reason for hiding this comment

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

aria-label is currently set to a generic lowercase 'device'. For better screen reader output and consistency with other labels in this codebase, use a more descriptive label (e.g., "Device" or "Device selector"), or rely on the visible label prop if Picker already wires it up correctly.

Copilot uses AI. Check for mistakes.
items={items}
>
{(item) => <Item key={item.id}>{item.name}</Item>}
</Picker>
);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/**
* Copyright (C) 2025 Intel Corporation
* SPDX-License-Identifier: Apache-2.0
*/

export interface DeviceOption {
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.

most probably that type will come from @/api once server supprts it.

id: string;
name: string;
}
Loading