Skip to content

Commit 87a1efe

Browse files
authored
feat(inspect): add, remove list and connect sources (#3078)
* group disclosure config Signed-off-by: Colorado, Camilo <[email protected]> * render SourceOptions Signed-off-by: Colorado, Camilo <[email protected]> * update vites config Signed-off-by: Colorado, Camilo <[email protected]> * render sourcer list and connection status Signed-off-by: Colorado, Camilo <[email protected]> * implement EditSource component Signed-off-by: Colorado, Camilo <[email protected]> * refactor api-camera, split it in two components * refactor edit and new source components * display source config details Signed-off-by: Colorado, Camilo <[email protected]> * add SourceMenu unit tests Signed-off-by: Colorado, Camilo <[email protected]> * update source API * update forms with projectId * fix Pr comments Signed-off-by: Colorado, Camilo <[email protected]> --------- Signed-off-by: Colorado, Camilo <[email protected]>
1 parent 9096eb7 commit 87a1efe

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

49 files changed

+1722
-48
lines changed

application/ui/package-lock.json

Lines changed: 46 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

application/ui/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@
9696
"typescript-eslint": "^8.31.1",
9797
"vite": "^7.0.6",
9898
"vite-plugin-svgr": "^4.3.0",
99+
"vite-tsconfig-paths": "^5.1.4",
99100
"vitest": "^3.2.4"
100101
},
101102
"workspaces": [
Lines changed: 3 additions & 0 deletions
Loading

application/ui/src/assets/icons/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,5 @@ export { ReactComponent as Webcam } from './webcam.svg';
2020
export { ReactComponent as Webhook } from './webhook.svg';
2121
export { ReactComponent as Image } from './image.svg';
2222
export { ReactComponent as Fireworks } from './fire-works.svg';
23+
export { ReactComponent as PipelineLink } from './pipeline-link.svg';
24+
export { ReactComponent as Folder } from './folder.svg';
Lines changed: 5 additions & 0 deletions
Loading
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// Copyright (C) 2025 Intel Corporation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { ReactNode, useState } from 'react';
5+
6+
import { Disclosure, DisclosurePanel, DisclosureTitle, Flex, Text } from '@geti/ui';
7+
import { clsx } from 'clsx';
8+
import { isFunction } from 'lodash-es';
9+
10+
import styles from './disclosure-group.module.scss';
11+
12+
type DisclosureItem = { value: string; label: string; icon: ReactNode; content?: ReactNode };
13+
14+
type DisclosureItemProps = {
15+
value: string | null;
16+
onChange?: (value: string) => void;
17+
item: DisclosureItem;
18+
};
19+
20+
interface DisclosureGroupProps {
21+
items: DisclosureItem[];
22+
defaultActiveInput: string | null;
23+
}
24+
25+
const DisclosureItem = ({ item, value, onChange }: DisclosureItemProps) => {
26+
const isExpanded = item.value === value;
27+
28+
const handleExpandedChange = () => {
29+
isFunction(onChange) && onChange(item.value);
30+
};
31+
32+
return (
33+
<Disclosure
34+
isQuiet
35+
key={item.label}
36+
isExpanded={isExpanded}
37+
UNSAFE_className={clsx(styles.disclosure, { [styles.selected]: isExpanded })}
38+
onExpandedChange={handleExpandedChange}
39+
>
40+
<DisclosureTitle UNSAFE_className={styles.disclosureTitleContainer}>
41+
<Flex alignItems={'center'} justifyContent={'space-between'} width={'100%'}>
42+
<Flex marginStart={'size-50'} alignItems={'center'} gap={'size-100'}>
43+
{item.icon}
44+
<Text UNSAFE_className={styles.disclosureTitle}>{item.label}</Text>
45+
</Flex>
46+
</Flex>
47+
</DisclosureTitle>
48+
<DisclosurePanel>{isExpanded && item.content}</DisclosurePanel>
49+
</Disclosure>
50+
);
51+
};
52+
53+
export const DisclosureGroup = ({ items, defaultActiveInput }: DisclosureGroupProps) => {
54+
const [activeInput, setActiveInput] = useState(defaultActiveInput);
55+
56+
const handleActiveInputChange = (value: string) => {
57+
setActiveInput((prevValue) => (value !== prevValue ? value : null));
58+
};
59+
60+
return (
61+
<Flex width={'100%'} direction={'column'} gap={'size-100'}>
62+
{items.map((item) => (
63+
<DisclosureItem item={item} key={item.label} onChange={handleActiveInputChange} value={activeInput} />
64+
))}
65+
</Flex>
66+
);
67+
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
.disclosure {
2+
background-color: var(--spectrum-global-color-gray-75);
3+
border: var(--spectrum-alias-border-size-thin) solid var(--spectrum-global-color-gray-200) !important;
4+
border-radius: var(--spectrum-alias-border-radius-regular);
5+
}
6+
7+
.selected {
8+
border: var(--spectrum-alias-border-size-thick) solid var(--energy-blue) !important;
9+
}
10+
11+
.disclosureTitleContainer {
12+
width: 100%;
13+
}
14+
15+
.disclosureTitle {
16+
font-size: var(--spectrum-global-dimension-font-size-75);
17+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (C) 2025 Intel Corporation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { ActionButton, DialogTrigger, Flex, Slider, Text, View } from '@geti/ui';
5+
import { ChevronDownSmall } from '@geti/ui/icons';
6+
7+
import { useInference } from '../inference-provider.component';
8+
9+
export const InferenceOpacity = () => {
10+
const { inferenceOpacity, onInferenceOpacityChange, inferenceResult } = useInference();
11+
12+
return (
13+
<Flex alignItems={'center'} gap={'size-50'}>
14+
<Text>Opacity:</Text>
15+
<DialogTrigger type={'popover'} placement={'bottom'}>
16+
<ActionButton width={'size-800'} isDisabled={inferenceResult === undefined}>
17+
<Flex alignItems={'center'} gap={'size-50'}>
18+
<span>{Math.floor(inferenceOpacity * 100)}%</span>
19+
<Flex>
20+
<ChevronDownSmall style={{ order: 1 }} />
21+
</Flex>
22+
</Flex>
23+
</ActionButton>
24+
<View padding={'size-100'}>
25+
<Slider
26+
value={inferenceOpacity}
27+
onChange={onInferenceOpacityChange}
28+
maxValue={1}
29+
minValue={0}
30+
step={0.01}
31+
/>
32+
</View>
33+
</DialogTrigger>
34+
</Flex>
35+
);
36+
};
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
// Copyright (C) 2025 Intel Corporation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { CSSProperties, Suspense } from 'react';
5+
6+
import { PipelineLink } from '@geti-inspect/icons';
7+
import {
8+
Button,
9+
Content,
10+
Dialog,
11+
DialogTrigger,
12+
dimensionValue,
13+
Item,
14+
Loading,
15+
TabList,
16+
TabPanels,
17+
Tabs,
18+
Text,
19+
View,
20+
} from '@geti/ui';
21+
22+
import { SourceActions } from './sources/source-actions.component';
23+
24+
const paddingStyle = {
25+
'--spectrum-dialog-padding-x': dimensionValue('size-300'),
26+
'--spectrum-dialog-padding-y': dimensionValue('size-300'),
27+
} as CSSProperties;
28+
29+
export const InputOutputSetup = () => {
30+
return (
31+
<DialogTrigger type='popover'>
32+
<Button variant={'secondary'} UNSAFE_style={{ gap: dimensionValue('size-125') }}>
33+
<PipelineLink fill='white' />
34+
<Text width={'auto'}>Pipeline configuration</Text>
35+
</Button>
36+
<Dialog minWidth={'size-6000'} UNSAFE_style={paddingStyle}>
37+
<Content>
38+
<Tabs aria-label='Dataset import tabs' height={'100%'}>
39+
<TabList>
40+
<Item key='sources' textValue='Sources'>
41+
<Text>Input</Text>
42+
</Item>
43+
<Item key='sinks' textValue='Sinks'>
44+
<Text>Output</Text>
45+
</Item>
46+
</TabList>
47+
<TabPanels>
48+
<Item key='sources'>
49+
<View marginTop={'size-200'}>
50+
<Suspense fallback={<Loading size='M' />}>
51+
<SourceActions />
52+
</Suspense>
53+
</View>
54+
</Item>
55+
<Item key='sinks'>
56+
<View marginTop={'size-200'}>
57+
<Suspense fallback={<Loading size='M' />}>SinkOptions</Suspense>
58+
</View>
59+
</Item>
60+
</TabPanels>
61+
</Tabs>
62+
</Content>
63+
</Dialog>
64+
</DialogTrigger>
65+
);
66+
};
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright (C) 2025 Intel Corporation
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { ReactNode } from 'react';
5+
6+
import { Button, Form } from '@geti/ui';
7+
8+
import { useConnectSourceToPipeline } from '../../../../../hooks/use-pipeline.hook';
9+
import { useSourceAction } from '../hooks/use-source-action.hook';
10+
import { SourceConfig } from '../util';
11+
12+
interface AddSourceProps<T> {
13+
config: Awaited<T>;
14+
onSaved: () => void;
15+
componentFields: (state: Awaited<T>) => ReactNode;
16+
bodyFormatter: (formData: FormData) => T;
17+
}
18+
19+
export const AddSource = <T extends SourceConfig>({
20+
config,
21+
onSaved,
22+
bodyFormatter,
23+
componentFields,
24+
}: AddSourceProps<T>) => {
25+
const connectToPipelineMutation = useConnectSourceToPipeline();
26+
27+
const [state, submitAction, isPending] = useSourceAction({
28+
config,
29+
isNewSource: true,
30+
onSaved: async (sourceId) => {
31+
await connectToPipelineMutation(sourceId);
32+
onSaved();
33+
},
34+
bodyFormatter,
35+
});
36+
37+
return (
38+
<Form action={submitAction}>
39+
<>{componentFields(state)}</>
40+
41+
<Button type='submit' isDisabled={isPending} UNSAFE_style={{ maxWidth: 'fit-content' }}>
42+
Add & Connect
43+
</Button>
44+
</Form>
45+
);
46+
};

0 commit comments

Comments
 (0)