Add vision/image support for local inference models (#8442)
Signed-off-by: jh-block <jhugo@block.xyz>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { useState, useEffect, useCallback, useRef } from 'react';
|
||||
import { Download, Trash2, X, ChevronDown, ChevronUp, Settings2 } from 'lucide-react';
|
||||
import { Download, Trash2, X, ChevronDown, ChevronUp, Settings2, Eye } from 'lucide-react';
|
||||
import { Button } from '../../ui/button';
|
||||
import { useModelAndProvider } from '../../ModelAndProviderContext';
|
||||
import { defineMessages, useIntl } from '../../../i18n';
|
||||
@@ -83,8 +83,57 @@ const i18n = defineMessages({
|
||||
id: 'localInferenceSettings.modelSettingsTitle',
|
||||
defaultMessage: 'Model settings',
|
||||
},
|
||||
vision: {
|
||||
id: 'localInferenceSettings.vision',
|
||||
defaultMessage: 'Vision',
|
||||
},
|
||||
visionEncoderDownloading: {
|
||||
id: 'localInferenceSettings.visionEncoderDownloading',
|
||||
defaultMessage: 'Vision encoder downloading…',
|
||||
},
|
||||
visionEncoderNotDownloaded: {
|
||||
id: 'localInferenceSettings.visionEncoderNotDownloaded',
|
||||
defaultMessage: 'Vision encoder not downloaded',
|
||||
},
|
||||
});
|
||||
|
||||
const VisionBadge = ({ model, intl }: { model: LocalModelResponse; intl: ReturnType<typeof useIntl> }) => {
|
||||
if (!model.vision_capable) return null;
|
||||
|
||||
const mmproj = model.mmproj_status;
|
||||
const isDownloaded = mmproj?.state === 'Downloaded';
|
||||
const isDownloading = mmproj?.state === 'Downloading';
|
||||
|
||||
if (isDownloaded) {
|
||||
return (
|
||||
<span className="inline-flex items-center gap-1 text-xs text-green-400 bg-green-500/10 px-2 py-0.5 rounded">
|
||||
<Eye className="w-3 h-3" />
|
||||
{intl.formatMessage(i18n.vision)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
if (isDownloading) {
|
||||
const percent = mmproj && 'progress_percent' in mmproj
|
||||
? Math.round(mmproj.progress_percent)
|
||||
: null;
|
||||
return (
|
||||
<span className="inline-flex items-center gap-1 text-xs text-yellow-400 bg-yellow-500/10 px-2 py-0.5 rounded">
|
||||
<Eye className="w-3 h-3" />
|
||||
{intl.formatMessage(i18n.visionEncoderDownloading)}
|
||||
{percent != null && ` ${percent}%`}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<span className="inline-flex items-center gap-1 text-xs text-text-muted bg-background-subtle px-2 py-0.5 rounded">
|
||||
<Eye className="w-3 h-3" />
|
||||
{intl.formatMessage(i18n.vision)}
|
||||
</span>
|
||||
);
|
||||
};
|
||||
|
||||
const formatBytes = (bytes: number): string => {
|
||||
if (bytes < 1024) return `${bytes}B`;
|
||||
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(0)}KB`;
|
||||
@@ -128,6 +177,19 @@ export const LocalInferenceSettings = () => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
// Poll model list while any vision encoder is downloading
|
||||
useEffect(() => {
|
||||
const hasDownloadingMmproj = models.some(
|
||||
(m) => m.vision_capable && m.mmproj_status?.state === 'Downloading'
|
||||
);
|
||||
if (!hasDownloadingMmproj) return;
|
||||
|
||||
const interval = setInterval(() => {
|
||||
loadModels();
|
||||
}, 2000);
|
||||
return () => clearInterval(interval);
|
||||
}, [models, loadModels]);
|
||||
|
||||
const selectModel = async (modelId: string) => {
|
||||
try {
|
||||
await setConfigProvider({
|
||||
@@ -365,6 +427,7 @@ export const LocalInferenceSettings = () => {
|
||||
{intl.formatMessage(i18n.recommended)}
|
||||
</span>
|
||||
)}
|
||||
<VisionBadge model={model} intl={intl} />
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<Button
|
||||
@@ -414,6 +477,7 @@ export const LocalInferenceSettings = () => {
|
||||
{intl.formatMessage(i18n.recommended)}
|
||||
</span>
|
||||
)}
|
||||
<VisionBadge model={model} intl={intl} />
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
|
||||
Reference in New Issue
Block a user