[New Feature] Support SAM 2.1 (#59)
* support sam 2.1 * refine config path and ckpt path * update README
This commit is contained in:
119
demo/frontend/src/settings/ApprovableInput.tsx
Normal file
119
demo/frontend/src/settings/ApprovableInput.tsx
Normal file
@@ -0,0 +1,119 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import Tooltip from '@/common/components/Tooltip';
|
||||
import {ArrowPathIcon, CheckIcon, XMarkIcon} from '@heroicons/react/24/solid';
|
||||
import {ChangeEvent, KeyboardEvent, useEffect, useMemo, useState} from 'react';
|
||||
import {Button, Form, Input, Join} from 'react-daisyui';
|
||||
|
||||
type Props<T extends string | number> = Omit<
|
||||
React.InputHTMLAttributes<HTMLInputElement>,
|
||||
'size' | 'color' | 'onChange'
|
||||
> & {
|
||||
label: string;
|
||||
defaultValue: T;
|
||||
initialValue: T;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
|
||||
function getStep(value: number) {
|
||||
const stringValue = String(value);
|
||||
const decimals = stringValue.split('.')[1];
|
||||
if (decimals != null) {
|
||||
// Not using 0.1 ** decimals.length because this will result in rounding
|
||||
// errors, e.g., 0.1 ** 2 => 0.010000000000000002.
|
||||
return 1 / 10 ** decimals.length;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
export default function ApprovableInput<T extends string | number>({
|
||||
label,
|
||||
defaultValue,
|
||||
initialValue,
|
||||
onChange,
|
||||
...otherProps
|
||||
}: Props<T>) {
|
||||
const [value, setValue] = useState<string>(`${initialValue}`);
|
||||
|
||||
useEffect(() => {
|
||||
setValue(`${initialValue}`);
|
||||
}, [initialValue]);
|
||||
|
||||
const step = useMemo(() => {
|
||||
return typeof defaultValue === 'number' && isFinite(defaultValue)
|
||||
? getStep(defaultValue)
|
||||
: undefined;
|
||||
}, [defaultValue]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Form.Label className="flex-col items-start gap-2" title={label}>
|
||||
<Join className="w-full">
|
||||
<Input
|
||||
{...otherProps}
|
||||
className="w-full join-item"
|
||||
value={value}
|
||||
step={step}
|
||||
placeholder={`${defaultValue}`}
|
||||
onChange={(event: ChangeEvent<HTMLInputElement>) => {
|
||||
setValue(event.target.value);
|
||||
}}
|
||||
onKeyDown={(event: KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
event.preventDefault();
|
||||
onChange(value);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Tooltip message="Reset to default">
|
||||
<Button
|
||||
className="join-item"
|
||||
onClick={event => {
|
||||
event.preventDefault();
|
||||
setValue(`${defaultValue}`);
|
||||
}}>
|
||||
<ArrowPathIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip message="Revert change">
|
||||
<Button
|
||||
className="join-item"
|
||||
color="neutral"
|
||||
disabled={initialValue == value}
|
||||
onClick={event => {
|
||||
event.preventDefault();
|
||||
setValue(`${initialValue}`);
|
||||
}}>
|
||||
<XMarkIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
<Tooltip message="Apply change">
|
||||
<Button
|
||||
className="join-item"
|
||||
color="primary"
|
||||
disabled={initialValue == value}
|
||||
onClick={event => {
|
||||
event.preventDefault();
|
||||
onChange(value);
|
||||
}}>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</Join>
|
||||
</Form.Label>
|
||||
</div>
|
||||
);
|
||||
}
|
39
demo/frontend/src/settings/SAM2Settings.tsx
Normal file
39
demo/frontend/src/settings/SAM2Settings.tsx
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {INFERENCE_API_ENDPOINT, VIDEO_API_ENDPOINT} from '@/demo/DemoConfig';
|
||||
import ApprovableInput from '@/settings/ApprovableInput';
|
||||
import useSettingsContext from '@/settings/useSettingsContext';
|
||||
|
||||
export default function SAMVSettings() {
|
||||
const {settings, dispatch} = useSettingsContext();
|
||||
|
||||
return (
|
||||
<div>
|
||||
<ApprovableInput
|
||||
label="Video API Endpoint"
|
||||
defaultValue={VIDEO_API_ENDPOINT}
|
||||
initialValue={settings.videoAPIEndpoint}
|
||||
onChange={url => dispatch({type: 'change-video-api-endpoint', url})}
|
||||
/>
|
||||
<ApprovableInput
|
||||
label="Inference API Endpoint"
|
||||
defaultValue={INFERENCE_API_ENDPOINT}
|
||||
initialValue={settings.inferenceAPIEndpoint}
|
||||
onChange={url => dispatch({type: 'change-inference-api-endpoint', url})}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
97
demo/frontend/src/settings/SettingsContextProvider.tsx
Normal file
97
demo/frontend/src/settings/SettingsContextProvider.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import emptyFunction from '@/common/utils/emptyFunction';
|
||||
import {INFERENCE_API_ENDPOINT, VIDEO_API_ENDPOINT} from '@/demo/DemoConfig';
|
||||
import SettingsModal from '@/settings/SettingsModal';
|
||||
import {
|
||||
Action,
|
||||
DEFAULT_SETTINGS,
|
||||
Settings,
|
||||
settingsReducer,
|
||||
} from '@/settings/SettingsReducer';
|
||||
import {
|
||||
PropsWithChildren,
|
||||
createContext,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import {useImmerReducer} from 'use-immer';
|
||||
|
||||
type ContextProps = {
|
||||
settings: Settings;
|
||||
dispatch: React.Dispatch<Action>;
|
||||
openModal: () => void;
|
||||
closeModal: () => void;
|
||||
hasChanged: boolean;
|
||||
};
|
||||
|
||||
export const SettingsContext = createContext<ContextProps>({
|
||||
settings: DEFAULT_SETTINGS,
|
||||
dispatch: emptyFunction,
|
||||
openModal: emptyFunction,
|
||||
closeModal: emptyFunction,
|
||||
hasChanged: false,
|
||||
});
|
||||
|
||||
type Props = PropsWithChildren;
|
||||
|
||||
export default function SettingsContextProvider({children}: Props) {
|
||||
const [state, dispatch] = useImmerReducer(
|
||||
settingsReducer,
|
||||
DEFAULT_SETTINGS,
|
||||
settings => {
|
||||
// Load the settings from local storage. Eventually use the reducer init
|
||||
// to handle initial loading.
|
||||
return settingsReducer(settings, {type: 'load-state'});
|
||||
},
|
||||
);
|
||||
|
||||
const modalRef = useRef<HTMLDialogElement>(null);
|
||||
|
||||
const openModal = useCallback(() => {
|
||||
modalRef.current?.showModal();
|
||||
}, [modalRef]);
|
||||
|
||||
const handleCloseModal = useCallback(() => {
|
||||
modalRef.current?.close();
|
||||
}, [modalRef]);
|
||||
|
||||
const hasChanged = useMemo(() => {
|
||||
return (
|
||||
VIDEO_API_ENDPOINT !== state.videoAPIEndpoint ||
|
||||
INFERENCE_API_ENDPOINT !== state.inferenceAPIEndpoint
|
||||
);
|
||||
}, [state.videoAPIEndpoint, state.inferenceAPIEndpoint]);
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
settings: state,
|
||||
dispatch,
|
||||
openModal,
|
||||
closeModal: handleCloseModal,
|
||||
hasChanged,
|
||||
}),
|
||||
[state, dispatch, openModal, handleCloseModal, hasChanged],
|
||||
);
|
||||
|
||||
return (
|
||||
<SettingsContext.Provider value={value}>
|
||||
{children}
|
||||
<SettingsModal ref={modalRef} />
|
||||
</SettingsContext.Provider>
|
||||
);
|
||||
}
|
95
demo/frontend/src/settings/SettingsModal.tsx
Normal file
95
demo/frontend/src/settings/SettingsModal.tsx
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {DEMO_FRIENDLY_NAME} from '@/demo/DemoConfig';
|
||||
import SAM2Settings from '@/settings/SAM2Settings';
|
||||
import {XMarkIcon} from '@heroicons/react/24/solid';
|
||||
import {forwardRef, useState} from 'react';
|
||||
import {Button, Modal} from 'react-daisyui';
|
||||
import useSettingsContext from './useSettingsContext';
|
||||
|
||||
type Props = unknown;
|
||||
|
||||
type Config = {
|
||||
key: 'sam2';
|
||||
title: string;
|
||||
component: React.ElementType;
|
||||
};
|
||||
|
||||
const SettingsConfig: Config[] = [
|
||||
{
|
||||
key: 'sam2',
|
||||
title: DEMO_FRIENDLY_NAME,
|
||||
component: SAM2Settings,
|
||||
},
|
||||
];
|
||||
|
||||
export default forwardRef<HTMLDialogElement, Props>(
|
||||
function SettingsModal(_props, ref) {
|
||||
const {closeModal} = useSettingsContext();
|
||||
const [activeConfig, setActiveConfig] = useState<Config>(SettingsConfig[0]);
|
||||
|
||||
const SettingsComponent = activeConfig.component;
|
||||
|
||||
return (
|
||||
<Modal
|
||||
data-testid="settings-modal"
|
||||
ref={ref}
|
||||
className="lg:absolute lg:top-10 lg:w-11/12 lg:max-w-4xl flex flex-col"
|
||||
responsive={true}>
|
||||
<Button
|
||||
size="sm"
|
||||
color="ghost"
|
||||
shape="circle"
|
||||
className="absolute right-2 top-2"
|
||||
startIcon={<XMarkIcon className="w-6 h-6" />}
|
||||
onClick={closeModal}
|
||||
/>
|
||||
<Modal.Header className="font-bold">Settings</Modal.Header>
|
||||
<Modal.Body className="flex flex-col grow overflow-hidden">
|
||||
<div className="flex flex-col md:lg:flex-row gap-4 md:lg:gap-12 overflow-hidden">
|
||||
<div className="flex flex-row shrink-0 md:lg:flex-col gap-4 md:lg:py-2 overflow-x-auto">
|
||||
{SettingsConfig.map(config => (
|
||||
<div
|
||||
key={config.key}
|
||||
data-testid={`show-settings-${config.key}`}
|
||||
className={`cursor-pointer whitespace-nowrap ${
|
||||
activeConfig.key === config.key && 'text-primary'
|
||||
} ${
|
||||
activeConfig.key === config.key &&
|
||||
'sm:underline md:lg:no-underline sm:underline-offset-4'
|
||||
}`}
|
||||
onClick={() => setActiveConfig(config)}>
|
||||
{config.title}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div
|
||||
data-testid={`settings-${activeConfig.key}`}
|
||||
className="overflow-hidden overflow-y-auto grow md:lg:pt-2">
|
||||
<div className="flex flex-col grow-0 flex-1">
|
||||
<h1 className="hidden md:lg:block">{activeConfig.title}</h1>
|
||||
<SettingsComponent />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Modal.Body>
|
||||
<Modal.Actions className="shrink-0">
|
||||
<Button onClick={closeModal}>Close</Button>
|
||||
</Modal.Actions>
|
||||
</Modal>
|
||||
);
|
||||
},
|
||||
);
|
70
demo/frontend/src/settings/SettingsReducer.ts
Normal file
70
demo/frontend/src/settings/SettingsReducer.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {INFERENCE_API_ENDPOINT, VIDEO_API_ENDPOINT} from '@/demo/DemoConfig';
|
||||
|
||||
export type Settings = {
|
||||
videoAPIEndpoint: string;
|
||||
inferenceAPIEndpoint: string;
|
||||
};
|
||||
|
||||
// Key used to store the settings in the browser's local storage.
|
||||
export const SAM2_SETTINGS_KEY = 'SAM2_SETTINGS_KEY';
|
||||
|
||||
export type Action =
|
||||
| {type: 'load-state'}
|
||||
| {type: 'change-video-api-endpoint'; url: string}
|
||||
| {type: 'change-inference-api-endpoint'; url: string};
|
||||
|
||||
export const DEFAULT_SETTINGS: Settings = {
|
||||
videoAPIEndpoint: VIDEO_API_ENDPOINT,
|
||||
inferenceAPIEndpoint: INFERENCE_API_ENDPOINT,
|
||||
};
|
||||
|
||||
export function settingsReducer(state: Settings, action: Action): Settings {
|
||||
function storeSettings(newState: Settings): void {
|
||||
localStorage.setItem(SAM2_SETTINGS_KEY, JSON.stringify(newState));
|
||||
}
|
||||
|
||||
switch (action.type) {
|
||||
case 'load-state': {
|
||||
try {
|
||||
const serializedSettings = localStorage.getItem(SAM2_SETTINGS_KEY);
|
||||
if (serializedSettings != null) {
|
||||
return JSON.parse(serializedSettings) as Settings;
|
||||
} else {
|
||||
// Store default settings in local storage. This will populate the
|
||||
// settings in the local storage on first app load or when user
|
||||
// cleared the browser cache.
|
||||
storeSettings(state);
|
||||
}
|
||||
} catch {
|
||||
// Could not parse settings. Using default settings instead.
|
||||
}
|
||||
return state;
|
||||
}
|
||||
case 'change-video-api-endpoint':
|
||||
state.videoAPIEndpoint = action.url;
|
||||
break;
|
||||
case 'change-inference-api-endpoint':
|
||||
state.inferenceAPIEndpoint = action.url;
|
||||
break;
|
||||
}
|
||||
|
||||
// Store the settings state on every change
|
||||
storeSettings(state);
|
||||
|
||||
return state;
|
||||
}
|
21
demo/frontend/src/settings/useSettingsContext.tsx
Normal file
21
demo/frontend/src/settings/useSettingsContext.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import {useContext} from 'react';
|
||||
import {SettingsContext} from '@/settings/SettingsContextProvider';
|
||||
|
||||
export default function useSettingsContext() {
|
||||
return useContext(SettingsContext);
|
||||
}
|
Reference in New Issue
Block a user