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
120 changes: 0 additions & 120 deletions application/ui/src/components/stream/stream.tsx

This file was deleted.

13 changes: 11 additions & 2 deletions application/ui/src/components/stream/web-rtc-connection.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { fetchClient } from '../../api/client';

export type WebRTCConnectionStatus = 'idle' | 'connecting' | 'connected' | 'disconnected' | 'failed';

type WebRTCConnectionEvent =
Expand Down Expand Up @@ -32,7 +34,6 @@ export class WebRTCConnection {
private timeoutId?: ReturnType<typeof setTimeout>;

constructor() {
// TODO: replace with uuid
this.webrtcId = Math.random().toString(36).substring(7);
}

Expand Down Expand Up @@ -123,7 +124,15 @@ export class WebRTCConnection {
private async sendOffer(): Promise<SessionData | undefined> {
if (!this.peerConnection) return;

throw new Error('Work in progress: not implemented');
const { data } = await fetchClient.POST('/api/webrtc/offer', {
body: {
sdp: this.peerConnection.localDescription?.sdp ?? '',
type: this.peerConnection.localDescription?.type ?? '',
webrtc_id: this.webrtcId,
},
});

return data as SessionData;
}

private async handleOfferResponse(data: SessionData | undefined): Promise<boolean> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createContext, ReactNode, use, useState } from 'react';

import { $api } from '@geti-inspect/api';
import { components } from '@geti-inspect/api/spec';
import { useProjectIdentifier } from '@geti-inspect/hooks';

import { MediaItem } from './dataset/types';
import { useSelectedMediaItem } from './selected-media-item-provider.component';
Expand Down Expand Up @@ -61,6 +62,9 @@ interface InferenceProviderProps {
}

export const InferenceProvider = ({ children }: InferenceProviderProps) => {
const { projectId } = useProjectIdentifier();
const updatePipeline = $api.useMutation('patch', '/api/projects/{project_id}/pipeline');

const { inferenceResult, onInference, isPending } = useInferenceMutation();
const [selectedModelId, setSelectedModelId] = useState<string | undefined>(undefined);
const [inferenceOpacity, setInferenceOpacity] = useState<number>(0.75);
Expand All @@ -69,6 +73,10 @@ export const InferenceProvider = ({ children }: InferenceProviderProps) => {

const onSetSelectedModelId = (modelId: string | undefined) => {
setSelectedModelId(modelId);

Choose a reason for hiding this comment

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

I think we could get rid of setSelectedModelId now and instead use the model id from the project's pipeline query.

updatePipeline.mutate({
params: { path: { project_id: projectId } },
body: { model_id: modelId },
});

if (modelId && selectedMediaItem) {
onInference(selectedMediaItem, modelId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { useSpinDelay } from 'spin-delay';

import { useInference } from './inference-provider.component';
import { useSelectedMediaItem } from './selected-media-item-provider.component';
import { StreamContainer } from './stream/stream-container';

import styles from './inference.module.scss';

Expand Down Expand Up @@ -56,6 +57,10 @@ export const InferenceResult = () => {
const isInferenceAvailable = useIsInferenceAvailable();
const isLoadingInference = useSpinDelay(isPending, { delay: 300 });

if (selectedMediaItem === undefined) {
return <StreamContainer />;

Choose a reason for hiding this comment

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

One thing to note: here we will need to improve the validation a bit more. In the demo code I send you I rendered the StreamContainer here out of convenience.

In a later UX we should:

  • Show a help message if no source has been defined yet
  • Show the selected media item + live inference when a media item + model is selected
  • Show the selected media item + help text if no model is selected/trained yet
  • Show the video stream from the active source

}

if (!isInferenceAvailable && selectedMediaItem === undefined) {
return (
<Grid
Expand Down
1 change: 1 addition & 0 deletions application/ui/src/features/inspect/stream/stream.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
const { status, webRTCConnectionRef } = useWebRTCConnection();

const connect = useCallback(async () => {
console.log('feature connect');

Check failure on line 45 in application/ui/src/features/inspect/stream/stream.tsx

View workflow job for this annotation

GitHub Actions / Eslint checks

Unexpected console statement. Only these console methods are allowed: warn, error, time, timeEnd, info
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

Debug console.log statement should be removed before merging to production. This appears to be a leftover debugging artifact.

Suggested change
console.log('feature connect');

Copilot uses AI. Check for mistakes.
const videoOutput = videoRef.current;
const webrtcConnection = webRTCConnectionRef.current;
const peerConnection = webrtcConnection?.getPeerConnection();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { $api } from '@geti-inspect/api';
import { useProjectIdentifier } from '@geti-inspect/hooks';
import { Switch, toast } from '@geti/ui';

export const PipelineSwitch = () => {
const { projectId } = useProjectIdentifier();
const { data: pipeline } = $api.useSuspenseQuery('get', '/api/projects/{project_id}/pipeline', {
params: { path: { project_id: projectId } },
});

const enablePipeline = $api.useMutation('post', '/api/projects/{project_id}/pipeline:enable', {
onError: (error) => {
if (error) {
toast({ type: 'error', message: String(error.detail) });
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The error message displayed to users may not be helpful if error.detail is undefined or contains technical details. Consider providing a user-friendly fallback message like 'Failed to enable pipeline' when error.detail is not available.

Suggested change
toast({ type: 'error', message: String(error.detail) });
const message =
typeof error?.detail === 'string' && error.detail.trim()
? error.detail
: 'Failed to enable pipeline';
toast({ type: 'error', message });

Copilot uses AI. Check for mistakes.
}
},
meta: {
invalidates: [
['get', '/api/projects/{project_id}/pipeline', { params: { path: { project_id: projectId } } }],
],
},
});
const disablePipeline = $api.useMutation('post', '/api/projects/{project_id}/pipeline:disable', {
meta: {
invalidates: [
['get', '/api/projects/{project_id}/pipeline', { params: { path: { project_id: projectId } } }],
],
},
});
Comment on lines +23 to +29
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The disablePipeline mutation is missing an onError handler similar to enablePipeline. Consider adding error handling to provide user feedback when disabling the pipeline fails.

Copilot uses AI. Check for mistakes.

const handleChange = (isSelected: boolean) => {
const handler = isSelected ? enablePipeline.mutate : disablePipeline.mutate;
handler({ params: { path: { project_id: projectId } } });
};

return (
<Switch isSelected={pipeline.status === 'running'} onChange={handleChange}>
Enabled
</Switch>
);
};
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { $api } from '@geti-inspect/api';
import { useProjectIdentifier } from '@geti-inspect/hooks';
import { omit } from 'lodash-es';
import { v4 as uuid } from 'uuid';

import { SinkConfig } from '../utils';

Expand All @@ -21,15 +20,12 @@ export const useSinkMutation = (isNewSink: boolean) => {

return async (body: SinkConfig) => {
if (isNewSink) {
const id = uuid();
const sinkPayload = { ...body, id };

await addSink.mutateAsync({
body: sinkPayload,
body,
params: { path: { project_id: projectId } },
});

return id;
return body.id;
}

const response = await updateSink.mutateAsync({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { v4 as uuid } from 'uuid';

import { LocalFolderSinkConfig, SinkOutputFormats } from '../utils';

export const getLocalFolderInitialConfig = (project_id: string): LocalFolderSinkConfig => ({
id: '',
id: uuid(),
name: '',
project_id,
sink_type: 'folder',
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { v4 as uuid } from 'uuid';

import { MqttSinkConfig, SinkOutputFormats } from '../utils';

export const getMqttInitialConfig = (project_id: string): MqttSinkConfig => ({
id: '',
id: uuid(),
name: '',
project_id,
topic: '',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { v4 as uuid } from 'uuid';

import { RosSinkConfig, SinkOutputFormats } from '../utils';

export const getRosInitialConfig = (project_id: string): RosSinkConfig => ({
id: '',
id: uuid(),
name: '',
project_id,
topic: '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ export const SinkMenu = ({ id, name, isConnected, onEdit }: SinkMenuProps) => {
const updatePipeline = $api.useMutation('patch', '/api/projects/{project_id}/pipeline', {
meta: {
invalidates: [
['get', '/api/projects/{project_id}/sinks', { params: { path: { project_id: projectId } } }],
['get', '/api/projects/{project_id}/pipeline', { params: { path: { project_id: projectId } } }],
],
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { isEmpty } from 'lodash-es';
import { RequiredTextField } from '../../../../../components/required-text-field/required-text-field.component';
import { Fields, getPairsFromObject, Pair } from './utils';

type KeyValueBuilderProps = {
type HeaderKeyValueBuilderProps = {
title: string;
keysName: string;
valuesName: string;
Expand All @@ -17,7 +17,7 @@ type KeyValueBuilderProps = {
const updatePairAtIndex = (indexToUpdate: number, field: Fields, value: string) => (pair: Pair, index: number) =>
index === indexToUpdate ? { ...pair, [field]: value } : pair;

export const KeyValueBuilder = ({ title, keysName, valuesName, config = {} }: KeyValueBuilderProps) => {
export const HeaderKeyValueBuilder = ({ title, keysName, valuesName, config = {} }: HeaderKeyValueBuilderProps) => {
const [pairs, setPairs] = useState<Pair[]>(getPairsFromObject(config));

const addPair = () => {
Expand Down
Loading
Loading