mirror of
https://github.com/immich-app/immich
synced 2025-11-07 17:27:20 +00:00
feat(ml)!: customizable ML settings (#3891)
* consolidated endpoints, added live configuration * added ml settings to server * added settings dashboard * updated deps, fixed typos * simplified modelconfig updated tests * Added ml setting accordion for admin page updated tests * merge `clipText` and `clipVision` * added face distance setting clarified setting * add clip mode in request, dropdown for face models * polished ml settings updated descriptions * update clip field on error * removed unused import * add description for image classification threshold * pin safetensors for arm wheel updated poetry lock * moved dto * set model type only in ml repository * revert form-data package install use fetch instead of axios * added slotted description with link updated facial recognition description clarified effect of disabling tasks * validation before model load * removed unnecessary getconfig call * added migration * updated api updated api updated api --------- Co-authored-by: Alex Tran <alex.tran1502@gmail.com>
This commit is contained in:
parent
22f5e05060
commit
bcc36d14a1
56 changed files with 2324 additions and 655 deletions
|
|
@ -4,38 +4,45 @@
|
|||
NotificationType,
|
||||
} from '$lib/components/shared-components/notification/notification';
|
||||
import { handleError } from '$lib/utils/handle-error';
|
||||
import { api, SystemConfigDto } from '@api';
|
||||
import { api, SystemConfigMachineLearningDto } from '@api';
|
||||
import { isEqual } from 'lodash-es';
|
||||
import { fade } from 'svelte/transition';
|
||||
import SettingButtonsRow from '../setting-buttons-row.svelte';
|
||||
import SettingInputField, { SettingInputFieldType } from '../setting-input-field.svelte';
|
||||
import SettingSwitch from '../setting-switch.svelte';
|
||||
import SettingAccordion from '../setting-accordion.svelte';
|
||||
import SettingSelect from '../setting-select.svelte';
|
||||
|
||||
export let machineLearningConfig: SystemConfigMachineLearningDto; // this is the config that is being edited
|
||||
export let disabled = false;
|
||||
|
||||
let config: SystemConfigDto;
|
||||
let defaultConfig: SystemConfigDto;
|
||||
let savedConfig: SystemConfigMachineLearningDto;
|
||||
let defaultConfig: SystemConfigMachineLearningDto;
|
||||
|
||||
async function refreshConfig() {
|
||||
[config, defaultConfig] = await Promise.all([
|
||||
api.systemConfigApi.getConfig().then((res) => res.data),
|
||||
api.systemConfigApi.getDefaults().then((res) => res.data),
|
||||
[savedConfig, defaultConfig] = await Promise.all([
|
||||
api.systemConfigApi.getConfig().then((res) => res.data.machineLearning),
|
||||
api.systemConfigApi.getDefaults().then((res) => res.data.machineLearning),
|
||||
]);
|
||||
}
|
||||
|
||||
async function reset() {
|
||||
const { data: resetConfig } = await api.systemConfigApi.getConfig();
|
||||
config = resetConfig;
|
||||
machineLearningConfig = { ...resetConfig.machineLearning };
|
||||
savedConfig = { ...resetConfig.machineLearning };
|
||||
notificationController.show({ message: 'Reset to the last saved settings', type: NotificationType.Info });
|
||||
}
|
||||
|
||||
async function saveSetting() {
|
||||
try {
|
||||
const { data: current } = await api.systemConfigApi.getConfig();
|
||||
await api.systemConfigApi.updateConfig({
|
||||
systemConfigDto: { ...current, machineLearning: config.machineLearning },
|
||||
const result = await api.systemConfigApi.updateConfig({
|
||||
systemConfigDto: { ...current, machineLearning: machineLearningConfig },
|
||||
});
|
||||
await refreshConfig();
|
||||
|
||||
machineLearningConfig = { ...result.data.machineLearning };
|
||||
savedConfig = { ...result.data.machineLearning };
|
||||
|
||||
notificationController.show({ message: 'Settings saved', type: NotificationType.Info });
|
||||
} catch (error) {
|
||||
handleError(error, 'Unable to save settings');
|
||||
|
|
@ -43,10 +50,7 @@
|
|||
}
|
||||
|
||||
async function resetToDefault() {
|
||||
await refreshConfig();
|
||||
const { data: defaults } = await api.systemConfigApi.getDefaults();
|
||||
config = defaults;
|
||||
|
||||
machineLearningConfig = { ...defaultConfig };
|
||||
notificationController.show({ message: 'Reset settings to defaults', type: NotificationType.Info });
|
||||
}
|
||||
</script>
|
||||
|
|
@ -54,52 +58,152 @@
|
|||
<div class="mt-2">
|
||||
{#await refreshConfig() then}
|
||||
<div in:fade={{ duration: 500 }}>
|
||||
<form autocomplete="off" on:submit|preventDefault class="mx-4 flex flex-col gap-4 py-4">
|
||||
<SettingSwitch
|
||||
title="Enabled"
|
||||
subtitle="Use machine learning features"
|
||||
{disabled}
|
||||
bind:checked={config.machineLearning.enabled}
|
||||
/>
|
||||
<form autocomplete="off" on:submit|preventDefault class="mx-4 mt-4">
|
||||
<div class="flex flex-col gap-4">
|
||||
<SettingSwitch
|
||||
title="ENABLED"
|
||||
subtitle="If disabled, all ML features will be disabled regardless of the below settings."
|
||||
{disabled}
|
||||
bind:checked={machineLearningConfig.enabled}
|
||||
/>
|
||||
|
||||
<hr />
|
||||
<hr />
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="URL"
|
||||
desc="URL of machine learning server"
|
||||
bind:value={config.machineLearning.url}
|
||||
required={true}
|
||||
disabled={disabled || !config.machineLearning.enabled}
|
||||
isEdited={!(config.machineLearning.url === config.machineLearning.url)}
|
||||
/>
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="URL"
|
||||
desc="URL of the machine learning server"
|
||||
bind:value={machineLearningConfig.url}
|
||||
required={true}
|
||||
disabled={disabled || !machineLearningConfig.enabled}
|
||||
isEdited={machineLearningConfig.url !== savedConfig.url}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SettingSwitch
|
||||
title="SMART SEARCH"
|
||||
subtitle="Extract CLIP embeddings for smart search"
|
||||
bind:checked={config.machineLearning.clipEncodeEnabled}
|
||||
disabled={disabled || !config.machineLearning.enabled}
|
||||
/>
|
||||
<SettingAccordion title="Image Tagging" subtitle="Tag and classify images with object labels">
|
||||
<div class="ml-4 mt-4 flex flex-col gap-4">
|
||||
<SettingSwitch
|
||||
title="ENABLED"
|
||||
subtitle="If disabled, images will not be tagged. This affects the Things section in the Explore page as well as 'm:' searches."
|
||||
bind:checked={machineLearningConfig.classification.enabled}
|
||||
disabled={disabled || !machineLearningConfig.enabled}
|
||||
/>
|
||||
|
||||
<SettingSwitch
|
||||
title="FACIAL RECOGNITION"
|
||||
subtitle="Recognize and group faces in photos"
|
||||
disabled={disabled || !config.machineLearning.enabled}
|
||||
bind:checked={config.machineLearning.facialRecognitionEnabled}
|
||||
/>
|
||||
<hr />
|
||||
|
||||
<SettingSwitch
|
||||
title="IMAGE TAGGING"
|
||||
subtitle="Tag and classify images"
|
||||
disabled={disabled || !config.machineLearning.enabled}
|
||||
bind:checked={config.machineLearning.tagImageEnabled}
|
||||
/>
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="IMAGE CLASSIFICATION MODEL"
|
||||
bind:value={machineLearningConfig.classification.modelName}
|
||||
required={true}
|
||||
disabled={disabled || !machineLearningConfig.enabled || !machineLearningConfig.classification.enabled}
|
||||
isEdited={machineLearningConfig.classification.modelName !== savedConfig.classification.modelName}
|
||||
>
|
||||
<p slot="desc" class="immich-form-label pb-2 text-sm">
|
||||
The name of an image classification model listed <a
|
||||
href="https://huggingface.co/models?pipeline_tag=image-classification&sort=trending"><u>here</u></a
|
||||
>. It must be tagged with the 'Image Classification' task and must support ONNX conversion.
|
||||
</p>
|
||||
</SettingInputField>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
label="IMAGE CLASSIFICATION THRESHOLD"
|
||||
desc="Minimum confidence score to add a particular object tag. Lower values will add more tags to images, but may result in more false positives. Will not have any effect until the Tag Objects job is re-run."
|
||||
bind:value={machineLearningConfig.classification.minScore}
|
||||
step="0.1"
|
||||
min="0"
|
||||
max="1"
|
||||
disabled={disabled || !machineLearningConfig.enabled || !machineLearningConfig.classification.enabled}
|
||||
isEdited={machineLearningConfig.classification.minScore !== savedConfig.classification.minScore}
|
||||
/>
|
||||
</div>
|
||||
</SettingAccordion>
|
||||
|
||||
<SettingAccordion title="Smart Search" subtitle="Search for images semantically using CLIP embeddings">
|
||||
<div class="ml-4 mt-4 flex flex-col gap-4">
|
||||
<SettingSwitch
|
||||
title="ENABLED"
|
||||
subtitle="If disabled, images will not be encoded for smart search."
|
||||
bind:checked={machineLearningConfig.clip.enabled}
|
||||
disabled={disabled || !machineLearningConfig.enabled}
|
||||
/>
|
||||
|
||||
<hr />
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.TEXT}
|
||||
label="CLIP MODEL"
|
||||
bind:value={machineLearningConfig.clip.modelName}
|
||||
required={true}
|
||||
disabled={disabled || !machineLearningConfig.enabled || !machineLearningConfig.clip.enabled}
|
||||
isEdited={machineLearningConfig.clip.modelName !== savedConfig.clip.modelName}
|
||||
>
|
||||
<p slot="desc" class="immich-form-label pb-2 text-sm">
|
||||
The name of a CLIP model listed <a
|
||||
href="https://clip-as-service.jina.ai/user-guides/benchmark/#size-and-efficiency"><u>here</u></a
|
||||
>. Note that you must re-run the 'Encode CLIP' job for all images upon changing a model.
|
||||
</p>
|
||||
</SettingInputField>
|
||||
</div>
|
||||
</SettingAccordion>
|
||||
|
||||
<SettingAccordion title="Facial Recognition" subtitle="Detect, recognize and group faces in images">
|
||||
<div class="ml-4 mt-4 flex flex-col gap-4">
|
||||
<SettingSwitch
|
||||
title="ENABLED"
|
||||
subtitle="If disabled, images will not be encoded for facial recognition and will not populate the People section in the Explore page."
|
||||
bind:checked={machineLearningConfig.facialRecognition.enabled}
|
||||
disabled={disabled || !machineLearningConfig.enabled}
|
||||
/>
|
||||
|
||||
<hr />
|
||||
|
||||
<SettingSelect
|
||||
label="FACIAL RECOGNITION MODEL"
|
||||
desc="Smaller models are faster and use less memory, but perform worse. Note that you must re-run the Recognize Faces job for all images upon changing a model."
|
||||
name="facial-recognition-model"
|
||||
bind:value={machineLearningConfig.facialRecognition.modelName}
|
||||
options={[
|
||||
{ value: 'buffalo_l', text: 'buffalo_l' },
|
||||
{ value: 'buffalo_s', text: 'buffalo_s' },
|
||||
]}
|
||||
disabled={disabled || !machineLearningConfig.enabled || !machineLearningConfig.facialRecognition.enabled}
|
||||
isEdited={machineLearningConfig.facialRecognition.modelName !== savedConfig.facialRecognition.modelName}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
label="MIN DETECTION SCORE"
|
||||
desc="Minimum confidence score for a face to be detected from 0-1. Lower values will detect more faces but may result in false positives."
|
||||
bind:value={machineLearningConfig.facialRecognition.minScore}
|
||||
step="0.1"
|
||||
min="0"
|
||||
max="1"
|
||||
disabled={disabled || !machineLearningConfig.enabled || !machineLearningConfig.facialRecognition.enabled}
|
||||
isEdited={machineLearningConfig.facialRecognition.minScore !== savedConfig.facialRecognition.minScore}
|
||||
/>
|
||||
|
||||
<SettingInputField
|
||||
inputType={SettingInputFieldType.NUMBER}
|
||||
label="MAX RECOGNITION DISTANCE"
|
||||
desc="Maximum distance between two faces to be considered the same person, ranging from 0-2. Lowering this can prevent labeling two people as the same person, while raising it can prevent labeling the same person as two different people. Note that it is easier to merge two people than to split one person in two, so err on the side of a lower threshold when possible."
|
||||
bind:value={machineLearningConfig.facialRecognition.maxDistance}
|
||||
step="0.1"
|
||||
min="0"
|
||||
max="2"
|
||||
disabled={disabled || !machineLearningConfig.enabled || !machineLearningConfig.facialRecognition.enabled}
|
||||
isEdited={machineLearningConfig.facialRecognition.maxDistance !==
|
||||
savedConfig.facialRecognition.maxDistance}
|
||||
/>
|
||||
</div>
|
||||
</SettingAccordion>
|
||||
|
||||
<SettingButtonsRow
|
||||
on:reset={reset}
|
||||
on:save={saveSetting}
|
||||
on:reset-to-default={resetToDefault}
|
||||
showResetToDefault={!isEqual(config, defaultConfig)}
|
||||
showResetToDefault={!isEqual(savedConfig, defaultConfig)}
|
||||
{disabled}
|
||||
/>
|
||||
</form>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue