mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-07-29 04:08:12 +08:00
Feat: tenant llm provider (#14595)
### What problem does this PR solve? Python implementation of the Go-based model_provider API suite. ### Type of change - [x] New Feature (non-breaking change which adds functionality) --------- Co-authored-by: bill <yibie_jingnian@163.com>
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useSelectLlmOptionsByModelType } from '@/hooks/use-llm-request';
|
||||
import { useFetchAllAddedModels } from '@/hooks/use-llm-request';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { camelCase } from 'lodash';
|
||||
import { ReactNode, useMemo } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { MinerUOptionsFormField } from './mineru-options-form-field';
|
||||
import { SelectWithSearch } from './originui/select-with-search';
|
||||
import { buildModelTree } from './model-tree-select';
|
||||
import { PaddleOCROptionsFormField } from './paddleocr-options-form-field';
|
||||
import { TreeSelect, TreeSelectNode } from './tree-select';
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
@@ -44,9 +44,9 @@ export function LayoutRecognizeFormField({
|
||||
const form = useFormContext();
|
||||
|
||||
const { t } = useTranslate('knowledgeDetails');
|
||||
const allOptions = useSelectLlmOptionsByModelType();
|
||||
const { data: allAddedModels } = useFetchAllAddedModels();
|
||||
|
||||
const options = useMemo(() => {
|
||||
const treeData = useMemo(() => {
|
||||
const list = optionsWithoutLLM
|
||||
? optionsWithoutLLM
|
||||
: [
|
||||
@@ -60,28 +60,28 @@ export function LayoutRecognizeFormField({
|
||||
value: x,
|
||||
}));
|
||||
|
||||
const image2TextList = [
|
||||
...allOptions[LlmModelType.Image2text],
|
||||
...allOptions[LlmModelType.Ocr],
|
||||
].map((x) => {
|
||||
return {
|
||||
...x,
|
||||
options: x.options.map((y) => {
|
||||
return {
|
||||
...y,
|
||||
label: (
|
||||
<div className="flex justify-between items-center gap-2">
|
||||
{y.label}
|
||||
<span className="text-red-500 text-sm">Experimental</span>
|
||||
</div>
|
||||
),
|
||||
};
|
||||
}),
|
||||
};
|
||||
});
|
||||
const prependNodes: TreeSelectNode[] = list.map((x) => ({
|
||||
id: x.value,
|
||||
title: x.label,
|
||||
}));
|
||||
|
||||
return [...list, ...image2TextList];
|
||||
}, [allOptions, optionsWithoutLLM, t]);
|
||||
const modelTree = buildModelTree(
|
||||
allAddedModels,
|
||||
['image2text', 'ocr'],
|
||||
(node) => (
|
||||
<div className="flex justify-between items-center gap-2 w-full">
|
||||
<span className="flex items-center gap-1.5 truncate">
|
||||
{node.label}
|
||||
</span>
|
||||
<span className="text-state-error text-sm flex-shrink-0">
|
||||
Experimental
|
||||
</span>
|
||||
</div>
|
||||
),
|
||||
);
|
||||
|
||||
return [...prependNodes, ...modelTree];
|
||||
}, [allAddedModels, optionsWithoutLLM, t]);
|
||||
|
||||
return (
|
||||
<FormField
|
||||
@@ -107,11 +107,17 @@ export function LayoutRecognizeFormField({
|
||||
</FormLabel>
|
||||
<div className={horizontal ? 'w-3/4' : 'w-full'}>
|
||||
<FormControl>
|
||||
<SelectWithSearch
|
||||
<TreeSelect
|
||||
{...field}
|
||||
options={options}
|
||||
data={treeData}
|
||||
testId={testId}
|
||||
></SelectWithSearch>
|
||||
showSearch
|
||||
defaultExpandAll
|
||||
renderSelected={(node) => {
|
||||
if (!node) return null;
|
||||
return node.label ?? node.title;
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,33 @@
|
||||
import { getLLMIconName, getLlmNameAndFIdByLlmId } from '@/utils/llm-util';
|
||||
import { parseModelValue } from '@/utils/llm-util';
|
||||
import { memo } from 'react';
|
||||
import { LlmIcon } from '../svg-icon';
|
||||
|
||||
interface IProps {
|
||||
id?: string;
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const LLMLabel = ({ value }: IProps) => {
|
||||
const { llmName, fId } = getLlmNameAndFIdByLlmId(value);
|
||||
export const LLMLabel = ({ value }: IProps) => {
|
||||
const parsed = value ? parseModelValue(value) : null;
|
||||
const modelName = parsed?.model_name;
|
||||
const instanceName = parsed?.model_instance;
|
||||
const iconName = parsed ? parsed.model_provider : '';
|
||||
|
||||
if (!modelName) return null;
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-1 text-xs text-text-secondary">
|
||||
<LlmIcon name={getLLMIconName(fId, llmName)} width={20} height={20} />
|
||||
<span className="flex-1 truncate"> {llmName}</span>
|
||||
<div className="flex items-center gap-1.5 min-w-0">
|
||||
<LlmIcon
|
||||
name={iconName}
|
||||
width={22}
|
||||
height={22}
|
||||
imgClass="size-[22px] flex-shrink-0"
|
||||
/>
|
||||
<span className="font-medium truncate">{modelName}</span>
|
||||
{instanceName && (
|
||||
<span className="text-slate-400 truncate flex-shrink-0">
|
||||
{instanceName}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
|
||||
import * as SelectPrimitive from '@radix-ui/react-select';
|
||||
import { forwardRef, memo, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { LlmSettingFieldItems } from '../llm-setting-items/next';
|
||||
import { Popover, PopoverContent, PopoverTrigger } from '../ui/popover';
|
||||
import { Select, SelectTrigger, SelectValue } from '../ui/select';
|
||||
import LLMLabel from './llm-label';
|
||||
|
||||
export interface NextInnerLLMSelectProps {
|
||||
id?: string;
|
||||
@@ -51,8 +51,6 @@ const NextInnerLLMSelect = forwardRef<
|
||||
}
|
||||
}, [filter, ttsModel]);
|
||||
|
||||
const modelOptions = useComposeLlmOptionsByModelTypes(modelTypes);
|
||||
|
||||
return (
|
||||
<Select disabled={disabled} value={value}>
|
||||
<Popover open={isPopoverOpen} onOpenChange={setIsPopoverOpen}>
|
||||
@@ -66,17 +64,13 @@ const NextInnerLLMSelect = forwardRef<
|
||||
data-testid={triggerTestId}
|
||||
>
|
||||
<SelectValue placeholder={t('common.pleaseSelect')}>
|
||||
{
|
||||
modelOptions
|
||||
.flatMap((x) => x.options)
|
||||
.find((x) => x.value === value)?.label
|
||||
}
|
||||
<LLMLabel value={value} />
|
||||
</SelectValue>
|
||||
</SelectTrigger>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent side={'left'}>
|
||||
<LlmSettingFieldItems
|
||||
options={modelOptions}
|
||||
modelTypes={modelTypes}
|
||||
llmOptionTestIdPrefix={optionTestIdPrefix}
|
||||
></LlmSettingFieldItems>
|
||||
</PopoverContent>
|
||||
|
||||
@@ -1,45 +1,24 @@
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
|
||||
import { ModelTreeSelect } from '@/components/model-tree-select';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { SelectWithSearch } from '../originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '../ragflow-form';
|
||||
|
||||
export type LLMFormFieldProps = {
|
||||
options?: any[];
|
||||
modelTypes?: string[];
|
||||
name?: string;
|
||||
testId?: string;
|
||||
optionTestIdPrefix?: string;
|
||||
config?: any;
|
||||
};
|
||||
|
||||
export const useModelOptions = () => {
|
||||
const modelOptions = useComposeLlmOptionsByModelTypes([
|
||||
LlmModelType.Chat,
|
||||
LlmModelType.Image2text,
|
||||
]);
|
||||
return {
|
||||
modelOptions,
|
||||
};
|
||||
};
|
||||
|
||||
export function LLMFormField({
|
||||
options,
|
||||
name,
|
||||
testId,
|
||||
optionTestIdPrefix,
|
||||
config,
|
||||
}: LLMFormFieldProps) {
|
||||
export function LLMFormField({ name, config, modelTypes }: LLMFormFieldProps) {
|
||||
const { t } = useTranslation();
|
||||
const { modelOptions } = useModelOptions();
|
||||
|
||||
return (
|
||||
<RAGFlowFormItem name={name || 'llm_id'} label={t('chat.model')}>
|
||||
<SelectWithSearch
|
||||
options={options || modelOptions}
|
||||
testId={testId}
|
||||
optionTestIdPrefix={optionTestIdPrefix}
|
||||
{...config}
|
||||
></SelectWithSearch>
|
||||
<ModelTreeSelect
|
||||
allowClear={config?.allowClear ?? false}
|
||||
modelTypes={modelTypes}
|
||||
/>
|
||||
</RAGFlowFormItem>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import { useHandleFreedomChange } from './use-watch-change';
|
||||
|
||||
interface LlmSettingFieldItemsProps {
|
||||
prefix?: string;
|
||||
options?: any[];
|
||||
modelTypes?: string[];
|
||||
llmId?: string;
|
||||
llmSelectTestId?: string;
|
||||
llmOptionTestIdPrefix?: string;
|
||||
@@ -69,7 +69,7 @@ export const LlmSettingSchema = {
|
||||
|
||||
export function LlmSettingFieldItems({
|
||||
prefix,
|
||||
options,
|
||||
modelTypes,
|
||||
llmSelectTestId,
|
||||
llmOptionTestIdPrefix,
|
||||
showFields = [
|
||||
@@ -137,7 +137,7 @@ export function LlmSettingFieldItems({
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<LLMFormField
|
||||
options={options}
|
||||
modelTypes={modelTypes}
|
||||
name={llmId ?? getFieldWithPrefix('llm_id')}
|
||||
testId={llmSelectTestId}
|
||||
optionTestIdPrefix={llmOptionTestIdPrefix}
|
||||
|
||||
190
web/src/components/model-tree-select.tsx
Normal file
190
web/src/components/model-tree-select.tsx
Normal file
@@ -0,0 +1,190 @@
|
||||
import LLMLabel from '@/components/llm-select/llm-label';
|
||||
import { LlmIcon } from '@/components/svg-icon';
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { useFetchAllAddedModels } from '@/hooks/use-llm-request';
|
||||
import { IAddedModel } from '@/interfaces/database/llm';
|
||||
import { getRealModelName } from '@/utils/llm-util';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { TreeSelect, TreeSelectNode } from './tree-select';
|
||||
|
||||
/** Maps form field names to their supported model types */
|
||||
export const ModelTypeMap: Record<string, string[]> = {
|
||||
llm_id: ['chat', 'image2text'],
|
||||
embd_id: ['embedding'],
|
||||
img2txt_id: ['image2text'],
|
||||
asr_id: ['speech2text'],
|
||||
rerank_id: ['rerank'],
|
||||
tts_id: ['tts'],
|
||||
};
|
||||
|
||||
export function buildModelTree(
|
||||
allModels: IAddedModel[],
|
||||
modelTypes: string[],
|
||||
renderLeafLabel?: (
|
||||
node: TreeSelectNode,
|
||||
model: IAddedModel,
|
||||
) => React.ReactNode,
|
||||
): TreeSelectNode[] {
|
||||
const filtered = allModels.filter((m) =>
|
||||
m.model_type?.some((t) => modelTypes.includes(t)),
|
||||
);
|
||||
|
||||
const seenLeafIds = new Set<string>();
|
||||
const providerMap = new Map<string, Map<string, IAddedModel[]>>();
|
||||
|
||||
for (const model of filtered) {
|
||||
let instances = providerMap.get(model.provider_name);
|
||||
if (!instances) {
|
||||
instances = new Map();
|
||||
providerMap.set(model.provider_name, instances);
|
||||
}
|
||||
let modelList = instances.get(model.instance_name);
|
||||
if (!modelList) {
|
||||
modelList = [];
|
||||
instances.set(model.instance_name, modelList);
|
||||
}
|
||||
modelList.push(model);
|
||||
}
|
||||
|
||||
return Array.from(providerMap.entries()).map(([provider, instances]) => ({
|
||||
id: provider,
|
||||
title: provider,
|
||||
children: Array.from(instances.entries()).map(([instance, models]) => ({
|
||||
id: `${provider}||${instance}`,
|
||||
title: instance,
|
||||
children: models.reduce<TreeSelectNode[]>((acc, m) => {
|
||||
const modelName = getRealModelName(m.name);
|
||||
const id = `${modelName}@${m.instance_name}@${m.provider_name}`;
|
||||
if (seenLeafIds.has(id)) return acc;
|
||||
seenLeafIds.add(id);
|
||||
const leafNode: TreeSelectNode = {
|
||||
id,
|
||||
title: modelName,
|
||||
label: (
|
||||
<span className="flex items-center gap-1.5 truncate">
|
||||
<LlmIcon
|
||||
name={m.provider_name}
|
||||
width={22}
|
||||
height={22}
|
||||
imgClass="size-[22px] flex-shrink-0"
|
||||
/>
|
||||
<span className="truncate">{modelName}</span>
|
||||
</span>
|
||||
),
|
||||
data: {
|
||||
provider_name: m.provider_name,
|
||||
instance_name: m.instance_name,
|
||||
model_name: modelName,
|
||||
},
|
||||
};
|
||||
if (renderLeafLabel) {
|
||||
leafNode.label = renderLeafLabel(leafNode, m);
|
||||
}
|
||||
acc.push(leafNode);
|
||||
return acc;
|
||||
}, []),
|
||||
})),
|
||||
}));
|
||||
}
|
||||
|
||||
export interface ModelTreeSelectProps {
|
||||
modelTypes?: string[];
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
disabled?: boolean;
|
||||
placeholder?: string;
|
||||
showSearch?: boolean;
|
||||
allowClear?: boolean;
|
||||
className?: string;
|
||||
renderSelected?: (node: TreeSelectNode | undefined) => React.ReactNode;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
export function ModelTreeSelect({
|
||||
modelTypes = ModelTypeMap.llm_id,
|
||||
value,
|
||||
onChange,
|
||||
disabled,
|
||||
placeholder,
|
||||
showSearch = true,
|
||||
allowClear = false,
|
||||
className,
|
||||
renderSelected,
|
||||
testId,
|
||||
}: ModelTreeSelectProps) {
|
||||
const { data: allAddedModels } = useFetchAllAddedModels();
|
||||
|
||||
const treeData = useMemo(
|
||||
() => buildModelTree(allAddedModels, modelTypes),
|
||||
[allAddedModels, modelTypes],
|
||||
);
|
||||
|
||||
const defaultRenderSelected = useCallback(
|
||||
(node: TreeSelectNode | undefined) => {
|
||||
if (!node?.id) return null;
|
||||
return <LLMLabel value={node.id} />;
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
return (
|
||||
<TreeSelect
|
||||
data={treeData}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
placeholder={placeholder}
|
||||
disabled={disabled}
|
||||
showSearch={showSearch}
|
||||
allowClear={allowClear}
|
||||
defaultExpandAll
|
||||
className={className}
|
||||
renderSelected={renderSelected ?? defaultRenderSelected}
|
||||
testId={testId}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export interface ModelTreeSelectFormFieldProps extends ModelTreeSelectProps {
|
||||
name?: string;
|
||||
label?: string;
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export function ModelTreeSelectFormField({
|
||||
name = 'llm_id',
|
||||
label,
|
||||
tooltip,
|
||||
...rest
|
||||
}: ModelTreeSelectFormFieldProps) {
|
||||
const form = useFormContext();
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
{label && <FormLabel tooltip={tooltip}>{label}</FormLabel>}
|
||||
<FormControl>
|
||||
<ModelTreeSelect
|
||||
{...rest}
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder={rest.placeholder ?? t('common.pleaseSelect')}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -1,9 +1,7 @@
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { ModelTreeSelect } from '@/components/model-tree-select';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useSelectLlmOptionsByModelType } from '@/hooks/use-llm-request';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { SelectWithSearch } from './originui/select-with-search';
|
||||
import { SliderInputFormField } from './slider-input-form-field';
|
||||
import {
|
||||
FormControl,
|
||||
@@ -26,8 +24,6 @@ const RerankId = 'rerank_id';
|
||||
function RerankFormField() {
|
||||
const form = useFormContext();
|
||||
const { t } = useTranslate('knowledgeDetails');
|
||||
const allOptions = useSelectLlmOptionsByModelType();
|
||||
const options = allOptions[LlmModelType.Rerank];
|
||||
|
||||
return (
|
||||
<FormField
|
||||
@@ -37,12 +33,12 @@ function RerankFormField() {
|
||||
<FormItem>
|
||||
<FormLabel tooltip={t('rerankTip')}>{t('rerankModel')}</FormLabel>
|
||||
<FormControl>
|
||||
<SelectWithSearch
|
||||
<ModelTreeSelect
|
||||
modelTypes={['rerank']}
|
||||
allowClear
|
||||
placeholder={t('rerankPlaceholder')}
|
||||
{...field}
|
||||
options={options}
|
||||
></SelectWithSearch>
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
|
||||
269
web/src/components/tree-select.tsx
Normal file
269
web/src/components/tree-select.tsx
Normal file
@@ -0,0 +1,269 @@
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ChevronDown, ChevronRight, Search, X } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export interface TreeSelectNode {
|
||||
id: string;
|
||||
title: string;
|
||||
label?: React.ReactNode;
|
||||
children?: TreeSelectNode[];
|
||||
disabled?: boolean;
|
||||
data?: Record<string, any>;
|
||||
}
|
||||
|
||||
interface TreeSelectProps {
|
||||
data: TreeSelectNode[];
|
||||
value?: string;
|
||||
onChange?: (value: string) => void;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
allowClear?: boolean;
|
||||
showSearch?: boolean;
|
||||
className?: string;
|
||||
defaultExpandAll?: boolean;
|
||||
renderSelected?: (node: TreeSelectNode | undefined) => React.ReactNode;
|
||||
testId?: string;
|
||||
}
|
||||
|
||||
export function TreeSelect({
|
||||
data,
|
||||
value,
|
||||
onChange,
|
||||
placeholder,
|
||||
disabled,
|
||||
allowClear,
|
||||
showSearch,
|
||||
className,
|
||||
defaultExpandAll,
|
||||
renderSelected,
|
||||
testId,
|
||||
}: TreeSelectProps) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [expandedIds, setExpandedIds] = useState<Set<string>>(new Set());
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (!defaultExpandAll) return;
|
||||
const ids = new Set<string>();
|
||||
const walk = (nodes: TreeSelectNode[]) => {
|
||||
for (const node of nodes) {
|
||||
if (node.children?.length) {
|
||||
ids.add(node.id);
|
||||
walk(node.children);
|
||||
}
|
||||
}
|
||||
};
|
||||
walk(data);
|
||||
setExpandedIds(ids);
|
||||
}, [data, defaultExpandAll]);
|
||||
|
||||
const selectedNode = useMemo(() => {
|
||||
const find = (nodes: TreeSelectNode[]): TreeSelectNode | undefined => {
|
||||
for (const node of nodes) {
|
||||
if (node.id === value) return node;
|
||||
if (node.children) {
|
||||
const found = find(node.children);
|
||||
if (found) return found;
|
||||
}
|
||||
}
|
||||
};
|
||||
return find(data);
|
||||
}, [data, value]);
|
||||
|
||||
const isLeaf = useCallback(
|
||||
(node: TreeSelectNode) => !node.children?.length,
|
||||
[],
|
||||
);
|
||||
|
||||
const handleToggle = useCallback((id: string) => {
|
||||
setExpandedIds((prev) => {
|
||||
const next = new Set(prev);
|
||||
if (next.has(id)) {
|
||||
next.delete(id);
|
||||
} else {
|
||||
next.add(id);
|
||||
}
|
||||
return next;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(node: TreeSelectNode) => {
|
||||
if (node.disabled) return;
|
||||
if (isLeaf(node)) {
|
||||
onChange?.(node.id);
|
||||
setOpen(false);
|
||||
setSearchTerm('');
|
||||
} else {
|
||||
handleToggle(node.id);
|
||||
}
|
||||
},
|
||||
[isLeaf, onChange, handleToggle],
|
||||
);
|
||||
|
||||
const handleClear = useCallback(
|
||||
(e: React.MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
onChange?.('');
|
||||
},
|
||||
[onChange],
|
||||
);
|
||||
|
||||
const filterTree = useCallback(
|
||||
(nodes: TreeSelectNode[], term: string): TreeSelectNode[] => {
|
||||
if (!term) return nodes;
|
||||
return nodes.reduce<TreeSelectNode[]>((acc, node) => {
|
||||
const titleMatch = node.title
|
||||
.toLowerCase()
|
||||
.includes(term.toLowerCase());
|
||||
const filteredChildren = node.children
|
||||
? filterTree(node.children, term)
|
||||
: undefined;
|
||||
if (titleMatch || filteredChildren?.length) {
|
||||
acc.push({ ...node, children: filteredChildren ?? node.children });
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const filteredData = useMemo(
|
||||
() => filterTree(data, searchTerm),
|
||||
[data, searchTerm, filterTree],
|
||||
);
|
||||
|
||||
const visibleExpandedIds = useMemo(() => {
|
||||
if (!searchTerm) return expandedIds;
|
||||
const ids = new Set<string>();
|
||||
const walk = (nodes: TreeSelectNode[]) => {
|
||||
for (const node of nodes) {
|
||||
if (node.children?.length) {
|
||||
ids.add(node.id);
|
||||
walk(node.children);
|
||||
}
|
||||
}
|
||||
};
|
||||
walk(filteredData);
|
||||
return ids;
|
||||
}, [searchTerm, expandedIds, filteredData]);
|
||||
|
||||
const renderTree = useCallback(
|
||||
(nodes: TreeSelectNode[], level = 0): React.ReactNode => {
|
||||
return nodes.map((node) => {
|
||||
const leaf = isLeaf(node);
|
||||
const expanded = visibleExpandedIds.has(node.id);
|
||||
const selected = value === node.id;
|
||||
|
||||
return (
|
||||
<div key={node.id}>
|
||||
<div
|
||||
className={cn(
|
||||
'flex items-center rounded-sm cursor-pointer text-sm',
|
||||
'hover:bg-accent transition-colors',
|
||||
!leaf && 'text-text-primary font-medium',
|
||||
selected && 'bg-accent text-accent-foreground',
|
||||
node.disabled && 'opacity-50 pointer-events-none',
|
||||
)}
|
||||
style={{
|
||||
paddingLeft: `${level * 20 + 4}px`,
|
||||
paddingRight: '8px',
|
||||
paddingTop: '6px',
|
||||
paddingBottom: '6px',
|
||||
}}
|
||||
onClick={() => handleSelect(node)}
|
||||
>
|
||||
<span className="w-4 h-4 mr-0.5 flex-shrink-0 flex items-center justify-center">
|
||||
{!leaf && (
|
||||
<>
|
||||
{expanded ? (
|
||||
<ChevronDown className="h-3.5 w-3.5 text-text-secondary" />
|
||||
) : (
|
||||
<ChevronRight className="h-3.5 w-3.5 text-text-secondary" />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</span>
|
||||
<span className="truncate">{node.label ?? node.title}</span>
|
||||
</div>
|
||||
{!leaf && expanded && node.children && (
|
||||
<div>{renderTree(node.children, level + 1)}</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
},
|
||||
[isLeaf, visibleExpandedIds, value, handleSelect],
|
||||
);
|
||||
|
||||
return (
|
||||
<Popover open={open} onOpenChange={setOpen}>
|
||||
<PopoverTrigger asChild disabled={disabled}>
|
||||
<button
|
||||
type="button"
|
||||
data-testid={testId}
|
||||
className={cn(
|
||||
'flex items-center justify-between border border-border-button rounded-md px-3 py-1.5 w-full',
|
||||
'bg-bg-input text-sm',
|
||||
'hover:bg-border-button transition-colors',
|
||||
disabled && 'opacity-50 cursor-not-allowed',
|
||||
className,
|
||||
)}
|
||||
>
|
||||
<span className={cn('truncate', !selectedNode && 'text-slate-400')}>
|
||||
{renderSelected
|
||||
? renderSelected(selectedNode)
|
||||
: selectedNode?.title || placeholder || t('common.pleaseSelect')}
|
||||
</span>
|
||||
<div className="flex items-center ml-2 flex-shrink-0">
|
||||
{allowClear && value ? (
|
||||
<X
|
||||
className="h-4 w-4 opacity-50 hover:opacity-100"
|
||||
onClick={handleClear}
|
||||
/>
|
||||
) : (
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
)}
|
||||
</div>
|
||||
</button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent
|
||||
className="p-0 w-[var(--radix-popover-trigger-width)]"
|
||||
align="start"
|
||||
sideOffset={4}
|
||||
>
|
||||
{showSearch && (
|
||||
<div className="flex items-center border-b px-3 py-2">
|
||||
<Search className="h-4 w-4 text-slate-400 mr-2 flex-shrink-0" />
|
||||
<input
|
||||
type="text"
|
||||
className="flex-1 bg-transparent text-sm outline-none placeholder:text-slate-400"
|
||||
placeholder={t('common.search')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div
|
||||
className="max-h-60 overflow-auto p-1"
|
||||
onWheel={(e) => e.stopPropagation()}
|
||||
>
|
||||
{filteredData.length > 0 ? (
|
||||
renderTree(filteredData)
|
||||
) : (
|
||||
<div className="py-6 text-center text-sm text-slate-400">
|
||||
{t('common.noData')}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,8 @@
|
||||
export enum ModelStatus {
|
||||
Active = 'active',
|
||||
Inactive = 'inactive',
|
||||
}
|
||||
|
||||
export enum LLMFactory {
|
||||
TongYiQianWen = 'Tongyi-Qianwen',
|
||||
Moonshot = 'Moonshot',
|
||||
@@ -140,6 +145,24 @@ export const IconMap = {
|
||||
[LLMFactory.Perplexity]: 'perplexity',
|
||||
};
|
||||
|
||||
export const ModelTypeToField: Record<string, string> = {
|
||||
chat: 'llm_id',
|
||||
embedding: 'embd_id',
|
||||
image2text: 'img2txt_id',
|
||||
speech2text: 'asr_id',
|
||||
rerank: 'rerank_id',
|
||||
tts: 'tts_id',
|
||||
};
|
||||
|
||||
export const FieldToModelType: Record<string, string> = {
|
||||
llm_id: 'chat',
|
||||
embd_id: 'embedding',
|
||||
img2txt_id: 'vision',
|
||||
asr_id: 'asr',
|
||||
rerank_id: 'rerank',
|
||||
tts_id: 'tts',
|
||||
};
|
||||
|
||||
export const APIMapUrl = {
|
||||
[LLMFactory.OpenAI]: 'https://platform.openai.com/api-keys',
|
||||
[LLMFactory.Anthropic]: 'https://console.anthropic.com/settings/keys',
|
||||
|
||||
@@ -29,7 +29,7 @@ import {
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { useTranslate } from './common-hooks';
|
||||
import { useSetPaginationParams } from './route-hook';
|
||||
import { useFetchTenantInfo, useSaveSetting } from './use-user-setting-request';
|
||||
import { useSaveSetting } from './use-user-setting-request';
|
||||
|
||||
export function usePrevious<T>(value: T) {
|
||||
const ref = useRef<T>();
|
||||
@@ -748,12 +748,6 @@ export const useSelectItem = (defaultId?: string) => {
|
||||
return { selectedId, handleItemClick };
|
||||
};
|
||||
|
||||
export const useFetchModelId = () => {
|
||||
const { data: tenantInfo } = useFetchTenantInfo(true);
|
||||
|
||||
return tenantInfo?.llm_id ?? '';
|
||||
};
|
||||
|
||||
const ChunkTokenNumMap = {
|
||||
naive: 128,
|
||||
knowledge_graph: 8192,
|
||||
|
||||
@@ -197,6 +197,10 @@ export const useNavigatePage = () => {
|
||||
[navigate],
|
||||
);
|
||||
|
||||
const navigateToModelSetting = useCallback(() => {
|
||||
navigate(`${Routes.UserSetting}${Routes.Model}`);
|
||||
}, [navigate]);
|
||||
|
||||
return {
|
||||
navigateToDatasetList,
|
||||
navigateToDataset,
|
||||
@@ -223,5 +227,6 @@ export const useNavigatePage = () => {
|
||||
navigateToDataSourceDetail,
|
||||
navigateToMemory,
|
||||
navigateToMemoryList,
|
||||
navigateToModelSetting,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,197 +1,68 @@
|
||||
import { LlmIcon } from '@/components/svg-icon';
|
||||
import message from '@/components/ui/message';
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { DefaultOptionType } from '@/interfaces/antd-compat';
|
||||
import { ResponseGetType } from '@/interfaces/database/base';
|
||||
import { ModelTypeToField } from '@/constants/llm';
|
||||
import {
|
||||
IFactory,
|
||||
IAddedModel,
|
||||
IAvailableProvider,
|
||||
IDefaultModel,
|
||||
IInstanceModel,
|
||||
IMyLlmValue,
|
||||
IThirdOAIModelCollection as IThirdAiModelCollection,
|
||||
IThirdOAIModel,
|
||||
IThirdOAIModelCollection,
|
||||
IProviderInstance,
|
||||
} from '@/interfaces/database/llm';
|
||||
import {
|
||||
IAddLlmRequestBody,
|
||||
IDeleteLlmRequestBody,
|
||||
IAddInstanceModelRequestBody,
|
||||
IAddProviderInstanceRequestBody,
|
||||
IAddProviderRequestBody,
|
||||
IDeleteProviderInstanceRequestBody,
|
||||
IListAllModelsRequestParams,
|
||||
IListProvidersRequestParams,
|
||||
ISetDefaultModelRequestBody,
|
||||
IUpdateModelStatusRequestBody,
|
||||
} from '@/interfaces/request/llm';
|
||||
import userService from '@/services/user-service';
|
||||
import { getLLMIconName, getRealModelName } from '@/utils/llm-util';
|
||||
import llmService from '@/services/llm-service';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { buildLlmUuid } from '@/utils/llm-util';
|
||||
import { buildModelValue, parseModelValue } from '@/utils/llm-util';
|
||||
import { useWarnEmptyModel } from './use-warn-empty-model';
|
||||
|
||||
export const enum LLMApiAction {
|
||||
LlmList = 'llmList',
|
||||
MyLlmList = 'myLlmList',
|
||||
MyLlmListDetailed = 'myLlmListDetailed',
|
||||
FactoryList = 'factoryList',
|
||||
SaveApiKey = 'saveApiKey',
|
||||
SaveTenantInfo = 'saveTenantInfo',
|
||||
AddLlm = 'addLlm',
|
||||
DeleteLlm = 'deleteLlm',
|
||||
EnableLlm = 'enableLlm',
|
||||
DeleteFactory = 'deleteFactory',
|
||||
AllModels = 'allModels',
|
||||
AvailableProviders = 'availableProviders',
|
||||
AddedProviders = 'addedProviders',
|
||||
AddProvider = 'addProvider',
|
||||
AddProviderInstance = 'addProviderInstance',
|
||||
AddInstanceModel = 'addInstanceModel',
|
||||
DeleteProviderInstance = 'deleteProviderInstance',
|
||||
ListDefaultModels = 'listDefaultModels',
|
||||
SetDefaultModel = 'setDefaultModel',
|
||||
}
|
||||
|
||||
export const useFetchLlmList = (modelType?: LlmModelType) => {
|
||||
const { data } = useQuery<IThirdAiModelCollection>({
|
||||
queryKey: [LLMApiAction.LlmList],
|
||||
initialData: {},
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.llmList({ model_type: modelType });
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
export const LlmKeys = {
|
||||
availableProviders: () => [LLMApiAction.AvailableProviders] as const,
|
||||
addedProviders: () => [LLMApiAction.AddedProviders] as const,
|
||||
allModels: (modelType?: string) =>
|
||||
[LLMApiAction.AllModels, modelType] as const,
|
||||
providerInstances: (providerName: string) =>
|
||||
[LLMApiAction.AddedProviders, providerName, 'instances'] as const,
|
||||
instanceModels: (providerName: string, instanceName: string) =>
|
||||
[
|
||||
LLMApiAction.AddedProviders,
|
||||
providerName,
|
||||
instanceName,
|
||||
'models',
|
||||
] as const,
|
||||
defaultModels: () => [LLMApiAction.ListDefaultModels] as const,
|
||||
};
|
||||
|
||||
type IThirdOAIModelWithUuid = IThirdOAIModel & { uuid: string };
|
||||
|
||||
export function useSelectFlatLlmList(modelType?: LlmModelType) {
|
||||
const llmList = useFetchLlmList(modelType);
|
||||
|
||||
return Object.values(llmList).reduce<IThirdOAIModelWithUuid[]>((pre, cur) => {
|
||||
pre.push(...cur.map((x) => ({ ...x, uuid: buildLlmUuid(x) })));
|
||||
|
||||
return pre;
|
||||
}, []);
|
||||
}
|
||||
|
||||
export function useFindLlmByUuid(modelType?: LlmModelType) {
|
||||
const flatList = useSelectFlatLlmList(modelType);
|
||||
|
||||
return (uuid: string) => {
|
||||
return flatList.find((x) => x.uuid === uuid);
|
||||
};
|
||||
}
|
||||
|
||||
function buildLlmOptionsWithIcon(x: IThirdOAIModel) {
|
||||
return {
|
||||
label: (
|
||||
<div className="flex items-center justify-center gap-2">
|
||||
<LlmIcon
|
||||
name={getLLMIconName(x.fid, x.llm_name)}
|
||||
width={24}
|
||||
height={24}
|
||||
size={'small'}
|
||||
imgClass="size-6"
|
||||
/>
|
||||
<span>{getRealModelName(x.llm_name)}</span>
|
||||
</div>
|
||||
),
|
||||
value: `${x.llm_name}@${x.fid}`,
|
||||
disabled: !x.available,
|
||||
is_tools: x.is_tools,
|
||||
};
|
||||
}
|
||||
|
||||
export const useSelectLlmOptionsByModelType = () => {
|
||||
const llmInfo: IThirdOAIModelCollection = useFetchLlmList();
|
||||
|
||||
const groupImage2TextOptions = useCallback(() => {
|
||||
const modelType = LlmModelType.Image2text;
|
||||
const modelTag = modelType.toUpperCase();
|
||||
return Object.entries(llmInfo)
|
||||
.map(([key, value]) => {
|
||||
return {
|
||||
label: key,
|
||||
options: value
|
||||
.filter(
|
||||
(x) =>
|
||||
(x.model_type.includes(modelType) ||
|
||||
(x.tags && x.tags.includes(modelTag))) &&
|
||||
x.available &&
|
||||
x.status === '1',
|
||||
)
|
||||
.map(buildLlmOptionsWithIcon),
|
||||
};
|
||||
})
|
||||
.filter((x) => x.options.length > 0);
|
||||
}, [llmInfo]);
|
||||
|
||||
const groupOptionsByModelType = useCallback(
|
||||
(modelType: LlmModelType) => {
|
||||
return Object.entries(llmInfo)
|
||||
.filter(([, value]) =>
|
||||
modelType
|
||||
? value.some((x) => x.model_type.includes(modelType))
|
||||
: true,
|
||||
)
|
||||
.map(([key, value]) => {
|
||||
return {
|
||||
label: key,
|
||||
options: value
|
||||
.filter(
|
||||
(x) =>
|
||||
(modelType ? x.model_type.includes(modelType) : true) &&
|
||||
x.available,
|
||||
)
|
||||
.map(buildLlmOptionsWithIcon),
|
||||
};
|
||||
})
|
||||
.filter((x) => x.options.length > 0);
|
||||
},
|
||||
[llmInfo],
|
||||
);
|
||||
|
||||
return {
|
||||
[LlmModelType.Chat]: groupOptionsByModelType(LlmModelType.Chat),
|
||||
[LlmModelType.Embedding]: groupOptionsByModelType(LlmModelType.Embedding),
|
||||
[LlmModelType.Image2text]: groupImage2TextOptions(),
|
||||
[LlmModelType.Speech2text]: groupOptionsByModelType(
|
||||
LlmModelType.Speech2text,
|
||||
),
|
||||
[LlmModelType.Rerank]: groupOptionsByModelType(LlmModelType.Rerank),
|
||||
[LlmModelType.TTS]: groupOptionsByModelType(LlmModelType.TTS),
|
||||
[LlmModelType.Ocr]: groupOptionsByModelType(LlmModelType.Ocr),
|
||||
};
|
||||
};
|
||||
|
||||
// Merge different types of models from the same manufacturer under one manufacturer
|
||||
export const useComposeLlmOptionsByModelTypes = (
|
||||
modelTypes: LlmModelType[],
|
||||
) => {
|
||||
const allOptions = useSelectLlmOptionsByModelType();
|
||||
return modelTypes.reduce<
|
||||
(DefaultOptionType & {
|
||||
options: {
|
||||
label: JSX.Element;
|
||||
value: string;
|
||||
disabled: boolean;
|
||||
is_tools: boolean;
|
||||
}[];
|
||||
})[]
|
||||
>((pre, cur) => {
|
||||
const options = allOptions[cur];
|
||||
options.forEach((x) => {
|
||||
const item = pre.find((y) => y.label === x.label);
|
||||
if (item) {
|
||||
x.options.forEach((y) => {
|
||||
// A model that is both an image2text and speech2text model
|
||||
if (!item.options.some((z) => z.value === y.value)) {
|
||||
item.options.push(y);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
pre.push(x);
|
||||
}
|
||||
});
|
||||
|
||||
return pre;
|
||||
}, []);
|
||||
};
|
||||
|
||||
export const useFetchLlmFactoryList = (): ResponseGetType<IFactory[]> => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: [LLMApiAction.FactoryList],
|
||||
export const useFetchAvailableProviders = () => {
|
||||
const { data, isFetching: loading } = useQuery<IAvailableProvider[]>({
|
||||
queryKey: LlmKeys.availableProviders(),
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.factoriesList();
|
||||
const params: IListProvidersRequestParams = { available: true };
|
||||
const { data } = await llmService.listProviders({ params }, true);
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
@@ -200,190 +71,198 @@ export const useFetchLlmFactoryList = (): ResponseGetType<IFactory[]> => {
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchAddedProviders = () => {
|
||||
const { data, isFetching: loading } = useQuery<IAvailableProvider[]>({
|
||||
queryKey: LlmKeys.addedProviders(),
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await llmService.listProviders({ params: {} }, true);
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchAllAddedModels = (modelType?: string) => {
|
||||
const { data, isFetching: loading } = useQuery<IAddedModel[]>({
|
||||
queryKey: LlmKeys.allModels(modelType),
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const params: IListAllModelsRequestParams = {};
|
||||
if (modelType) {
|
||||
params.type = modelType;
|
||||
}
|
||||
const { data } = await llmService.listAllAddedModels({ params }, true);
|
||||
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export function useFindLlmByUuid() {
|
||||
const { data: models } = useFetchAllAddedModels();
|
||||
|
||||
return (uuid: string) => {
|
||||
const parsed = parseModelValue(uuid);
|
||||
if (parsed) {
|
||||
return models.find(
|
||||
(m) =>
|
||||
m.name === parsed.model_name &&
|
||||
m.instance_name === parsed.model_instance &&
|
||||
m.provider_name === parsed.model_provider,
|
||||
);
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
}
|
||||
|
||||
export const useFetchProviderInstances = (providerName: string) => {
|
||||
const { data, isFetching: loading } = useQuery<IProviderInstance[]>({
|
||||
queryKey: LlmKeys.providerInstances(providerName),
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
enabled: !!providerName,
|
||||
queryFn: async () => {
|
||||
const { data } = await llmService.listProviderInstances(
|
||||
{ provider_name: providerName },
|
||||
true,
|
||||
);
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchInstanceModels = (
|
||||
providerName: string,
|
||||
instanceName: string,
|
||||
) => {
|
||||
const { data, isFetching: loading } = useQuery<IInstanceModel[]>({
|
||||
queryKey: LlmKeys.instanceModels(providerName, instanceName),
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
enabled: !!providerName && !!instanceName,
|
||||
queryFn: async () => {
|
||||
const { data } = await llmService.listInstanceModels(
|
||||
{ provider_name: providerName, instance_name: instanceName },
|
||||
true,
|
||||
);
|
||||
return data?.data ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export type LlmItem = { name: string; logo: string } & IMyLlmValue;
|
||||
|
||||
export const useFetchMyLlmList = (): ResponseGetType<
|
||||
Record<string, IMyLlmValue>
|
||||
> => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: [LLMApiAction.MyLlmList],
|
||||
initialData: {},
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.myLlm();
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchMyLlmListDetailed = (): ResponseGetType<
|
||||
Record<string, any>
|
||||
> => {
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: [LLMApiAction.MyLlmListDetailed],
|
||||
initialData: {},
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await userService.myLlm({ include_details: true });
|
||||
|
||||
return data?.data ?? {};
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useSelectLlmList = () => {
|
||||
const { data: myLlmList, loading: myLlmListLoading } = useFetchMyLlmList();
|
||||
const { data: factoryList, loading: factoryListLoading } =
|
||||
useFetchLlmFactoryList();
|
||||
|
||||
const nextMyLlmList: Array<LlmItem> = useMemo(() => {
|
||||
return Object.entries(myLlmList).map(([key, value]) => ({
|
||||
name: key,
|
||||
logo: factoryList.find((x) => x.name === key)?.logo ?? '',
|
||||
...value,
|
||||
llm: value.llm?.map((x) => ({ ...x, name: x.name })),
|
||||
}));
|
||||
}, [myLlmList, factoryList]);
|
||||
|
||||
const nextFactoryList = useMemo(() => {
|
||||
const currentList = factoryList.filter((x) =>
|
||||
Object.keys(myLlmList).every((y) => y !== x.name),
|
||||
);
|
||||
return currentList;
|
||||
// return sortLLmFactoryListBySpecifiedOrder(currentList);
|
||||
}, [factoryList, myLlmList]);
|
||||
|
||||
return {
|
||||
myLlmList: nextMyLlmList,
|
||||
factoryList: nextFactoryList,
|
||||
loading: myLlmListLoading || factoryListLoading,
|
||||
};
|
||||
};
|
||||
|
||||
export interface IApiKeySavingParams {
|
||||
llm_factory: string;
|
||||
api_key: string;
|
||||
llm_name?: string;
|
||||
model_type?: string;
|
||||
base_url?: string;
|
||||
source_fid?: string;
|
||||
verify?: boolean;
|
||||
}
|
||||
|
||||
export const useSaveApiKey = () => {
|
||||
const queryClient = useQueryClient();
|
||||
// const { t } = useTranslation();
|
||||
export const useAddProvider = () => {
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [LLMApiAction.SaveApiKey],
|
||||
mutationFn: async (params: IApiKeySavingParams) => {
|
||||
const { data } = await userService.setApiKey(params);
|
||||
if (data.code === 0) {
|
||||
// message.success(t('message.modified'));
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmList] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [LLMApiAction.MyLlmListDetailed],
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.FactoryList] });
|
||||
mutationKey: [LLMApiAction.AddProvider],
|
||||
mutationFn: async (params: IAddProviderRequestBody) => {
|
||||
try {
|
||||
const { data: listRes } = await llmService.listProviders(
|
||||
{ params: {} },
|
||||
true,
|
||||
);
|
||||
const isProviderAdded = listRes?.data?.some(
|
||||
(p: IAvailableProvider) => p.name === params.provider_name,
|
||||
);
|
||||
if (isProviderAdded) {
|
||||
return { code: 0, data: null };
|
||||
}
|
||||
} catch {
|
||||
// ignore list failure and proceed to add
|
||||
}
|
||||
const { data } = await llmService.addProvider(params);
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, saveApiKey: mutateAsync };
|
||||
return { data, loading, addProvider: mutateAsync };
|
||||
};
|
||||
|
||||
export interface ISystemModelSettingSavingParams {
|
||||
tenant_id: string;
|
||||
name?: string;
|
||||
asr_id: string;
|
||||
embd_id: string;
|
||||
img2txt_id: string;
|
||||
llm_id: string;
|
||||
}
|
||||
|
||||
export const useSaveTenantInfo = () => {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [LLMApiAction.SaveTenantInfo],
|
||||
mutationFn: async (params: ISystemModelSettingSavingParams) => {
|
||||
const { data } = await userService.setTenantInfo(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.modified'));
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, saveTenantInfo: mutateAsync };
|
||||
};
|
||||
|
||||
export const useAddLlm = () => {
|
||||
export const useAddProviderInstance = () => {
|
||||
const { addProvider } = useAddProvider();
|
||||
const queryClient = useQueryClient();
|
||||
// const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [LLMApiAction.AddLlm],
|
||||
mutationFn: async (params: IAddLlmRequestBody & { verify?: boolean }) => {
|
||||
const { data } = await userService.addLlm(params);
|
||||
mutationKey: [LLMApiAction.AddProviderInstance],
|
||||
mutationFn: async (
|
||||
params: IAddProviderInstanceRequestBody & { verify?: boolean },
|
||||
) => {
|
||||
try {
|
||||
await addProvider({ provider_name: params.llm_factory });
|
||||
|
||||
const { data: instancesRes } = await llmService.listProviderInstances(
|
||||
{ provider_name: params.llm_factory },
|
||||
true,
|
||||
);
|
||||
const instanceExists = instancesRes?.data?.some(
|
||||
(i: IProviderInstance) => i.instance_name === params.instance_name,
|
||||
);
|
||||
if (instanceExists && !params.verify) {
|
||||
return { code: 0, data: null };
|
||||
}
|
||||
} catch {
|
||||
// ignore list failure and proceed to add
|
||||
}
|
||||
|
||||
const { data } = await llmService.addProviderInstance(params);
|
||||
if (data.code === 0 && !params.verify) {
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmList] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [LLMApiAction.MyLlmListDetailed],
|
||||
queryKey: LlmKeys.addedProviders(),
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.FactoryList] });
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.LlmList] });
|
||||
// message.success(t('message.modified'));
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, addLlm: mutateAsync };
|
||||
return { data, loading, addProviderInstance: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteLlm = () => {
|
||||
export const useAddInstanceModel = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [LLMApiAction.DeleteLlm],
|
||||
mutationFn: async (params: IDeleteLlmRequestBody) => {
|
||||
const { data } = await userService.deleteLlm(params);
|
||||
mutationKey: [LLMApiAction.AddInstanceModel],
|
||||
mutationFn: async (
|
||||
params: {
|
||||
provider_name: string;
|
||||
instance_name: string;
|
||||
} & IAddInstanceModelRequestBody,
|
||||
) => {
|
||||
const { data } = await llmService.addInstanceModel(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmList] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [LLMApiAction.MyLlmListDetailed],
|
||||
queryKey: LlmKeys.addedProviders(),
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.FactoryList] });
|
||||
message.success(t('message.deleted'));
|
||||
}
|
||||
return data.code;
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteLlm: mutateAsync };
|
||||
return { data, loading, addInstanceModel: mutateAsync };
|
||||
};
|
||||
|
||||
export const useEnableLlm = () => {
|
||||
export const useDeleteProviderInstance = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
@@ -391,52 +270,104 @@ export const useEnableLlm = () => {
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [LLMApiAction.EnableLlm],
|
||||
mutationFn: async (params: IDeleteLlmRequestBody & { enable: boolean }) => {
|
||||
const reqParam: IDeleteLlmRequestBody & {
|
||||
enable?: boolean;
|
||||
status?: 1 | 0;
|
||||
} = { ...params, status: params.enable ? 1 : 0 };
|
||||
delete reqParam.enable;
|
||||
const { data } = await userService.enableLlm(reqParam);
|
||||
mutationKey: [LLMApiAction.DeleteProviderInstance],
|
||||
mutationFn: async (params: IDeleteProviderInstanceRequestBody) => {
|
||||
const { data } = await llmService.deleteProviderInstance(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmList] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [LLMApiAction.MyLlmListDetailed],
|
||||
queryKey: LlmKeys.addedProviders(),
|
||||
exact: true,
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.FactoryList] });
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: LlmKeys.providerInstances(params.provider_name),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: LlmKeys.defaultModels(),
|
||||
});
|
||||
|
||||
message.success(t('message.deleted'));
|
||||
}
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteProviderInstance: mutateAsync };
|
||||
};
|
||||
|
||||
export const useUpdateModelStatus = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const { isPending: loading, mutateAsync } = useMutation({
|
||||
mutationKey: [LLMApiAction.AddedProviders, 'updateModelStatus'],
|
||||
mutationFn: async (params: IUpdateModelStatusRequestBody) => {
|
||||
const { data } = await llmService.updateModelStatus(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.modified'));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: LlmKeys.defaultModels(),
|
||||
});
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: LlmKeys.instanceModels(
|
||||
params.provider_name,
|
||||
params.instance_name,
|
||||
),
|
||||
});
|
||||
}
|
||||
return data.code;
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, enableLlm: mutateAsync };
|
||||
return { loading, updateModelStatus: mutateAsync };
|
||||
};
|
||||
|
||||
export const useDeleteFactory = () => {
|
||||
export const useFetchDefaultModels = () => {
|
||||
const { data, isFetching: loading } = useQuery<IDefaultModel[]>({
|
||||
queryKey: LlmKeys.defaultModels(),
|
||||
initialData: [],
|
||||
gcTime: 0,
|
||||
queryFn: async () => {
|
||||
const { data } = await llmService.listDefaultModels({}, true);
|
||||
return data?.data?.models ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
export const useFetchDefaultModelDictionary = (showEmptyModelWarn = false) => {
|
||||
const { data: defaultModels } = useFetchDefaultModels();
|
||||
|
||||
const result = useMemo(() => {
|
||||
const dict: Record<string, string> = {};
|
||||
Object.entries(ModelTypeToField).forEach(([key, field]) => {
|
||||
const model = defaultModels.find((m) => m.model_type === key);
|
||||
dict[field] = model && model.enable ? buildModelValue(model) : '';
|
||||
});
|
||||
return dict;
|
||||
}, [defaultModels]);
|
||||
|
||||
useWarnEmptyModel(showEmptyModelWarn, result.embd_id, result.llm_id);
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
export const useSetDefaultModel = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [LLMApiAction.DeleteFactory],
|
||||
mutationFn: async (params: IDeleteLlmRequestBody) => {
|
||||
const { data } = await userService.deleteFactory(params);
|
||||
|
||||
const { isPending: loading, mutateAsync } = useMutation({
|
||||
mutationKey: [LLMApiAction.SetDefaultModel],
|
||||
mutationFn: async (params: ISetDefaultModelRequestBody) => {
|
||||
const { data } = await llmService.setDefaultModel(params);
|
||||
if (data.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.MyLlmList] });
|
||||
message.success(t('message.modified'));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [LLMApiAction.MyLlmListDetailed],
|
||||
queryKey: LlmKeys.defaultModels(),
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.FactoryList] });
|
||||
queryClient.invalidateQueries({ queryKey: [LLMApiAction.LlmList] });
|
||||
message.success(t('message.deleted'));
|
||||
}
|
||||
return data.code;
|
||||
return data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, deleteFactory: mutateAsync };
|
||||
return { loading, setDefaultModel: mutateAsync };
|
||||
};
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import message from '@/components/ui/message';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { ResponseGetType } from '@/interfaces/database/base';
|
||||
import { IToken } from '@/interfaces/database/chat';
|
||||
import { ITenantInfo } from '@/interfaces/database/dataset';
|
||||
@@ -11,7 +10,6 @@ import {
|
||||
} from '@/interfaces/database/user-setting';
|
||||
import { ISetLangfuseConfigRequestBody } from '@/interfaces/request/system';
|
||||
import { DEFAULT_LANGUAGE_CODE, supportedLanguages } from '@/locales/config';
|
||||
import { Routes } from '@/routes';
|
||||
import userService, {
|
||||
addTenantUser,
|
||||
agreeTenant,
|
||||
@@ -20,11 +18,10 @@ import userService, {
|
||||
listTenantUser,
|
||||
} from '@/services/user-service';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
import { useWarnEmptyModel } from './use-warn-empty-model';
|
||||
|
||||
export const enum UserSettingApiAction {
|
||||
UserInfo = 'userInfo',
|
||||
@@ -69,11 +66,10 @@ export const useFetchUserInfo = (): ResponseGetType<IUserInfo> => {
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
// Stop using this interface to retrieve the default model; instead, directly call `useFetchDefaultModelDictionary`.
|
||||
export const useFetchTenantInfo = (
|
||||
showEmptyModelWarn = false,
|
||||
): ResponseGetType<ITenantInfo> => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { data, isFetching: loading } = useQuery({
|
||||
queryKey: [UserSettingApiAction.TenantInfo, showEmptyModelWarn],
|
||||
initialData: {},
|
||||
@@ -84,27 +80,6 @@ export const useFetchTenantInfo = (
|
||||
// llm_id is chat_id
|
||||
// asr_id is speech2txt
|
||||
const { data } = res;
|
||||
if (
|
||||
showEmptyModelWarn &&
|
||||
(isEmpty(data.embd_id) || isEmpty(data.llm_id))
|
||||
) {
|
||||
Modal.warning({
|
||||
title: t('common.warn'),
|
||||
content: (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(t('setting.modelProvidersWarn')),
|
||||
}}
|
||||
></div>
|
||||
),
|
||||
closable: false,
|
||||
showCancel: false,
|
||||
onOk() {
|
||||
// window.open('/user-setting/model', '_self');
|
||||
navigate(`${Routes.UserSetting}${Routes.Model}`);
|
||||
},
|
||||
});
|
||||
}
|
||||
data.chat_id = data.llm_id;
|
||||
data.speech2text_id = data.asr_id;
|
||||
|
||||
@@ -115,6 +90,8 @@ export const useFetchTenantInfo = (
|
||||
},
|
||||
});
|
||||
|
||||
useWarnEmptyModel(showEmptyModelWarn, data?.embd_id, data?.llm_id);
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
|
||||
43
web/src/hooks/use-warn-empty-model.tsx
Normal file
43
web/src/hooks/use-warn-empty-model.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigatePage } from './logic-hooks/navigate-hooks';
|
||||
|
||||
export const useWarnEmptyModel = (
|
||||
showEmptyModelWarn: boolean,
|
||||
embdId?: string,
|
||||
llmId?: string,
|
||||
) => {
|
||||
const { t } = useTranslation();
|
||||
const warnedRef = useRef(false);
|
||||
const { navigateToModelSetting } = useNavigatePage();
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
showEmptyModelWarn &&
|
||||
!warnedRef.current &&
|
||||
(isEmpty(embdId) || isEmpty(llmId)) &&
|
||||
typeof embdId === 'string' &&
|
||||
typeof llmId === 'string'
|
||||
) {
|
||||
warnedRef.current = true;
|
||||
Modal.warning({
|
||||
title: t('common.warn'),
|
||||
content: (
|
||||
<div
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: DOMPurify.sanitize(t('setting.modelProvidersWarn')),
|
||||
}}
|
||||
></div>
|
||||
),
|
||||
closable: false,
|
||||
showCancel: false,
|
||||
onOk() {
|
||||
navigateToModelSetting();
|
||||
},
|
||||
});
|
||||
}
|
||||
}, [showEmptyModelWarn, embdId, llmId, navigateToModelSetting, t]);
|
||||
};
|
||||
@@ -40,3 +40,41 @@ export interface Llm {
|
||||
status: '0' | '1';
|
||||
used_token: number;
|
||||
}
|
||||
|
||||
export interface IAvailableProvider {
|
||||
name: string;
|
||||
model_types: string[];
|
||||
url: { default?: string; [key: string]: string | undefined };
|
||||
}
|
||||
|
||||
export interface IProviderInstance {
|
||||
api_key: string;
|
||||
id: string;
|
||||
instance_name: string;
|
||||
provider_id: string;
|
||||
region: string;
|
||||
status: string;
|
||||
}
|
||||
export interface IAddedModel {
|
||||
model_type: string[];
|
||||
name: string;
|
||||
provider_id: string;
|
||||
provider_name: string;
|
||||
instance_id: string;
|
||||
instance_name: string;
|
||||
}
|
||||
|
||||
export interface IInstanceModel {
|
||||
max_tokens: number;
|
||||
model_type: string[];
|
||||
name: string;
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface IDefaultModel {
|
||||
enable: boolean;
|
||||
model_instance: string;
|
||||
model_name: string;
|
||||
model_provider: string;
|
||||
model_type: string;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export interface IAddLlmRequestBody {
|
||||
llm_factory: string; // Ollama
|
||||
llm_name: string;
|
||||
model_type: string;
|
||||
model_type: string | string[];
|
||||
api_base?: string; // chat|embedding|speech2text|image2text
|
||||
api_key?: string | Record<string, any>;
|
||||
max_tokens: number;
|
||||
@@ -12,3 +12,50 @@ export interface IDeleteLlmRequestBody {
|
||||
llm_factory: string; // Ollama
|
||||
llm_name?: string;
|
||||
}
|
||||
|
||||
export interface IListProvidersRequestParams {
|
||||
available?: boolean;
|
||||
}
|
||||
|
||||
export interface IAddProviderRequestBody {
|
||||
provider_name: string;
|
||||
}
|
||||
|
||||
export type IAddProviderInstanceRequestBody = IAddLlmRequestBody & {
|
||||
instance_name: string;
|
||||
};
|
||||
|
||||
export interface IDeleteProviderInstanceRequestBody {
|
||||
provider_name: string;
|
||||
instances: string[];
|
||||
}
|
||||
|
||||
export interface IShowProviderInstanceRequestParams {
|
||||
provider_name: string;
|
||||
instance_name: string;
|
||||
}
|
||||
|
||||
export interface IAddInstanceModelRequestBody {
|
||||
model_name: string;
|
||||
model_type: string[];
|
||||
max_tokens: number;
|
||||
extra?: Record<string, any>;
|
||||
}
|
||||
|
||||
export interface IListAllModelsRequestParams {
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface IUpdateModelStatusRequestBody {
|
||||
provider_name: string;
|
||||
instance_name: string;
|
||||
model_name: string;
|
||||
status: 'active' | 'inactive';
|
||||
}
|
||||
|
||||
export interface ISetDefaultModelRequestBody {
|
||||
model_provider: string;
|
||||
model_instance: string;
|
||||
model_type: string;
|
||||
model_name: string;
|
||||
}
|
||||
|
||||
@@ -1514,6 +1514,10 @@ Example: Virtual Hosted Style`,
|
||||
addLlmTitle: 'Add LLM',
|
||||
editLlmTitle: 'Edit {{name}} model',
|
||||
editModel: 'Edit model',
|
||||
instanceName: 'Instance name',
|
||||
instanceNameMessage: 'Please input the instance name!',
|
||||
instanceNameTip:
|
||||
'A unique name to identify this provider instance under the same factory.',
|
||||
modelName: 'Model name',
|
||||
modelID: 'Model ID',
|
||||
modelUid: 'Model UID',
|
||||
|
||||
@@ -1099,7 +1099,8 @@ NER:使用 spaCy NER 和基于规则的关键词提取来抽取实体和关系
|
||||
gmailTokenTip:
|
||||
'请上传由 Google Console 生成的 OAuth JSON。如果仅包含 client credentials,请通过浏览器授权一次以获取长期有效的刷新 Token。',
|
||||
dropboxDescription: '连接 Dropbox,同步指定账号下的文件与文件夹。',
|
||||
teamsDescription: '通过 Microsoft Graph 连接 Microsoft Teams,同步频道帖子与回复。',
|
||||
teamsDescription:
|
||||
'通过 Microsoft Graph 连接 Microsoft Teams,同步频道帖子与回复。',
|
||||
teamsTenantIdTip:
|
||||
'Azure AD 租户 ID。需要具备 Team.ReadBasic.All 与 ChannelMessage.Read.All 应用权限(管理员同意)的应用。',
|
||||
slackDescription: '连接你的 Slack 工作区,同步频道消息与讨论串。',
|
||||
@@ -1107,7 +1108,8 @@ NER:使用 spaCy NER 和基于规则的关键词提取来抽取实体和关系
|
||||
'Slack 机器人用户 OAuth Token(以 xoxb- 开头)。应用需具备 channels:read、channels:history 和 users:read 权限。',
|
||||
slackChannelsTip:
|
||||
'可选:需要同步的频道名称(例如 general)。留空则同步所有可访问的频道。',
|
||||
sharepointDescription: '通过 Microsoft Graph 连接 SharePoint 站点,同步其文档库。',
|
||||
sharepointDescription:
|
||||
'通过 Microsoft Graph 连接 SharePoint 站点,同步其文档库。',
|
||||
sharepointSiteUrlTip:
|
||||
'要索引的 SharePoint 站点完整 URL,例如 https://contoso.sharepoint.com/sites/MySite。需要具备 Sites.Read.All 与 Files.Read.All 应用权限(管理员同意)的 Azure AD 应用。',
|
||||
boxDescription: '连接你的 Box 云盘以同步文件和文件夹。',
|
||||
@@ -1238,6 +1240,9 @@ NER:使用 spaCy NER 和基于规则的关键词提取来抽取实体和关系
|
||||
addLlmTitle: '添加 LLM',
|
||||
editLlmTitle: '编辑 {{name}} 模型',
|
||||
editModel: '编辑模型',
|
||||
instanceName: '实例名称',
|
||||
instanceNameMessage: '请输入实例名称!',
|
||||
instanceNameTip: '用于在同一厂商下唯一标识该实例的名称。',
|
||||
modelName: '模型名称',
|
||||
modelID: '模型ID',
|
||||
modelUid: '模型UID',
|
||||
|
||||
@@ -17,8 +17,9 @@ import {
|
||||
SelectTrigger,
|
||||
SelectValue,
|
||||
} from '@/components/ui/select';
|
||||
import { useSelectFlatLlmList } from '@/hooks/use-llm-request';
|
||||
import { useFetchAllAddedModels } from '@/hooks/use-llm-request';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { parseModelValue } from '@/utils/llm-util';
|
||||
import { PropsWithChildren, useMemo } from 'react';
|
||||
|
||||
export function CardWithForm() {
|
||||
@@ -80,12 +81,23 @@ export function LabelCard({ children, className, ...props }: LabelCardProps) {
|
||||
}
|
||||
|
||||
export function LLMLabelCard({ llmId }: { llmId?: string }) {
|
||||
const flatLlmList = useSelectFlatLlmList();
|
||||
const { data: allAddedModels } = useFetchAllAddedModels();
|
||||
|
||||
const isValidLlm = useMemo(() => {
|
||||
if (!llmId) return false;
|
||||
return flatLlmList.some((llm) => llm.uuid === llmId);
|
||||
}, [flatLlmList, llmId]);
|
||||
|
||||
const parsed = parseModelValue(llmId);
|
||||
if (parsed) {
|
||||
return allAddedModels.some(
|
||||
(m) =>
|
||||
m.name === parsed.model_name &&
|
||||
m.instance_name === parsed.model_instance &&
|
||||
m.provider_name === parsed.model_provider,
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}, [allAddedModels, llmId]);
|
||||
|
||||
return (
|
||||
<LabelCard
|
||||
|
||||
@@ -19,6 +19,7 @@ import { Input, NumberInput } from '@/components/ui/input';
|
||||
import { Label } from '@/components/ui/label';
|
||||
import { Separator } from '@/components/ui/separator';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useFindLlmByUuid } from '@/hooks/use-llm-request';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { get } from 'lodash';
|
||||
@@ -158,7 +159,9 @@ function AgentForm({ node }: INextOperatorForm) {
|
||||
<FormWrapper>
|
||||
{isSubAgent && <DescriptionField></DescriptionField>}
|
||||
<LargeModelFormField showSpeech2TextModel></LargeModelFormField>
|
||||
{findLlmByUuid(llmId)?.tags?.includes('IMAGE2TEXT') && (
|
||||
{findLlmByUuid(llmId)?.model_type?.includes(
|
||||
LlmModelType.Image2text,
|
||||
) && (
|
||||
<QueryVariable
|
||||
name="visual_files_var"
|
||||
label="Visual Input File"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useFetchModelId } from '@/hooks/logic-hooks';
|
||||
import { useFetchDefaultModelDictionary } from '@/hooks/use-llm-request';
|
||||
import { RAGFlowNodeType } from '@/interfaces/database/agent';
|
||||
import { get, isEmpty, omit } from 'lodash';
|
||||
import { useMemo } from 'react';
|
||||
@@ -13,15 +13,15 @@ function omitToolsAndMcp(values: Record<string, any>) {
|
||||
}
|
||||
|
||||
export function useValues(node?: RAGFlowNodeType) {
|
||||
const llmId = useFetchModelId();
|
||||
const defaultModelDictionary = useFetchDefaultModelDictionary();
|
||||
|
||||
const defaultValues = useMemo(
|
||||
() => ({
|
||||
...omitToolsAndMcp(initialAgentValues),
|
||||
llm_id: llmId,
|
||||
llm_id: defaultModelDictionary.llm_id,
|
||||
prompts: '',
|
||||
}),
|
||||
[llmId],
|
||||
[defaultModelDictionary],
|
||||
);
|
||||
|
||||
const values = useMemo(() => {
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { crossLanguageOptions } from '@/components/cross-language-form-field';
|
||||
import { LayoutRecognizeFormField } from '@/components/layout-recognize-form-field';
|
||||
import {
|
||||
LLMFormField,
|
||||
LLMFormFieldProps,
|
||||
} from '@/components/llm-setting-items/llm-form-field';
|
||||
import {
|
||||
SelectWithSearch,
|
||||
SelectWithSearchFlagOptionType,
|
||||
@@ -75,19 +71,6 @@ export function ParserMethodFormField({
|
||||
);
|
||||
}
|
||||
|
||||
export function LargeModelFormField({
|
||||
prefix,
|
||||
options,
|
||||
}: CommonProps & Pick<LLMFormFieldProps, 'options'>) {
|
||||
return (
|
||||
<LLMFormField
|
||||
name={buildFieldNameWithPrefix('vlm.llm_id', prefix)}
|
||||
options={options}
|
||||
config={{ allowClear: true }}
|
||||
></LLMFormField>
|
||||
);
|
||||
}
|
||||
|
||||
export function FlattenMediaToTextFormField({ prefix }: CommonProps) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { ParseDocumentType } from '@/components/layout-recognize-form-field';
|
||||
import {
|
||||
ModelTreeSelectFormField,
|
||||
ModelTypeMap,
|
||||
} from '@/components/model-tree-select';
|
||||
import {
|
||||
SelectWithSearch,
|
||||
SelectWithSearchFlagOptionType,
|
||||
} from '@/components/originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
@@ -13,7 +15,6 @@ import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
FlattenMediaToTextFormField,
|
||||
LanguageFormField,
|
||||
LargeModelFormField,
|
||||
ParserMethodFormField,
|
||||
RemoveHeaderFooterFormField,
|
||||
RmdirFormField,
|
||||
@@ -38,9 +39,6 @@ export function PdfFormFields({ prefix }: CommonProps) {
|
||||
const form = useFormContext();
|
||||
|
||||
const parseMethodName = buildFieldNameWithPrefix('parse_method', prefix);
|
||||
const modelOptions = useComposeLlmOptionsByModelTypes([
|
||||
LlmModelType.Image2text,
|
||||
]);
|
||||
const parseMethod = useWatch({
|
||||
name: parseMethodName,
|
||||
});
|
||||
@@ -109,10 +107,12 @@ export function PdfFormFields({ prefix }: CommonProps) {
|
||||
<ParserMethodFormField prefix={prefix}></ParserMethodFormField>
|
||||
<FlattenMediaToTextFormField prefix={prefix} />
|
||||
{!flattenMediaToText && (
|
||||
<LargeModelFormField
|
||||
prefix={prefix}
|
||||
options={modelOptions}
|
||||
></LargeModelFormField>
|
||||
<ModelTreeSelectFormField
|
||||
name={buildFieldNameWithPrefix('vlm.llm_id', prefix)}
|
||||
label={t('chat.model')}
|
||||
modelTypes={ModelTypeMap.img2txt_id}
|
||||
allowClear
|
||||
/>
|
||||
)}
|
||||
{languageShown && <LanguageFormField prefix={prefix}></LanguageFormField>}
|
||||
{tcadpOptionsShown && (
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
import { ParseDocumentType } from '@/components/layout-recognize-form-field';
|
||||
import {
|
||||
ModelTreeSelectFormField,
|
||||
ModelTypeMap,
|
||||
} from '@/components/model-tree-select';
|
||||
import {
|
||||
SelectWithSearch,
|
||||
SelectWithSearchFlagOptionType,
|
||||
} from '@/components/originui/select-with-search';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
FlattenMediaToTextFormField,
|
||||
LargeModelFormField,
|
||||
ParserMethodFormField,
|
||||
} from './common-form-fields';
|
||||
import { CommonProps } from './interface';
|
||||
@@ -31,9 +32,6 @@ const markdownImageResponseTypeOptions: SelectWithSearchFlagOptionType[] = [
|
||||
export function SpreadsheetFormFields({ prefix }: CommonProps) {
|
||||
const { t } = useTranslation();
|
||||
const form = useFormContext();
|
||||
const modelOptions = useComposeLlmOptionsByModelTypes([
|
||||
LlmModelType.Image2text,
|
||||
]);
|
||||
|
||||
const parseMethodName = buildFieldNameWithPrefix('parse_method', prefix);
|
||||
|
||||
@@ -103,10 +101,12 @@ export function SpreadsheetFormFields({ prefix }: CommonProps) {
|
||||
></ParserMethodFormField>
|
||||
<FlattenMediaToTextFormField prefix={prefix} />
|
||||
{!flattenMediaToText && (
|
||||
<LargeModelFormField
|
||||
prefix={prefix}
|
||||
options={modelOptions}
|
||||
></LargeModelFormField>
|
||||
<ModelTreeSelectFormField
|
||||
name={buildFieldNameWithPrefix('vlm.llm_id', prefix)}
|
||||
label={t('chat.model')}
|
||||
modelTypes={ModelTypeMap.img2txt_id}
|
||||
allowClear
|
||||
/>
|
||||
)}
|
||||
{tcadpOptionsShown && (
|
||||
<>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
|
||||
import {
|
||||
ModelTreeSelectFormField,
|
||||
ModelTypeMap,
|
||||
} from '@/components/model-tree-select';
|
||||
import { useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
FlattenMediaToTextFormField,
|
||||
LargeModelFormField,
|
||||
RemoveHeaderFooterFormField,
|
||||
RmdirFormField,
|
||||
} from './common-form-fields';
|
||||
@@ -11,9 +13,7 @@ import { CommonProps } from './interface';
|
||||
import { buildFieldNameWithPrefix } from './utils';
|
||||
|
||||
export function TextMarkdownFormFields({ prefix }: CommonProps) {
|
||||
const modelOptions = useComposeLlmOptionsByModelTypes([
|
||||
LlmModelType.Image2text,
|
||||
]);
|
||||
const { t } = useTranslation();
|
||||
const flattenMediaToText = useWatch({
|
||||
name: buildFieldNameWithPrefix('flatten_media_to_text', prefix),
|
||||
});
|
||||
@@ -23,10 +23,12 @@ export function TextMarkdownFormFields({ prefix }: CommonProps) {
|
||||
<RmdirFormField prefix={prefix} />
|
||||
<FlattenMediaToTextFormField prefix={prefix} />
|
||||
{!flattenMediaToText && (
|
||||
<LargeModelFormField
|
||||
prefix={prefix}
|
||||
options={modelOptions}
|
||||
></LargeModelFormField>
|
||||
<ModelTreeSelectFormField
|
||||
name={buildFieldNameWithPrefix('vlm.llm_id', prefix)}
|
||||
label={t('chat.model')}
|
||||
modelTypes={ModelTypeMap.img2txt_id}
|
||||
allowClear
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,38 +1,39 @@
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
|
||||
import {
|
||||
LargeModelFormField,
|
||||
OutputFormatFormFieldProps,
|
||||
} from './common-form-fields';
|
||||
ModelTreeSelectFormField,
|
||||
ModelTypeMap,
|
||||
} from '@/components/model-tree-select';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { OutputFormatFormFieldProps } from './common-form-fields';
|
||||
import { buildFieldNameWithPrefix } from './utils';
|
||||
|
||||
export function AudioFormFields({ prefix }: OutputFormatFormFieldProps) {
|
||||
const modelOptions = useComposeLlmOptionsByModelTypes([
|
||||
LlmModelType.Speech2text,
|
||||
]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Multimodal Model */}
|
||||
<LargeModelFormField
|
||||
prefix={prefix}
|
||||
options={modelOptions}
|
||||
></LargeModelFormField>
|
||||
<ModelTreeSelectFormField
|
||||
name={buildFieldNameWithPrefix('vlm.llm_id', prefix)}
|
||||
label={t('chat.model')}
|
||||
modelTypes={ModelTypeMap.asr_id}
|
||||
allowClear
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function VideoFormFields({ prefix }: OutputFormatFormFieldProps) {
|
||||
const modelOptions = useComposeLlmOptionsByModelTypes([
|
||||
LlmModelType.Image2text,
|
||||
]);
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* Multimodal Model */}
|
||||
<LargeModelFormField
|
||||
prefix={prefix}
|
||||
options={modelOptions}
|
||||
></LargeModelFormField>
|
||||
<ModelTreeSelectFormField
|
||||
name={buildFieldNameWithPrefix('vlm.llm_id', prefix)}
|
||||
label={t('chat.model')}
|
||||
modelTypes={ModelTypeMap.img2txt_id}
|
||||
allowClear
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
|
||||
import {
|
||||
ModelTreeSelectFormField,
|
||||
ModelTypeMap,
|
||||
} from '@/components/model-tree-select';
|
||||
import { useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import {
|
||||
FlattenMediaToTextFormField,
|
||||
LargeModelFormField,
|
||||
OutputFormatFormFieldProps,
|
||||
RemoveHeaderFooterFormField,
|
||||
RmdirFormField,
|
||||
@@ -11,9 +13,7 @@ import {
|
||||
import { buildFieldNameWithPrefix } from './utils';
|
||||
|
||||
export function WordFormFields({ prefix }: OutputFormatFormFieldProps) {
|
||||
const modelOptions = useComposeLlmOptionsByModelTypes([
|
||||
LlmModelType.Image2text,
|
||||
]);
|
||||
const { t } = useTranslation();
|
||||
const flattenMediaToText = useWatch({
|
||||
name: buildFieldNameWithPrefix('flatten_media_to_text', prefix),
|
||||
});
|
||||
@@ -24,10 +24,12 @@ export function WordFormFields({ prefix }: OutputFormatFormFieldProps) {
|
||||
<RemoveHeaderFooterFormField prefix={prefix} />
|
||||
<FlattenMediaToTextFormField prefix={prefix} />
|
||||
{!flattenMediaToText && (
|
||||
<LargeModelFormField
|
||||
prefix={prefix}
|
||||
options={modelOptions}
|
||||
></LargeModelFormField>
|
||||
<ModelTreeSelectFormField
|
||||
name={buildFieldNameWithPrefix('vlm.llm_id', prefix)}
|
||||
label={t('chat.model')}
|
||||
modelTypes={ModelTypeMap.img2txt_id}
|
||||
allowClear
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { NextLLMSelect } from '@/components/llm-select/next';
|
||||
import { MessageHistoryWindowSizeFormField } from '@/components/message-history-window-size-item';
|
||||
import { ModelTreeSelectFormField } from '@/components/model-tree-select';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
@@ -24,20 +24,10 @@ const RewriteQuestionForm = ({ form }: INextOperatorForm) => {
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
<ModelTreeSelectFormField
|
||||
name="llm_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel tooltip={t('chat.modelTip')}>
|
||||
{t('chat.model')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<NextLLMSelect {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
label={t('chat.model')}
|
||||
tooltip={t('chat.modelTip')}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useFetchModelId } from '@/hooks/logic-hooks';
|
||||
import { useFetchDefaultModelDictionary } from '@/hooks/use-llm-request';
|
||||
import { Connection, Node, Position, ReactFlowInstance } from '@xyflow/react';
|
||||
import humanId from 'human-id';
|
||||
import { t } from 'i18next';
|
||||
@@ -123,13 +123,17 @@ function useAddGroupNode() {
|
||||
return { addGroupNode };
|
||||
}
|
||||
export const useInitializeOperatorParams = () => {
|
||||
const llmId = useFetchModelId();
|
||||
const defaultModelDictionary = useFetchDefaultModelDictionary();
|
||||
const llmId = defaultModelDictionary.llm_id;
|
||||
|
||||
const initialFormValuesMap = useMemo(() => {
|
||||
return {
|
||||
[Operator.Begin]: initialBeginValues,
|
||||
[Operator.Retrieval]: initialRetrievalValues,
|
||||
[Operator.Categorize]: { ...initialCategorizeValues, llm_id: llmId },
|
||||
[Operator.Categorize]: {
|
||||
...initialCategorizeValues,
|
||||
llm_id: llmId,
|
||||
},
|
||||
[Operator.RewriteQuestion]: {
|
||||
...initialRewriteQuestionValues,
|
||||
llm_id: llmId,
|
||||
|
||||
@@ -3,10 +3,8 @@ import {
|
||||
FormFieldType,
|
||||
RenderField,
|
||||
} from '@/components/dynamic-form';
|
||||
import {
|
||||
SelectWithSearch,
|
||||
SelectWithSearchFlagOptionType,
|
||||
} from '@/components/originui/select-with-search';
|
||||
import { ModelTreeSelect, ModelTypeMap } from '@/components/model-tree-select';
|
||||
import { SelectWithSearch } from '@/components/originui/select-with-search';
|
||||
import { SliderInputFormField } from '@/components/slider-input-form-field';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
@@ -19,9 +17,8 @@ import {
|
||||
import { Radio } from '@/components/ui/radio';
|
||||
import { Spin } from '@/components/ui/spin';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { LlmModelType, ParseType } from '@/constants/knowledge';
|
||||
import { ParseType } from '@/constants/knowledge';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { history } from '@/utils/simple-history-util';
|
||||
import { t } from 'i18next';
|
||||
@@ -51,7 +48,6 @@ import {
|
||||
useHandleKbEmbedding,
|
||||
useHasParsedDocument,
|
||||
useSelectChunkMethodList,
|
||||
useSelectEmbeddingModelOptions,
|
||||
} from '../hooks';
|
||||
interface IProps {
|
||||
line?: 1 | 2;
|
||||
@@ -117,13 +113,12 @@ export const EmbeddingSelect = ({
|
||||
}) => {
|
||||
const { t } = useTranslate('knowledgeConfiguration');
|
||||
const form = useFormContext();
|
||||
const embeddingModelOptions = useSelectEmbeddingModelOptions();
|
||||
const { handleChange } = useHandleKbEmbedding();
|
||||
|
||||
const oldValue = useMemo(() => {
|
||||
const embdStr = form.getValues(name || 'embedding_model');
|
||||
return embdStr || '';
|
||||
}, [form]);
|
||||
}, [form, name]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
return (
|
||||
<Spin
|
||||
@@ -132,14 +127,14 @@ export const EmbeddingSelect = ({
|
||||
'opacity-20': loading,
|
||||
})}
|
||||
>
|
||||
<SelectWithSearch
|
||||
<ModelTreeSelect
|
||||
modelTypes={ModelTypeMap.embd_id}
|
||||
onChange={async (value) => {
|
||||
field.onChange(value);
|
||||
if (isEdit && disabled) {
|
||||
setLoading(true);
|
||||
const res = await handleChange({
|
||||
embed_id: value,
|
||||
// callback: field.onChange,
|
||||
});
|
||||
if (res.code !== 0) {
|
||||
field.onChange(oldValue);
|
||||
@@ -149,7 +144,6 @@ export const EmbeddingSelect = ({
|
||||
}}
|
||||
disabled={disabled && !isEdit}
|
||||
value={field.value}
|
||||
options={embeddingModelOptions}
|
||||
placeholder={t('embeddingModelPlaceholder')}
|
||||
testId={testId}
|
||||
/>
|
||||
@@ -544,18 +538,14 @@ export const LLMSelect = ({
|
||||
disabled?: boolean;
|
||||
}) => {
|
||||
const { t } = useTranslate('knowledgeConfiguration');
|
||||
const modelOptions = useComposeLlmOptionsByModelTypes([
|
||||
LlmModelType.Chat,
|
||||
LlmModelType.Image2text,
|
||||
]);
|
||||
return (
|
||||
<SelectWithSearch
|
||||
onChange={async (value) => {
|
||||
<ModelTreeSelect
|
||||
modelTypes={ModelTypeMap.llm_id}
|
||||
onChange={(value) => {
|
||||
field.onChange(value);
|
||||
}}
|
||||
disabled={disabled && !isEdit}
|
||||
value={field.value}
|
||||
options={modelOptions as SelectWithSearchFlagOptionType[]}
|
||||
placeholder={t('embeddingModelPlaceholder')}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
|
||||
import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request';
|
||||
import { useSelectLlmOptionsByModelType } from '@/hooks/use-llm-request';
|
||||
import { useSelectParserList } from '@/hooks/use-user-setting-request';
|
||||
import { checkEmbedding } from '@/services/knowledge-service';
|
||||
import { useIsFetching } from '@tanstack/react-query';
|
||||
@@ -22,11 +20,6 @@ export function useSelectChunkMethodList() {
|
||||
return parserList.filter((x) => !HiddenFields.some((y) => y === x.value));
|
||||
}
|
||||
|
||||
export function useSelectEmbeddingModelOptions() {
|
||||
const allOptions = useSelectLlmOptionsByModelType();
|
||||
return allOptions[LlmModelType.Embedding];
|
||||
}
|
||||
|
||||
export function useHasParsedDocument(isEdit?: boolean) {
|
||||
const { data: knowledgeDetails } = useFetchKnowledgeBaseConfiguration({
|
||||
isEdit,
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { FormLayout } from '@/constants/form';
|
||||
import { ParseType } from '@/constants/knowledge';
|
||||
import { useFetchTenantInfo } from '@/hooks/use-user-setting-request';
|
||||
import { useFetchDefaultModelDictionary } from '@/hooks/use-llm-request';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { omit } from 'lodash';
|
||||
@@ -39,7 +39,7 @@ const ChunkMethodName = 'chunk_method';
|
||||
|
||||
export function InputForm({ onOk }: IModalProps<any>) {
|
||||
const { t } = useTranslation();
|
||||
const { data: tenantInfo } = useFetchTenantInfo();
|
||||
const defaultModelDictionary = useFetchDefaultModelDictionary();
|
||||
|
||||
const FormSchema = z
|
||||
.object({
|
||||
@@ -87,7 +87,7 @@ export function InputForm({ onOk }: IModalProps<any>) {
|
||||
name: '',
|
||||
parseType: ParseType.BuiltIn,
|
||||
[ChunkMethodName]: '',
|
||||
embedding_model: tenantInfo?.embd_id,
|
||||
embedding_model: defaultModelDictionary?.embd_id,
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useHandleFilterSubmit } from '@/components/list-filter-bar/use-handle-f
|
||||
import message from '@/components/ui/message';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useHandleSearchChange } from '@/hooks/logic-hooks';
|
||||
import { useFetchTenantInfo } from '@/hooks/use-user-setting-request';
|
||||
import { useFetchDefaultModelDictionary } from '@/hooks/use-llm-request';
|
||||
import memoryService, { updateMemoryById } from '@/services/memory-service';
|
||||
import {
|
||||
buildOwnersFilter,
|
||||
@@ -243,13 +243,13 @@ export const useRenameMemory = () => {
|
||||
const { updateMemory } = useUpdateMemory();
|
||||
const { createMemory } = useCreateMemory();
|
||||
const [loading, setLoading] = useState(false);
|
||||
const { data: tenantInfo } = useFetchTenantInfo();
|
||||
const defaultModelDictionary = useFetchDefaultModelDictionary();
|
||||
|
||||
const handleShowChatRenameModal = useCallback(
|
||||
(record?: IMemory) => {
|
||||
if (record) {
|
||||
const embd_id = record.embd_id || tenantInfo?.embd_id;
|
||||
const llm_id = record.llm_id || tenantInfo?.llm_id;
|
||||
const embd_id = record.embd_id || defaultModelDictionary?.embd_id;
|
||||
const llm_id = record.llm_id || defaultModelDictionary?.llm_id;
|
||||
setMemory({
|
||||
...record,
|
||||
embd_id,
|
||||
@@ -258,7 +258,7 @@ export const useRenameMemory = () => {
|
||||
}
|
||||
showChatRenameModal();
|
||||
},
|
||||
[showChatRenameModal, tenantInfo],
|
||||
[showChatRenameModal, defaultModelDictionary],
|
||||
);
|
||||
|
||||
const handleHideModal = useCallback(() => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { FormFieldType, RenderField } from '@/components/dynamic-form';
|
||||
import { useModelOptions } from '@/components/llm-setting-items/llm-form-field';
|
||||
import { ModelTreeSelect } from '@/components/model-tree-select';
|
||||
import { EmbeddingSelect } from '@/pages/dataset/dataset-setting/configuration/common-item';
|
||||
import { MemoryOptions, MemoryType } from '@/pages/memories/constants';
|
||||
import { TFunction } from 'i18next';
|
||||
@@ -28,7 +28,6 @@ export const defaultMemoryModelForm = {
|
||||
memory_size: 0,
|
||||
};
|
||||
export const MemoryModelForm = () => {
|
||||
const { modelOptions } = useModelOptions();
|
||||
const { t } = useTranslation();
|
||||
const { data } = useFetchMemoryMessageList();
|
||||
return (
|
||||
@@ -40,7 +39,6 @@ export const MemoryModelForm = () => {
|
||||
placeholder: t('memories.selectModel'),
|
||||
required: true,
|
||||
horizontal: true,
|
||||
// hideLabel: true,
|
||||
type: FormFieldType.Custom,
|
||||
disabled: true,
|
||||
render: (field) => (
|
||||
@@ -58,12 +56,17 @@ export const MemoryModelForm = () => {
|
||||
field={{
|
||||
name: 'llm_id',
|
||||
label: t('memories.llm'),
|
||||
placeholder: t('memories.selectModel'),
|
||||
required: true,
|
||||
horizontal: true,
|
||||
type: FormFieldType.Select,
|
||||
type: FormFieldType.Custom,
|
||||
disabled: data?.messages?.total_count > 0,
|
||||
options: modelOptions as { value: string; label: string }[],
|
||||
render: (field) => (
|
||||
<ModelTreeSelect
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
placeholder={t('memories.selectModel')}
|
||||
/>
|
||||
),
|
||||
tooltip: t('memories.llmTooltip'),
|
||||
}}
|
||||
/>
|
||||
@@ -93,7 +96,6 @@ export const MemoryModelForm = () => {
|
||||
type: FormFieldType.Number,
|
||||
horizontal: true,
|
||||
tooltip: t('memory.config.memorySizeTooltip'),
|
||||
// placeholder: t('memory.config.memorySizePlaceholder'),
|
||||
required: false,
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -157,7 +157,7 @@ const ChatCard = forwardRef(function ChatCard(
|
||||
},
|
||||
},
|
||||
});
|
||||
}, [currentDialog, dialogId, form, patchChat]);
|
||||
}, [currentDialog, dialogId, form, patchChat, findLlmByUuid]);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
|
||||
@@ -1,18 +1,26 @@
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request';
|
||||
import { useMount } from 'ahooks';
|
||||
import { ModelTypeMap } from '@/components/model-tree-select';
|
||||
import { useFetchAllAddedModels } from '@/hooks/use-llm-request';
|
||||
import { getRealModelName } from '@/utils/llm-util';
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { UseFormReturn } from 'react-hook-form';
|
||||
|
||||
export function useSetDefaultModel(form: UseFormReturn<any>) {
|
||||
const modelOptions = useComposeLlmOptionsByModelTypes([
|
||||
LlmModelType.Chat,
|
||||
LlmModelType.Image2text,
|
||||
]);
|
||||
const { data: allAddedModels } = useFetchAllAddedModels();
|
||||
const hasSet = useRef(false);
|
||||
|
||||
useMount(() => {
|
||||
const firstModel = modelOptions.at(0)?.options.at(0)?.value;
|
||||
if (firstModel) {
|
||||
form.setValue('llm_id', firstModel);
|
||||
useEffect(() => {
|
||||
if (hasSet.current || !allAddedModels.length) return;
|
||||
const chatModels = allAddedModels.filter((m) =>
|
||||
m.model_type?.some((t) => ModelTypeMap.llm_id.includes(t)),
|
||||
);
|
||||
const first = chatModels[0];
|
||||
if (first) {
|
||||
const modelName = getRealModelName(first.name);
|
||||
form.setValue(
|
||||
'llm_id',
|
||||
`${modelName}@${first.instance_name}@${first.provider_name}`,
|
||||
);
|
||||
hasSet.current = true;
|
||||
}
|
||||
});
|
||||
}, [allAddedModels, form]);
|
||||
}
|
||||
|
||||
65
web/src/pages/next-chats/hooks/use-create-chat.ts
Normal file
65
web/src/pages/next-chats/hooks/use-create-chat.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useCreateChat } from '@/hooks/use-chat-request';
|
||||
import { useFetchDefaultModelDictionary } from '@/hooks/use-llm-request';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const useCreateChatDialog = () => {
|
||||
const {
|
||||
visible: createChatVisible,
|
||||
hideModal: hideCreateChatModal,
|
||||
showModal: showCreateChatModal,
|
||||
} = useSetModalState();
|
||||
const { createChat, loading: createLoading } = useCreateChat();
|
||||
const { t } = useTranslation();
|
||||
const defaultModelDictionary =
|
||||
useFetchDefaultModelDictionary(createChatVisible);
|
||||
|
||||
const InitialData = useMemo(
|
||||
() => ({
|
||||
name: '',
|
||||
icon: '',
|
||||
language: 'English',
|
||||
description: '',
|
||||
dataset_ids: [],
|
||||
prompt_config: {
|
||||
empty_response: '',
|
||||
prologue: t('chat.setAnOpenerInitial'),
|
||||
quote: true,
|
||||
keyword: false,
|
||||
tts: false,
|
||||
system: t('chat.systemInitialValue'),
|
||||
refine_multiturn: false,
|
||||
use_kg: false,
|
||||
reasoning: false,
|
||||
parameters: [{ key: 'knowledge', optional: false }],
|
||||
toc_enhance: false,
|
||||
},
|
||||
llm_id: defaultModelDictionary?.llm_id,
|
||||
llm_setting: {},
|
||||
similarity_threshold: 0.2,
|
||||
vector_similarity_weight: 0.3,
|
||||
top_n: 8,
|
||||
top_k: 1024,
|
||||
}),
|
||||
[t, defaultModelDictionary?.llm_id],
|
||||
);
|
||||
|
||||
const onCreateChatOk = useCallback(
|
||||
async (name: string) => {
|
||||
const ret = await createChat({ ...InitialData, name });
|
||||
if (ret === 0) {
|
||||
hideCreateChatModal();
|
||||
}
|
||||
},
|
||||
[InitialData, createChat, hideCreateChatModal],
|
||||
);
|
||||
|
||||
return {
|
||||
createChatLoading: createLoading,
|
||||
onCreateChatOk,
|
||||
createChatVisible,
|
||||
hideCreateChatModal,
|
||||
showCreateChatModal,
|
||||
};
|
||||
};
|
||||
@@ -1,11 +1,7 @@
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useCreateChat, usePatchChat } from '@/hooks/use-chat-request';
|
||||
import { useFindLlmByUuid } from '@/hooks/use-llm-request';
|
||||
import { useFetchTenantInfo } from '@/hooks/use-user-setting-request';
|
||||
import { usePatchChat } from '@/hooks/use-chat-request';
|
||||
import { IDialog } from '@/interfaces/database/chat';
|
||||
import { isEmpty } from 'lodash';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
export const useRenameChat = () => {
|
||||
const [chat, setChat] = useState<IDialog>({} as IDialog);
|
||||
@@ -14,70 +10,25 @@ export const useRenameChat = () => {
|
||||
hideModal: hideChatRenameModal,
|
||||
showModal: showChatRenameModal,
|
||||
} = useSetModalState();
|
||||
const { createChat, loading: createLoading } = useCreateChat();
|
||||
const { patchChat, loading: patchLoading } = usePatchChat();
|
||||
const { t } = useTranslation();
|
||||
const tenantInfo = useFetchTenantInfo();
|
||||
const findLlmByUuid = useFindLlmByUuid();
|
||||
|
||||
const InitialData = useMemo(
|
||||
() => ({
|
||||
name: '',
|
||||
icon: '',
|
||||
language: 'English',
|
||||
description: '',
|
||||
dataset_ids: [],
|
||||
prompt_config: {
|
||||
empty_response: '',
|
||||
prologue: t('chat.setAnOpenerInitial'),
|
||||
quote: true,
|
||||
keyword: false,
|
||||
tts: false,
|
||||
system: t('chat.systemInitialValue'),
|
||||
refine_multiturn: false,
|
||||
use_kg: false,
|
||||
reasoning: false,
|
||||
parameters: [{ key: 'knowledge', optional: false }],
|
||||
toc_enhance: false,
|
||||
},
|
||||
llm_id: tenantInfo.data.llm_id,
|
||||
llm_setting: {
|
||||
model_type: findLlmByUuid(tenantInfo.data.llm_id)?.model_type || 'chat',
|
||||
},
|
||||
similarity_threshold: 0.2,
|
||||
vector_similarity_weight: 0.3,
|
||||
top_n: 8,
|
||||
top_k: 1024,
|
||||
}),
|
||||
[t, tenantInfo.data.llm_id, findLlmByUuid],
|
||||
);
|
||||
|
||||
const onChatRenameOk = useCallback(
|
||||
async (name: string) => {
|
||||
let ret: number | undefined;
|
||||
if (isEmpty(chat)) {
|
||||
ret = await createChat({ ...InitialData, name });
|
||||
} else {
|
||||
ret = await patchChat({
|
||||
chatId: chat.id,
|
||||
params: { name },
|
||||
});
|
||||
}
|
||||
const ret = await patchChat({
|
||||
chatId: chat.id,
|
||||
params: { name },
|
||||
});
|
||||
|
||||
if (ret === 0) {
|
||||
hideChatRenameModal();
|
||||
}
|
||||
},
|
||||
[chat, InitialData, createChat, patchChat, hideChatRenameModal],
|
||||
[chat.id, patchChat, hideChatRenameModal],
|
||||
);
|
||||
|
||||
const handleShowChatRenameModal = useCallback(
|
||||
(record?: IDialog) => {
|
||||
if (record) {
|
||||
setChat(record);
|
||||
} else {
|
||||
setChat({} as IDialog);
|
||||
}
|
||||
(record: IDialog) => {
|
||||
setChat(record);
|
||||
showChatRenameModal();
|
||||
},
|
||||
[showChatRenameModal],
|
||||
@@ -89,7 +40,7 @@ export const useRenameChat = () => {
|
||||
}, [hideChatRenameModal]);
|
||||
|
||||
return {
|
||||
chatRenameLoading: createLoading || patchLoading,
|
||||
chatRenameLoading: patchLoading,
|
||||
initialChatName: chat?.name,
|
||||
onChatRenameOk,
|
||||
chatRenameVisible,
|
||||
|
||||
@@ -8,10 +8,11 @@ import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
|
||||
import { useFetchChatList } from '@/hooks/use-chat-request';
|
||||
import { pick } from 'lodash';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { useCallback, useEffect } from 'react';
|
||||
import { useCallback, useEffect, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSearchParams } from 'react-router';
|
||||
import { ChatCard } from './chat-card';
|
||||
import { useCreateChatDialog } from './hooks/use-create-chat';
|
||||
import { useRenameChat } from './hooks/use-rename-chat';
|
||||
|
||||
export default function ChatList() {
|
||||
@@ -26,6 +27,13 @@ export default function ChatList() {
|
||||
onChatRenameOk,
|
||||
chatRenameLoading,
|
||||
} = useRenameChat();
|
||||
const {
|
||||
createChatVisible,
|
||||
showCreateChatModal,
|
||||
hideCreateChatModal,
|
||||
onCreateChatOk,
|
||||
createChatLoading,
|
||||
} = useCreateChatDialog();
|
||||
|
||||
const handlePageChange = useCallback(
|
||||
(page: number, pageSize?: number) => {
|
||||
@@ -35,8 +43,8 @@ export default function ChatList() {
|
||||
);
|
||||
|
||||
const handleShowCreateModal = useCallback(() => {
|
||||
showChatRenameModal();
|
||||
}, [showChatRenameModal]);
|
||||
showCreateChatModal();
|
||||
}, [showCreateChatModal]);
|
||||
|
||||
const [searchParams, setSearchParams] = useSearchParams();
|
||||
const isCreate = searchParams.get('isCreate') === 'true';
|
||||
@@ -48,6 +56,39 @@ export default function ChatList() {
|
||||
}
|
||||
}, [isCreate, handleShowCreateModal, searchParams, setSearchParams]);
|
||||
|
||||
const renameDialogProps = useMemo(() => {
|
||||
if (chatRenameVisible) {
|
||||
return {
|
||||
hideModal: hideChatRenameModal,
|
||||
onOk: onChatRenameOk,
|
||||
initialName: initialChatName,
|
||||
loading: chatRenameLoading,
|
||||
title: initialChatName,
|
||||
};
|
||||
}
|
||||
if (createChatVisible) {
|
||||
return {
|
||||
hideModal: hideCreateChatModal,
|
||||
onOk: onCreateChatOk,
|
||||
initialName: '',
|
||||
loading: createChatLoading,
|
||||
title: t('chat.createChat'),
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}, [
|
||||
chatRenameVisible,
|
||||
createChatVisible,
|
||||
hideChatRenameModal,
|
||||
onChatRenameOk,
|
||||
initialChatName,
|
||||
chatRenameLoading,
|
||||
hideCreateChatModal,
|
||||
onCreateChatOk,
|
||||
createChatLoading,
|
||||
t,
|
||||
]);
|
||||
|
||||
return (
|
||||
<>
|
||||
{data.chats?.length || searchString ? (
|
||||
@@ -115,14 +156,8 @@ export default function ChatList() {
|
||||
</article>
|
||||
)}
|
||||
|
||||
{chatRenameVisible && (
|
||||
<RenameDialog
|
||||
hideModal={hideChatRenameModal}
|
||||
onOk={onChatRenameOk}
|
||||
initialName={initialChatName}
|
||||
loading={chatRenameLoading}
|
||||
title={initialChatName || t('chat.createChat')}
|
||||
></RenameDialog>
|
||||
{renameDialogProps && (
|
||||
<RenameDialog {...renameDialogProps}></RenameDialog>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
MetadataFilter,
|
||||
MetadataFilterSchema,
|
||||
} from '@/components/metadata-filter';
|
||||
import { ModelTreeSelect } from '@/components/model-tree-select';
|
||||
import { SimilaritySliderFormField } from '@/components/similarity-slider';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { SingleFormSlider } from '@/components/ui/dual-range-slider';
|
||||
@@ -23,14 +24,9 @@ import {
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { MultiSelect } from '@/components/ui/multi-select';
|
||||
import { RAGFlowSelect } from '@/components/ui/select';
|
||||
import { Spin } from '@/components/ui/spin';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { useFetchKnowledgeMetadataKeys } from '@/hooks/use-knowledge-request';
|
||||
import {
|
||||
useComposeLlmOptionsByModelTypes,
|
||||
useSelectLlmOptionsByModelType,
|
||||
} from '@/hooks/use-llm-request';
|
||||
import { useFetchTenantInfo } from '@/hooks/use-user-setting-request';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
@@ -39,7 +35,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { z } from 'zod';
|
||||
import { LlmModelType } from '../dataset/dataset/constant';
|
||||
import {
|
||||
ISearchAppDetailProps,
|
||||
IUpdateSearchProps,
|
||||
@@ -188,16 +183,6 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
}
|
||||
}, [open]);
|
||||
|
||||
const allOptions = useSelectLlmOptionsByModelType();
|
||||
const rerankModelOptions = useMemo(() => {
|
||||
return allOptions[LlmModelType.Rerank];
|
||||
}, [allOptions]);
|
||||
|
||||
const aiSummeryModelOptions = useComposeLlmOptionsByModelTypes([
|
||||
LlmModelType.Chat,
|
||||
LlmModelType.Image2text,
|
||||
]);
|
||||
|
||||
const rerankModelDisabled = useWatch({
|
||||
control: formMethods.control,
|
||||
name: 'search_config.use_rerank',
|
||||
@@ -344,15 +329,9 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
>
|
||||
<Form {...formMethods}>
|
||||
<form
|
||||
onSubmit={formMethods.handleSubmit(
|
||||
(data) => {
|
||||
console.log('Form submitted with data:', data);
|
||||
onSubmit(data as unknown as IUpdateSearchProps);
|
||||
},
|
||||
(errors) => {
|
||||
console.log('Validation errors:', errors);
|
||||
},
|
||||
)}
|
||||
onSubmit={formMethods.handleSubmit((data) => {
|
||||
onSubmit(data as unknown as IUpdateSearchProps);
|
||||
})}
|
||||
className="space-y-6"
|
||||
>
|
||||
<AvatarNameDescription avatarField="avatar" />
|
||||
@@ -452,11 +431,9 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
{t('chat.model')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<RAGFlowSelect
|
||||
<ModelTreeSelect
|
||||
modelTypes={['rerank']}
|
||||
{...field}
|
||||
options={rerankModelOptions}
|
||||
triggerClassName={'bg-bg-input'}
|
||||
// disabled={disabled}
|
||||
placeholder={t('chat.model')}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -524,7 +501,6 @@ const SearchSetting: React.FC<SearchSettingProps> = ({
|
||||
// ></LlmSettingFieldItems>
|
||||
<LlmSettingFieldItems
|
||||
prefix="search_config.llm_setting"
|
||||
options={aiSummeryModelOptions}
|
||||
showFields={[
|
||||
'temperature',
|
||||
'top_p',
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
import {
|
||||
SelectWithSearch,
|
||||
SelectWithSearchFlagOptionType,
|
||||
} from '@/components/originui/select-with-search';
|
||||
import { ModelTreeSelect } from '@/components/model-tree-select';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
@@ -17,10 +14,8 @@ import { Label } from '@/components/ui/label';
|
||||
import message from '@/components/ui/message';
|
||||
import { Slider } from '@/components/ui/slider';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { useSelectLlmOptionsByModelType } from '@/hooks/use-llm-request';
|
||||
import { SkillSearchConfig } from '@/services/skill-space-service';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import type {
|
||||
@@ -73,14 +68,6 @@ export const SearchConfigModal: React.FC<SearchConfigModalProps> = ({
|
||||
const [saving, setSaving] = useState(false);
|
||||
const [reindexing, setReindexing] = useState(false);
|
||||
|
||||
// Get embedding model options from user's configured LLMs
|
||||
const llmOptions = useSelectLlmOptionsByModelType();
|
||||
const embeddingModelOptions = useMemo(() => {
|
||||
return llmOptions[
|
||||
LlmModelType.Embedding
|
||||
] as SelectWithSearchFlagOptionType[];
|
||||
}, [llmOptions]);
|
||||
|
||||
useEffect(() => {
|
||||
if (open) {
|
||||
if (config) {
|
||||
@@ -166,12 +153,12 @@ export const SearchConfigModal: React.FC<SearchConfigModalProps> = ({
|
||||
{/* Embedding Model */}
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="embd_id">{t('skillSearch.embeddingModel')}</Label>
|
||||
<SelectWithSearch
|
||||
<ModelTreeSelect
|
||||
modelTypes={['embedding']}
|
||||
value={formData.embd_id}
|
||||
onChange={(value) =>
|
||||
setValue('embd_id', value, { shouldDirty: true })
|
||||
}
|
||||
options={embeddingModelOptions}
|
||||
placeholder={t('skillSearch.embeddingModelPlaceholder')}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -1605,8 +1605,8 @@ export const DataSourceFormDefaultValues = {
|
||||
tenant_id: '',
|
||||
client_id: '',
|
||||
client_secret: '',
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
[DataSourceKey.SLACK]: {
|
||||
name: '',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
export interface ApiKeyPostBody {
|
||||
instance_name: string;
|
||||
api_key: string;
|
||||
base_url: string;
|
||||
group_id?: string;
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
// src/components/ModelProviderCard.tsx
|
||||
import {
|
||||
ConfirmDeleteDialog,
|
||||
ConfirmDeleteDialogNode,
|
||||
} from '@/components/confirm-delete-dialog';
|
||||
import { LlmIcon } from '@/components/svg-icon';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
|
||||
import { LlmItem } from '@/hooks/use-llm-request';
|
||||
import { getRealModelName } from '@/utils/llm-util';
|
||||
import { EditOutlined, SettingOutlined } from '@ant-design/icons';
|
||||
import { ChevronsDown, ChevronsUp, Trash2 } from 'lucide-react';
|
||||
import { FC } from 'react';
|
||||
import { isLocalLlmFactory } from '../../utils';
|
||||
import {
|
||||
useHandleDeleteFactory,
|
||||
useHandleDeleteLlm,
|
||||
useHandleEnableLlm,
|
||||
} from '../hooks';
|
||||
import { mapModelKey } from './un-add-model';
|
||||
|
||||
interface IModelCardProps {
|
||||
item: LlmItem;
|
||||
clickApiKey: (llmFactory: string) => void;
|
||||
handleEditModel: (model: any, factory: LlmItem) => void;
|
||||
}
|
||||
|
||||
type TagType =
|
||||
| 'LLM'
|
||||
| 'TEXT EMBEDDING'
|
||||
| 'TEXT RE-RANK'
|
||||
| 'TTS'
|
||||
| 'SPEECH2TEXT'
|
||||
| 'IMAGE2TEXT'
|
||||
| 'MODERATION';
|
||||
|
||||
const sortTags = (tags: string) => {
|
||||
const orderMap: Record<TagType, number> = {
|
||||
LLM: 1,
|
||||
'TEXT EMBEDDING': 2,
|
||||
'TEXT RE-RANK': 3,
|
||||
TTS: 4,
|
||||
SPEECH2TEXT: 5,
|
||||
IMAGE2TEXT: 6,
|
||||
MODERATION: 7,
|
||||
};
|
||||
|
||||
return tags
|
||||
.split(',')
|
||||
.map((tag) => tag.trim())
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(orderMap[a as TagType] || 999) - (orderMap[b as TagType] || 999),
|
||||
);
|
||||
};
|
||||
|
||||
export const ModelProviderCard: FC<IModelCardProps> = ({
|
||||
item,
|
||||
clickApiKey,
|
||||
handleEditModel,
|
||||
}) => {
|
||||
const { visible, switchVisible } = useSetModalState();
|
||||
const { t } = useTranslate('setting');
|
||||
const { handleEnableLlm } = useHandleEnableLlm(item.name);
|
||||
const { deleteFactory } = useHandleDeleteFactory(item.name);
|
||||
const { handleDeleteLlm } = useHandleDeleteLlm(item.name);
|
||||
|
||||
const handleApiKeyClick = () => {
|
||||
clickApiKey(item.name);
|
||||
};
|
||||
|
||||
const handleShowMoreClick = () => {
|
||||
switchVisible();
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`w-full rounded-lg border border-border-button`}
|
||||
data-testid="added-model-card"
|
||||
data-provider={item.name}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="flex h-16 items-center justify-between p-4 cursor-pointer transition-colors text-text-secondary">
|
||||
<div className="flex items-center space-x-3">
|
||||
<LlmIcon name={item.name} width={32} />
|
||||
<div>
|
||||
<div className="font-medium text-xl text-text-primary">
|
||||
{item.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center space-x-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleApiKeyClick();
|
||||
}}
|
||||
>
|
||||
<SettingOutlined />
|
||||
{isLocalLlmFactory(item.name) ? t('addTheModel') : 'API-Key'}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleShowMoreClick();
|
||||
}}
|
||||
>
|
||||
<span>{visible ? t('hideModels') : t('showMoreModels')}</span>
|
||||
{!visible ? <ChevronsDown /> : <ChevronsUp />}
|
||||
</Button>
|
||||
|
||||
<ConfirmDeleteDialog
|
||||
onOk={() => deleteFactory({ llm_factory: item.name })}
|
||||
title={t('deleteModel')}
|
||||
content={{
|
||||
node: (
|
||||
<ConfirmDeleteDialogNode>
|
||||
<div className="flex items-center gap-2 border-0.5 text-text-secondary border-border-button rounded-lg px-3 py-4">
|
||||
<LlmIcon name={item.name} />
|
||||
{item.name}
|
||||
</div>
|
||||
</ConfirmDeleteDialogNode>
|
||||
),
|
||||
}}
|
||||
>
|
||||
<Button
|
||||
size="icon"
|
||||
variant="danger-hover"
|
||||
// onClick={(e) => {
|
||||
// e.stopPropagation();
|
||||
// handleDeleteFactory(item);
|
||||
// }}
|
||||
>
|
||||
<Trash2 />
|
||||
</Button>
|
||||
</ConfirmDeleteDialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
{visible && (
|
||||
<div className="">
|
||||
<div className="px-4 flex flex-wrap gap-1 mt-1">
|
||||
{sortTags(item.tags).map((tag, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-2 py-1 text-xs bg-bg-card text-text-secondary rounded-md"
|
||||
>
|
||||
{mapModelKey[tag.trim() as keyof typeof mapModelKey] ||
|
||||
tag.trim()}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="m-4 bg-bg-card rounded-lg max-h-96 overflow-auto scrollbar-auto">
|
||||
<ul>
|
||||
{item.llm.map((model) => (
|
||||
<li
|
||||
key={model.name}
|
||||
className="flex items-center border-b-[0.5px] border-border-button justify-between p-3 hover:bg-bg-card transition-colors"
|
||||
>
|
||||
<div className="flex items-center space-x-3">
|
||||
<span className="font-medium">
|
||||
{getRealModelName(model.name)}
|
||||
</span>
|
||||
<span className="px-2 py-1 text-xs bg-bg-card text-text-secondary rounded-md">
|
||||
{model.type}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-3">
|
||||
{isLocalLlmFactory(item.name) && (
|
||||
<Button
|
||||
variant="secondary"
|
||||
size="icon-sm"
|
||||
onClick={() => handleEditModel(model, item)}
|
||||
>
|
||||
<EditOutlined />
|
||||
</Button>
|
||||
)}
|
||||
|
||||
<Switch
|
||||
checked={model.status === '1'}
|
||||
onCheckedChange={(value) => {
|
||||
handleEnableLlm(model.name, value);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
size="icon-sm"
|
||||
variant="danger-hover"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
handleDeleteLlm(model.name);
|
||||
}}
|
||||
>
|
||||
<Trash2 size={16} />
|
||||
</Button>
|
||||
</div>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,176 +1,136 @@
|
||||
import {
|
||||
SelectWithSearch,
|
||||
SelectWithSearchFlagOptionType,
|
||||
} from '@/components/originui/select-with-search';
|
||||
import { ModelTreeSelect, ModelTypeMap } from '@/components/model-tree-select';
|
||||
import {
|
||||
Tooltip,
|
||||
TooltipContent,
|
||||
TooltipTrigger,
|
||||
} from '@/components/ui/tooltip';
|
||||
import { LlmModelType } from '@/constants/knowledge';
|
||||
import { FieldToModelType } from '@/constants/llm';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import {
|
||||
ISystemModelSettingSavingParams,
|
||||
useComposeLlmOptionsByModelTypes,
|
||||
useFetchDefaultModelDictionary,
|
||||
useSetDefaultModel,
|
||||
} from '@/hooks/use-llm-request';
|
||||
import { parseModelValue } from '@/utils/llm-util';
|
||||
import { CircleQuestionMark } from 'lucide-react';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useFetchSystemModelSettingOnMount } from '../hooks';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
|
||||
interface IProps {
|
||||
loading: boolean;
|
||||
onOk: (
|
||||
payload: Omit<ISystemModelSettingSavingParams, 'tenant_id' | 'name'>,
|
||||
) => void;
|
||||
interface ModelFieldItemProps {
|
||||
id: string;
|
||||
label: string;
|
||||
value: string;
|
||||
tooltip?: string;
|
||||
isRequired?: boolean;
|
||||
onChange: (id: string, value: string) => void;
|
||||
}
|
||||
|
||||
const SystemSetting = ({ onOk, loading }: IProps) => {
|
||||
const { systemSetting: initialValues, allOptions } =
|
||||
useFetchSystemModelSettingOnMount();
|
||||
function ModelFieldItem({
|
||||
label,
|
||||
value,
|
||||
tooltip,
|
||||
id,
|
||||
isRequired,
|
||||
onChange,
|
||||
}: ModelFieldItemProps) {
|
||||
const { t } = useTranslate('setting');
|
||||
|
||||
const [formData, setFormData] = useState({
|
||||
llm_id: '',
|
||||
embd_id: '',
|
||||
img2txt_id: '',
|
||||
asr_id: '',
|
||||
rerank_id: '',
|
||||
tts_id: '',
|
||||
});
|
||||
return (
|
||||
<div className="flex gap-3">
|
||||
<label className="block text-sm font-normal text-text-secondary mb-1 w-1/4">
|
||||
{isRequired && <span className="text-state-error">*</span>}
|
||||
{label}
|
||||
{tooltip && (
|
||||
<Tooltip>
|
||||
<TooltipContent>{tooltip}</TooltipContent>
|
||||
<TooltipTrigger>
|
||||
<CircleQuestionMark
|
||||
size={12}
|
||||
className="ml-1 text-text-secondary text-xs"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
</Tooltip>
|
||||
)}
|
||||
</label>
|
||||
<div className="w-3/4">
|
||||
<ModelTreeSelect
|
||||
modelTypes={ModelTypeMap[id as keyof typeof ModelTypeMap] ?? ['chat']}
|
||||
value={value}
|
||||
onChange={(val) => onChange(id, val)}
|
||||
placeholder={t('selectModelPlaceholder')}
|
||||
showSearch
|
||||
allowClear={id !== 'llm_id'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function SystemSetting() {
|
||||
const { t } = useTranslate('setting');
|
||||
const defaultModelDictionary = useFetchDefaultModelDictionary();
|
||||
const { setDefaultModel } = useSetDefaultModel();
|
||||
|
||||
const handleFieldChange = useCallback(
|
||||
(field: string, value: string) => {
|
||||
const updatedData = { ...formData, [field]: value || '' };
|
||||
setFormData(updatedData);
|
||||
console.log('updatedData', updatedData);
|
||||
onOk(updatedData);
|
||||
async (field: string, value: string) => {
|
||||
const modelType = FieldToModelType[field];
|
||||
if (!modelType) return;
|
||||
|
||||
if (!value) {
|
||||
await setDefaultModel({
|
||||
model_provider: '',
|
||||
model_instance: '',
|
||||
model_name: '',
|
||||
model_type: modelType,
|
||||
});
|
||||
} else {
|
||||
const parsed = parseModelValue(value);
|
||||
if (!parsed) return;
|
||||
await setDefaultModel({ ...parsed, model_type: modelType });
|
||||
}
|
||||
},
|
||||
[formData, onOk],
|
||||
[setDefaultModel],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setFormData({
|
||||
llm_id: initialValues.llm_id ?? '',
|
||||
embd_id: initialValues.embd_id ?? '',
|
||||
img2txt_id: initialValues.img2txt_id ?? '',
|
||||
asr_id: initialValues.asr_id ?? '',
|
||||
rerank_id: initialValues.rerank_id ?? '',
|
||||
tts_id: initialValues.tts_id ?? '',
|
||||
});
|
||||
}, [initialValues]);
|
||||
|
||||
const modelOptions = useComposeLlmOptionsByModelTypes([
|
||||
LlmModelType.Chat,
|
||||
LlmModelType.Image2text,
|
||||
]);
|
||||
|
||||
const llmList = useMemo(() => {
|
||||
return [
|
||||
{
|
||||
id: 'llm_id',
|
||||
label: t('chatModel'),
|
||||
isRequired: true,
|
||||
value: formData.llm_id,
|
||||
options: modelOptions as SelectWithSearchFlagOptionType[],
|
||||
value: defaultModelDictionary.llm_id,
|
||||
tooltip: t('chatModelTip'),
|
||||
testId: 'default-llm-combobox',
|
||||
},
|
||||
{
|
||||
id: 'embd_id',
|
||||
label: t('embeddingModel'),
|
||||
value: formData.embd_id,
|
||||
options: allOptions[
|
||||
LlmModelType.Embedding
|
||||
] as SelectWithSearchFlagOptionType[],
|
||||
value: defaultModelDictionary.embd_id,
|
||||
tooltip: t('embeddingModelTip'),
|
||||
testId: 'default-embedding-combobox',
|
||||
},
|
||||
{
|
||||
id: 'img2txt_id',
|
||||
label: t('img2txtModel'),
|
||||
value: formData.img2txt_id,
|
||||
options: allOptions[
|
||||
LlmModelType.Image2text
|
||||
] as SelectWithSearchFlagOptionType[],
|
||||
value: defaultModelDictionary.img2txt_id,
|
||||
tooltip: t('img2txtModelTip'),
|
||||
},
|
||||
{
|
||||
id: 'asr_id',
|
||||
label: t('sequence2txtModel'),
|
||||
value: formData.asr_id,
|
||||
options: allOptions[
|
||||
LlmModelType.Speech2text
|
||||
] as SelectWithSearchFlagOptionType[],
|
||||
value: defaultModelDictionary.asr_id,
|
||||
tooltip: t('sequence2txtModelTip'),
|
||||
},
|
||||
{
|
||||
id: 'rerank_id',
|
||||
label: t('rerankModel'),
|
||||
value: formData.rerank_id,
|
||||
options: allOptions[
|
||||
LlmModelType.Rerank
|
||||
] as SelectWithSearchFlagOptionType[],
|
||||
value: defaultModelDictionary.rerank_id,
|
||||
tooltip: t('rerankModelTip'),
|
||||
},
|
||||
{
|
||||
id: 'tts_id',
|
||||
label: t('ttsModel'),
|
||||
value: formData.tts_id,
|
||||
options: allOptions[
|
||||
LlmModelType.TTS
|
||||
] as SelectWithSearchFlagOptionType[],
|
||||
value: defaultModelDictionary.tts_id,
|
||||
tooltip: t('ttsModelTip'),
|
||||
},
|
||||
];
|
||||
}, [formData, modelOptions, t, allOptions]);
|
||||
|
||||
const Items = ({
|
||||
label,
|
||||
value,
|
||||
options,
|
||||
tooltip,
|
||||
id,
|
||||
isRequired,
|
||||
testId,
|
||||
}: {
|
||||
id: string;
|
||||
label: string;
|
||||
value: string;
|
||||
options: SelectWithSearchFlagOptionType[];
|
||||
tooltip?: string;
|
||||
isRequired?: boolean;
|
||||
testId?: string;
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex gap-3">
|
||||
<label className="block text-sm font-normal text-text-secondary mb-1 w-1/4">
|
||||
{isRequired && <span className="text-red-500">*</span>}
|
||||
{label}
|
||||
{tooltip && (
|
||||
<Tooltip>
|
||||
<TooltipContent>{tooltip}</TooltipContent>
|
||||
<TooltipTrigger>
|
||||
<CircleQuestionMark
|
||||
size={12}
|
||||
className="ml-1 text-text-secondary text-xs"
|
||||
/>
|
||||
</TooltipTrigger>
|
||||
</Tooltip>
|
||||
)}
|
||||
</label>
|
||||
<SelectWithSearch
|
||||
triggerClassName="w-3/4 flex items-center"
|
||||
allowClear={id !== 'llm_id'}
|
||||
value={value}
|
||||
options={options}
|
||||
onChange={(value) => handleFieldChange(id, value)}
|
||||
placeholder={t('selectModelPlaceholder')}
|
||||
emptyData={t('modelEmptyTip')}
|
||||
testId={testId}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}, [defaultModelDictionary, t]);
|
||||
|
||||
return (
|
||||
<article className="rounded-lg w-full">
|
||||
@@ -185,20 +145,15 @@ const SystemSetting = ({ onOk, loading }: IProps) => {
|
||||
|
||||
<div className="px-7 py-6 space-y-6 max-h-[70vh] overflow-y-auto border border-border-button rounded-lg">
|
||||
{llmList.map((item) => (
|
||||
<Items key={item.id} {...item} />
|
||||
<ModelFieldItem
|
||||
key={item.id}
|
||||
{...item}
|
||||
onChange={handleFieldChange}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
{/* <div className="border-t px-6 py-4 flex justify-end">
|
||||
<Button
|
||||
onClick={hideModal}
|
||||
disabled={loading}
|
||||
className="px-4 py-2 border border-gray-300 rounded-md text-gray-700 hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 disabled:opacity-50"
|
||||
>
|
||||
{t('common:cancel')}
|
||||
</Button>
|
||||
</div> */}
|
||||
</article>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default SystemSetting;
|
||||
|
||||
@@ -3,49 +3,56 @@ import { LlmIcon } from '@/components/svg-icon';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { SearchInput } from '@/components/ui/input';
|
||||
import { APIMapUrl } from '@/constants/llm';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useSelectLlmList } from '@/hooks/use-llm-request';
|
||||
import { useFetchAvailableProviders } from '@/hooks/use-llm-request';
|
||||
import { ArrowUpRight, Plus } from 'lucide-react';
|
||||
import { FC, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const mapModelKey = {
|
||||
IMAGE2TEXT: 'VLM',
|
||||
'TEXT EMBEDDING': 'Embedding',
|
||||
SPEECH2TEXT: 'ASR',
|
||||
'TEXT RE-RANK': 'Rerank',
|
||||
chat: 'LLM',
|
||||
vision: 'VLM',
|
||||
embedding: 'Embedding',
|
||||
asr: 'ASR',
|
||||
rerank: 'Rerank',
|
||||
tts: 'TTS',
|
||||
ocr: 'OCR',
|
||||
};
|
||||
const orderMap: Record<TagType, number> = {
|
||||
LLM: 1,
|
||||
'TEXT EMBEDDING': 2,
|
||||
'TEXT RE-RANK': 3,
|
||||
TTS: 4,
|
||||
SPEECH2TEXT: 5,
|
||||
IMAGE2TEXT: 6,
|
||||
MODERATION: 7,
|
||||
};
|
||||
type TagType =
|
||||
| 'LLM'
|
||||
| 'TEXT EMBEDDING'
|
||||
| 'TEXT RE-RANK'
|
||||
| 'TTS'
|
||||
| 'SPEECH2TEXT'
|
||||
| 'IMAGE2TEXT'
|
||||
| 'MODERATION';
|
||||
|
||||
const sortTags = (tags: string) => {
|
||||
return tags
|
||||
.split(',')
|
||||
.map((tag) => tag.trim())
|
||||
.sort(
|
||||
(a, b) =>
|
||||
(orderMap[a as TagType] || 999) - (orderMap[b as TagType] || 999),
|
||||
);
|
||||
const orderMap: Record<ModelType, number> = {
|
||||
chat: 1,
|
||||
embedding: 2,
|
||||
rerank: 3,
|
||||
tts: 4,
|
||||
asr: 5,
|
||||
vision: 6,
|
||||
ocr: 7,
|
||||
};
|
||||
|
||||
type ModelType =
|
||||
| 'chat'
|
||||
| 'embedding'
|
||||
| 'rerank'
|
||||
| 'tts'
|
||||
| 'asr'
|
||||
| 'vision'
|
||||
| 'ocr';
|
||||
|
||||
const sortModelTypes = (modelTypes: string[]) => {
|
||||
return [...modelTypes].sort(
|
||||
(a, b) =>
|
||||
(orderMap[a as ModelType] || 999) - (orderMap[b as ModelType] || 999),
|
||||
);
|
||||
};
|
||||
|
||||
export const AvailableModels: FC<{
|
||||
handleAddModel: (factory: string) => void;
|
||||
}> = ({ handleAddModel }) => {
|
||||
const { t } = useTranslate('setting');
|
||||
const { factoryList } = useSelectLlmList();
|
||||
const { t } = useTranslation();
|
||||
const { data: factoryList } = useFetchAvailableProviders();
|
||||
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const [selectedTag, setSelectedTag] = useState<string | null>(null);
|
||||
@@ -57,7 +64,7 @@ export const AvailableModels: FC<{
|
||||
.includes(searchTerm.toLowerCase());
|
||||
const matchesTag =
|
||||
selectedTag === null ||
|
||||
model.tags.split(',').some((tag) => tag.trim() === selectedTag);
|
||||
model.model_types.some((type) => type === selectedTag);
|
||||
return matchesSearch && matchesTag;
|
||||
});
|
||||
return models;
|
||||
@@ -66,11 +73,11 @@ export const AvailableModels: FC<{
|
||||
const allTags = useMemo(() => {
|
||||
const tagsSet = new Set<string>();
|
||||
factoryList.forEach((model) => {
|
||||
model.tags.split(',').forEach((tag) => tagsSet.add(tag.trim()));
|
||||
model.model_types.forEach((type) => tagsSet.add(type));
|
||||
});
|
||||
return Array.from(tagsSet).sort(
|
||||
(a, b) =>
|
||||
(orderMap[a as TagType] || 999) - (orderMap[b as TagType] || 999),
|
||||
(orderMap[a as ModelType] || 999) - (orderMap[b as ModelType] || 999),
|
||||
);
|
||||
}, [factoryList]);
|
||||
|
||||
@@ -84,14 +91,16 @@ export const AvailableModels: FC<{
|
||||
data-testid="available-models-section"
|
||||
>
|
||||
<header className="p-4 space-y-3">
|
||||
<h3 className="text-text-primary text-base">{t('availableModels')}</h3>
|
||||
<h3 className="text-text-primary text-base">
|
||||
{t('setting.availableModels')}
|
||||
</h3>
|
||||
{/* Search Bar */}
|
||||
<div>
|
||||
{/* <div className="relative"> */}
|
||||
<SearchInput
|
||||
data-testid="model-providers-search"
|
||||
type="text"
|
||||
placeholder={t('search')}
|
||||
placeholder={t('setting.search')}
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
className="w-full px-4 py-2 pl-10 bg-bg-input border border-border-default rounded-lg focus:outline-none focus:ring-1 focus:ring-border-button transition-colors"
|
||||
@@ -165,19 +174,17 @@ export const AvailableModels: FC<{
|
||||
className="px-2 opacity-0 transition-all group-hover:opacity-100 group-focus-within:opacity-100"
|
||||
>
|
||||
<Plus size={12} />
|
||||
{t('addTheModel')}
|
||||
{t('setting.addTheModel')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-wrap gap-1">
|
||||
{sortTags(model.tags).map((tag, index) => (
|
||||
{sortModelTypes(model.model_types).map((type, index) => (
|
||||
<span
|
||||
key={index}
|
||||
className="px-1 flex items-center h-5 text-xs bg-bg-card text-text-secondary rounded-md"
|
||||
>
|
||||
{/* {tag} */}
|
||||
{mapModelKey[tag.trim() as keyof typeof mapModelKey] ||
|
||||
tag.trim()}
|
||||
{mapModelKey[type as keyof typeof mapModelKey] || type}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,15 +1,38 @@
|
||||
import { LlmItem, useSelectLlmList } from '@/hooks/use-llm-request';
|
||||
import { t } from 'i18next';
|
||||
import { ModelProviderCard } from './modal-card';
|
||||
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
|
||||
import { LlmIcon } from '@/components/svg-icon';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from '@/components/ui/collapsible';
|
||||
import { Switch } from '@/components/ui/switch';
|
||||
import { ModelStatus } from '@/constants/llm';
|
||||
import {
|
||||
useDeleteProviderInstance,
|
||||
useFetchAddedProviders,
|
||||
useFetchInstanceModels,
|
||||
useFetchProviderInstances,
|
||||
useUpdateModelStatus,
|
||||
} from '@/hooks/use-llm-request';
|
||||
import {
|
||||
IAvailableProvider,
|
||||
IInstanceModel,
|
||||
IProviderInstance,
|
||||
} from '@/interfaces/database/llm';
|
||||
import { ChevronsDown, ChevronsUp, Trash2 } from 'lucide-react';
|
||||
import { useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { mapModelKey } from './un-add-model';
|
||||
|
||||
export const UsedModel = ({
|
||||
export function UsedModel({
|
||||
handleAddModel,
|
||||
handleEditModel,
|
||||
}: {
|
||||
handleAddModel: (factory: string) => void;
|
||||
handleEditModel: (model: any, factory: LlmItem) => void;
|
||||
}) => {
|
||||
const { myLlmList: llmList } = useSelectLlmList();
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const { data: providerList } = useFetchAddedProviders();
|
||||
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col w-full gap-5 mb-4"
|
||||
@@ -18,16 +41,218 @@ export const UsedModel = ({
|
||||
<div className="text-text-primary text-2xl font-medium mb-2 mt-4">
|
||||
{t('setting.addedModels')}
|
||||
</div>
|
||||
{llmList.map((llm) => {
|
||||
return (
|
||||
<ModelProviderCard
|
||||
key={llm.name}
|
||||
item={llm}
|
||||
clickApiKey={handleAddModel}
|
||||
handleEditModel={handleEditModel}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
{providerList.map((provider) => (
|
||||
<ProviderCard
|
||||
key={provider.name}
|
||||
provider={provider}
|
||||
handleAddModel={handleAddModel}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
function ProviderCard({
|
||||
provider,
|
||||
handleAddModel,
|
||||
}: {
|
||||
provider: IAvailableProvider;
|
||||
handleAddModel: (factory: string) => void;
|
||||
}) {
|
||||
const { data: instances } = useFetchProviderInstances(provider.name);
|
||||
|
||||
return (
|
||||
<div
|
||||
className="w-full rounded-lg border border-border-button"
|
||||
data-testid="added-model-card"
|
||||
data-provider={provider.name}
|
||||
>
|
||||
{/* Provider header */}
|
||||
<div className="flex h-16 items-center p-4 text-text-secondary">
|
||||
<div className="flex items-center space-x-3">
|
||||
<LlmIcon name={provider.name} width={32} />
|
||||
<div className="font-medium text-xl text-text-primary">
|
||||
{provider.name}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Instances */}
|
||||
{instances.length > 0 && (
|
||||
<div className="border-t border-border-button">
|
||||
{instances.map((instance) => (
|
||||
<InstanceRow
|
||||
key={instance.id}
|
||||
instance={instance}
|
||||
providerName={provider.name}
|
||||
handleAddModel={handleAddModel}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function InstanceRow({
|
||||
instance,
|
||||
providerName,
|
||||
// handleAddModel,
|
||||
}: {
|
||||
instance: IProviderInstance;
|
||||
providerName: string;
|
||||
handleAddModel: (factory: string) => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { deleteProviderInstance } = useDeleteProviderInstance();
|
||||
|
||||
const handleDelete = async () => {
|
||||
await deleteProviderInstance({
|
||||
provider_name: providerName,
|
||||
instances: [instance.instance_name],
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Collapsible
|
||||
open={visible}
|
||||
onOpenChange={setVisible}
|
||||
className="border-b border-border-button last:border-b-0"
|
||||
>
|
||||
<div>
|
||||
{/* Instance header */}
|
||||
<div className="flex items-center justify-between p-4">
|
||||
<span className="font-medium text-text-primary">
|
||||
{instance.instance_name}
|
||||
</span>
|
||||
<div className="flex items-center space-x-2">
|
||||
{/* <Button variant="outline" size="sm" onClick={handleApiKeyClick}>
|
||||
{t('setting.apiKey')}
|
||||
</Button> */}
|
||||
<CollapsibleTrigger asChild>
|
||||
<Button variant="outline" size="sm">
|
||||
<span>
|
||||
{visible
|
||||
? t('setting.hideModels')
|
||||
: t('setting.showMoreModels')}
|
||||
</span>
|
||||
{visible ? (
|
||||
<ChevronsUp size={16} />
|
||||
) : (
|
||||
<ChevronsDown size={16} />
|
||||
)}
|
||||
</Button>
|
||||
</CollapsibleTrigger>
|
||||
<ConfirmDeleteDialog onOk={handleDelete}>
|
||||
<Button size="icon" variant="danger-hover">
|
||||
<Trash2 size={16} />
|
||||
</Button>
|
||||
</ConfirmDeleteDialog>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Models */}
|
||||
<CollapsibleContent>
|
||||
<InstanceModelList
|
||||
providerName={providerName}
|
||||
instanceName={instance.instance_name}
|
||||
/>
|
||||
</CollapsibleContent>
|
||||
</div>
|
||||
</Collapsible>
|
||||
);
|
||||
}
|
||||
|
||||
function InstanceModelList({
|
||||
providerName,
|
||||
instanceName,
|
||||
}: {
|
||||
providerName: string;
|
||||
instanceName: string;
|
||||
}) {
|
||||
const { data: models } = useFetchInstanceModels(providerName, instanceName);
|
||||
|
||||
const modelTypes = useMemo(() => {
|
||||
const types = new Set<string>();
|
||||
models.forEach((m) => {
|
||||
if (m.model_type) {
|
||||
m.model_type.forEach((type) => types.add(type));
|
||||
}
|
||||
});
|
||||
return Array.from(types);
|
||||
}, [models]);
|
||||
|
||||
return (
|
||||
<div className="px-4 pb-4">
|
||||
{/* Model type tags */}
|
||||
{modelTypes.length > 0 && (
|
||||
<div className="flex flex-wrap gap-2 mb-3">
|
||||
{modelTypes.map((type) => (
|
||||
<span
|
||||
key={type}
|
||||
className="px-2 py-1 text-xs bg-bg-card text-text-secondary rounded-md"
|
||||
>
|
||||
{mapModelKey[type.trim() as keyof typeof mapModelKey] || type}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Model list */}
|
||||
<div className="bg-bg-card rounded-lg max-h-96 overflow-auto scrollbar-auto">
|
||||
<ul>
|
||||
{models.map((model) => (
|
||||
<ModelListItem
|
||||
key={model.name}
|
||||
model={model}
|
||||
providerName={providerName}
|
||||
instanceName={instanceName}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function ModelListItem({
|
||||
model,
|
||||
providerName,
|
||||
instanceName,
|
||||
}: {
|
||||
model: IInstanceModel;
|
||||
providerName: string;
|
||||
instanceName: string;
|
||||
}) {
|
||||
const { updateModelStatus } = useUpdateModelStatus();
|
||||
|
||||
const handleStatusChange = (checked: boolean) => {
|
||||
updateModelStatus({
|
||||
provider_name: providerName,
|
||||
instance_name: instanceName,
|
||||
model_name: model.name,
|
||||
status: checked ? ModelStatus.Active : ModelStatus.Inactive,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<li className="flex items-center border-b-[0.5px] border-border-button justify-between p-3 hover:bg-bg-card transition-colors last:border-b-0">
|
||||
<div className="flex items-center space-x-3">
|
||||
<span className="font-medium text-text-primary">{model.name}</span>
|
||||
{model.model_type.map((modelType) => (
|
||||
<span
|
||||
className="px-2 py-1 text-xs bg-bg-card text-text-secondary rounded-md"
|
||||
key={modelType}
|
||||
>
|
||||
{modelType}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
<Switch
|
||||
checked={model.status === ModelStatus.Active}
|
||||
onCheckedChange={handleStatusChange}
|
||||
/>
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,55 +1,114 @@
|
||||
import { LLMFactory } from '@/constants/llm';
|
||||
import { useSetModalState, useShowDeleteConfirm } from '@/hooks/common-hooks';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import {
|
||||
IApiKeySavingParams,
|
||||
ISystemModelSettingSavingParams,
|
||||
useAddLlm,
|
||||
useDeleteFactory,
|
||||
useDeleteLlm,
|
||||
useEnableLlm,
|
||||
useSaveApiKey,
|
||||
useSaveTenantInfo,
|
||||
useSelectLlmOptionsByModelType,
|
||||
useAddInstanceModel,
|
||||
useAddProviderInstance,
|
||||
useFetchAddedProviders,
|
||||
useFetchProviderInstances,
|
||||
} from '@/hooks/use-llm-request';
|
||||
import { useFetchTenantInfo } from '@/hooks/use-user-setting-request';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { IAddProviderInstanceRequestBody } from '@/interfaces/request/llm';
|
||||
import { getRealModelName } from '@/utils/llm-util';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
import { ApiKeyPostBody } from '../interface';
|
||||
import { MinerUFormValues } from './modal/mineru-modal';
|
||||
import { splitProviderPayload } from './payload-utils';
|
||||
|
||||
type SavingParamsState = Omit<IApiKeySavingParams, 'api_key'>;
|
||||
type SavingParamsState = {
|
||||
llm_factory: string;
|
||||
llm_name?: string;
|
||||
model_type?: string;
|
||||
instance_name?: string;
|
||||
base_url?: string;
|
||||
};
|
||||
export type VerifyResult = {
|
||||
isValid: boolean | null;
|
||||
logs: string;
|
||||
};
|
||||
|
||||
const useSubmitProviderInstance = () => {
|
||||
const { addProviderInstance } = useAddProviderInstance();
|
||||
const { addInstanceModel } = useAddInstanceModel();
|
||||
|
||||
return useCallback(
|
||||
async (payload: IAddProviderInstanceRequestBody, isVerify = false) => {
|
||||
if (isVerify) {
|
||||
return addProviderInstance({ ...payload, verify: true });
|
||||
}
|
||||
|
||||
const { instancePayload, modelPayload } = splitProviderPayload(payload);
|
||||
const hasModelPayload =
|
||||
!!modelPayload.model_name && !!modelPayload.model_type;
|
||||
|
||||
const instanceRet = await addProviderInstance({
|
||||
...instancePayload,
|
||||
llm_factory: payload.llm_factory,
|
||||
instance_name: payload.instance_name,
|
||||
} as IAddProviderInstanceRequestBody);
|
||||
if (instanceRet.code !== 0 || !hasModelPayload) {
|
||||
return instanceRet;
|
||||
}
|
||||
|
||||
if (!hasModelPayload) {
|
||||
return { code: 0, data: null } as any;
|
||||
}
|
||||
|
||||
return addInstanceModel({
|
||||
provider_name: payload.llm_factory,
|
||||
instance_name: payload.instance_name,
|
||||
...modelPayload,
|
||||
});
|
||||
},
|
||||
[addProviderInstance, addInstanceModel],
|
||||
);
|
||||
};
|
||||
|
||||
export const useFetchInstanceNameSet = (providerName: string) => {
|
||||
const { data: addedProviders } = useFetchAddedProviders();
|
||||
const providerExists = useMemo(
|
||||
() => addedProviders.some((p) => p.name === providerName),
|
||||
[addedProviders, providerName],
|
||||
);
|
||||
const { data: instances } = useFetchProviderInstances(
|
||||
providerExists ? providerName : '',
|
||||
);
|
||||
const instanceNameSet = useMemo(
|
||||
() => new Set(instances.map((i) => i.instance_name)),
|
||||
[instances],
|
||||
);
|
||||
return { instanceNameSet, providerExists };
|
||||
};
|
||||
|
||||
export const useHideWhenInstanceExists = (instanceNameSet: Set<string>) => {
|
||||
return useCallback(
|
||||
(formValues: any) => {
|
||||
const name = ((formValues?.instance_name as string) || '').trim();
|
||||
return !(name && instanceNameSet.has(name));
|
||||
},
|
||||
[instanceNameSet],
|
||||
);
|
||||
};
|
||||
export const useSubmitApiKey = () => {
|
||||
const [savingParams, setSavingParams] = useState<SavingParamsState>(
|
||||
{} as SavingParamsState,
|
||||
);
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const { saveApiKey } = useSaveApiKey();
|
||||
const submitProviderInstance = useSubmitProviderInstance();
|
||||
const [saveLoading, setSaveLoading] = useState(false);
|
||||
const {
|
||||
visible: apiKeyVisible,
|
||||
hideModal: hideApiKeyModal,
|
||||
showModal: showApiKeyModal,
|
||||
} = useSetModalState();
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
const onApiKeySavingOk = useCallback(
|
||||
async (postBody: ApiKeyPostBody, isVerify = false) => {
|
||||
if (!isVerify) {
|
||||
setSaveLoading(true);
|
||||
}
|
||||
const payload: IApiKeySavingParams = {
|
||||
...savingParams,
|
||||
...postBody,
|
||||
verify: isVerify,
|
||||
};
|
||||
let apiKey: string | Record<string, any> = postBody.api_key || '';
|
||||
|
||||
if (savingParams.llm_factory === LLMFactory.SILICONFLOW) {
|
||||
let sourceFid = LLMFactory.SILICONFLOW;
|
||||
let sourceFid: string = LLMFactory.SILICONFLOW;
|
||||
const baseUrl = postBody.base_url;
|
||||
if (baseUrl) {
|
||||
try {
|
||||
@@ -65,14 +124,24 @@ export const useSubmitApiKey = () => {
|
||||
// ignore invalid URL and keep default sourceFid
|
||||
}
|
||||
}
|
||||
payload.source_fid = sourceFid;
|
||||
apiKey = { api_key: postBody.api_key, source_fid: sourceFid };
|
||||
}
|
||||
|
||||
const ret = await saveApiKey(payload);
|
||||
const req: IAddProviderInstanceRequestBody = {
|
||||
instance_name:
|
||||
postBody.instance_name || savingParams.instance_name || '',
|
||||
llm_factory: savingParams.llm_factory,
|
||||
llm_name: savingParams.llm_name || '',
|
||||
model_type: savingParams.model_type || '',
|
||||
api_key: apiKey,
|
||||
api_base: postBody.base_url || '',
|
||||
max_tokens: 0,
|
||||
};
|
||||
|
||||
const ret = await submitProviderInstance(req, isVerify);
|
||||
if (!isVerify) {
|
||||
setSaveLoading(false);
|
||||
if (ret.code === 0) {
|
||||
queryClient.invalidateQueries({ queryKey: ['llmList'] });
|
||||
hideApiKeyModal();
|
||||
setEditMode(false);
|
||||
}
|
||||
@@ -93,7 +162,7 @@ export const useSubmitApiKey = () => {
|
||||
return res;
|
||||
}
|
||||
},
|
||||
[hideApiKeyModal, saveApiKey, savingParams, queryClient],
|
||||
[hideApiKeyModal, submitProviderInstance, savingParams],
|
||||
);
|
||||
|
||||
const onShowApiKeyModal = useCallback(
|
||||
@@ -117,57 +186,14 @@ export const useSubmitApiKey = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useSubmitSystemModelSetting = () => {
|
||||
const { data: systemSetting } = useFetchTenantInfo();
|
||||
const { saveTenantInfo: saveSystemModelSetting, loading } =
|
||||
useSaveTenantInfo();
|
||||
const {
|
||||
visible: systemSettingVisible,
|
||||
hideModal: hideSystemSettingModal,
|
||||
showModal: showSystemSettingModal,
|
||||
} = useSetModalState();
|
||||
|
||||
const onSystemSettingSavingOk = useCallback(
|
||||
async (
|
||||
payload: Omit<ISystemModelSettingSavingParams, 'tenant_id' | 'name'>,
|
||||
) => {
|
||||
const ret = await saveSystemModelSetting({
|
||||
tenant_id: systemSetting.tenant_id,
|
||||
name: systemSetting.name,
|
||||
...payload,
|
||||
});
|
||||
|
||||
if (ret === 0) {
|
||||
hideSystemSettingModal();
|
||||
}
|
||||
},
|
||||
[hideSystemSettingModal, saveSystemModelSetting, systemSetting],
|
||||
);
|
||||
|
||||
return {
|
||||
saveSystemModelSettingLoading: loading,
|
||||
onSystemSettingSavingOk,
|
||||
systemSettingVisible,
|
||||
hideSystemSettingModal,
|
||||
showSystemSettingModal,
|
||||
};
|
||||
};
|
||||
|
||||
export const useFetchSystemModelSettingOnMount = () => {
|
||||
const { data: systemSetting } = useFetchTenantInfo();
|
||||
const allOptions = useSelectLlmOptionsByModelType();
|
||||
|
||||
return { systemSetting, allOptions };
|
||||
};
|
||||
|
||||
export const useSubmitOllama = () => {
|
||||
const [selectedLlmFactory, setSelectedLlmFactory] = useState<string>('');
|
||||
const [editMode, setEditMode] = useState(false);
|
||||
const [initialValues, setInitialValues] = useState<
|
||||
Partial<IAddLlmRequestBody> & { provider_order?: string }
|
||||
Partial<IAddProviderInstanceRequestBody> & { provider_order?: string }
|
||||
>();
|
||||
const [saveLoading, setSaveLoading] = useState(false);
|
||||
const { addLlm } = useAddLlm();
|
||||
const submitProviderInstance = useSubmitProviderInstance();
|
||||
const {
|
||||
visible: llmAddingVisible,
|
||||
hideModal: hideLlmAddingModal,
|
||||
@@ -175,16 +201,20 @@ export const useSubmitOllama = () => {
|
||||
} = useSetModalState();
|
||||
|
||||
const onLlmAddingOk = useCallback(
|
||||
async (payload: IAddLlmRequestBody, isVerify = false) => {
|
||||
async (payload: IAddProviderInstanceRequestBody, isVerify = false) => {
|
||||
if (!isVerify) {
|
||||
setSaveLoading(true);
|
||||
}
|
||||
const cleanedPayload = { ...payload };
|
||||
if (!cleanedPayload.api_key || cleanedPayload.api_key.trim() === '') {
|
||||
delete cleanedPayload.api_key;
|
||||
}
|
||||
// if (
|
||||
// !cleanedPayload.api_key ||
|
||||
// (typeof cleanedPayload.api_key === 'string' &&
|
||||
// cleanedPayload.api_key.trim() === '')
|
||||
// ) {
|
||||
// delete cleanedPayload.api_key;
|
||||
// }
|
||||
|
||||
const ret = await addLlm({ ...cleanedPayload, verify: isVerify });
|
||||
const ret = await submitProviderInstance(cleanedPayload, isVerify);
|
||||
if (!isVerify) {
|
||||
setSaveLoading(false);
|
||||
if (ret.code === 0) {
|
||||
@@ -209,7 +239,7 @@ export const useSubmitOllama = () => {
|
||||
return res;
|
||||
}
|
||||
},
|
||||
[hideLlmAddingModal, addLlm, setSaveLoading],
|
||||
[hideLlmAddingModal, submitProviderInstance, setSaveLoading],
|
||||
);
|
||||
|
||||
const handleShowLlmAddingModal = (
|
||||
@@ -223,6 +253,8 @@ export const useSubmitOllama = () => {
|
||||
|
||||
if (isEdit && detailedData) {
|
||||
const initialVals = {
|
||||
instance_name:
|
||||
detailedData.instance_name || getRealModelName(detailedData.name),
|
||||
llm_name: getRealModelName(detailedData.name),
|
||||
model_type: detailedData.type,
|
||||
api_base: detailedData.api_base || '',
|
||||
@@ -251,7 +283,7 @@ export const useSubmitOllama = () => {
|
||||
|
||||
export const useSubmitVolcEngine = () => {
|
||||
const [saveLoading, setSaveLoading] = useState(false);
|
||||
const { addLlm } = useAddLlm();
|
||||
const submitProviderInstance = useSubmitProviderInstance();
|
||||
const {
|
||||
visible: volcAddingVisible,
|
||||
hideModal: hideVolcAddingModal,
|
||||
@@ -259,11 +291,11 @@ export const useSubmitVolcEngine = () => {
|
||||
} = useSetModalState();
|
||||
|
||||
const onVolcAddingOk = useCallback(
|
||||
async (payload: IAddLlmRequestBody, isVerify = false) => {
|
||||
async (payload: IAddProviderInstanceRequestBody, isVerify = false) => {
|
||||
if (!isVerify) {
|
||||
setSaveLoading(true);
|
||||
}
|
||||
const ret = await addLlm({ ...payload, verify: isVerify });
|
||||
const ret = await submitProviderInstance(payload, isVerify);
|
||||
if (!isVerify) {
|
||||
setSaveLoading(false);
|
||||
if (ret.code === 0) {
|
||||
@@ -286,7 +318,7 @@ export const useSubmitVolcEngine = () => {
|
||||
return res;
|
||||
}
|
||||
},
|
||||
[hideVolcAddingModal, addLlm, setSaveLoading],
|
||||
[hideVolcAddingModal, submitProviderInstance, setSaveLoading],
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -300,7 +332,7 @@ export const useSubmitVolcEngine = () => {
|
||||
|
||||
export const useSubmitTencentCloud = () => {
|
||||
const [saveLoading, setSaveLoading] = useState(false);
|
||||
const { addLlm } = useAddLlm();
|
||||
const submitProviderInstance = useSubmitProviderInstance();
|
||||
const {
|
||||
visible: TencentCloudAddingVisible,
|
||||
hideModal: hideTencentCloudAddingModal,
|
||||
@@ -308,11 +340,11 @@ export const useSubmitTencentCloud = () => {
|
||||
} = useSetModalState();
|
||||
|
||||
const onTencentCloudAddingOk = useCallback(
|
||||
async (payload: IAddLlmRequestBody, isVerify = false) => {
|
||||
async (payload: IAddProviderInstanceRequestBody, isVerify = false) => {
|
||||
if (!isVerify) {
|
||||
setSaveLoading(true);
|
||||
}
|
||||
const ret = await addLlm({ ...payload, verify: isVerify });
|
||||
const ret = await submitProviderInstance(payload, isVerify);
|
||||
if (!isVerify) {
|
||||
setSaveLoading(false);
|
||||
if (ret.code === 0) {
|
||||
@@ -335,7 +367,7 @@ export const useSubmitTencentCloud = () => {
|
||||
return res;
|
||||
}
|
||||
},
|
||||
[hideTencentCloudAddingModal, addLlm, setSaveLoading],
|
||||
[hideTencentCloudAddingModal, submitProviderInstance, setSaveLoading],
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -349,7 +381,7 @@ export const useSubmitTencentCloud = () => {
|
||||
|
||||
export const useSubmitSpark = () => {
|
||||
const [saveLoading, setSaveLoading] = useState(false);
|
||||
const { addLlm } = useAddLlm();
|
||||
const submitProviderInstance = useSubmitProviderInstance();
|
||||
const {
|
||||
visible: SparkAddingVisible,
|
||||
hideModal: hideSparkAddingModal,
|
||||
@@ -357,11 +389,11 @@ export const useSubmitSpark = () => {
|
||||
} = useSetModalState();
|
||||
|
||||
const onSparkAddingOk = useCallback(
|
||||
async (payload: IAddLlmRequestBody, isVerify = false) => {
|
||||
async (payload: IAddProviderInstanceRequestBody, isVerify = false) => {
|
||||
if (!isVerify) {
|
||||
setSaveLoading(true);
|
||||
}
|
||||
const ret = await addLlm({ ...payload, verify: isVerify });
|
||||
const ret = await submitProviderInstance(payload, isVerify);
|
||||
if (!isVerify) {
|
||||
setSaveLoading(false);
|
||||
if (ret.code === 0) {
|
||||
@@ -384,7 +416,7 @@ export const useSubmitSpark = () => {
|
||||
return res;
|
||||
}
|
||||
},
|
||||
[hideSparkAddingModal, addLlm, setSaveLoading],
|
||||
[hideSparkAddingModal, submitProviderInstance, setSaveLoading],
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -398,7 +430,7 @@ export const useSubmitSpark = () => {
|
||||
|
||||
export const useSubmityiyan = () => {
|
||||
const [saveLoading, setSaveLoading] = useState(false);
|
||||
const { addLlm } = useAddLlm();
|
||||
const submitProviderInstance = useSubmitProviderInstance();
|
||||
const {
|
||||
visible: yiyanAddingVisible,
|
||||
hideModal: hideyiyanAddingModal,
|
||||
@@ -406,11 +438,11 @@ export const useSubmityiyan = () => {
|
||||
} = useSetModalState();
|
||||
|
||||
const onyiyanAddingOk = useCallback(
|
||||
async (payload: IAddLlmRequestBody, isVerify = false) => {
|
||||
async (payload: IAddProviderInstanceRequestBody, isVerify = false) => {
|
||||
if (!isVerify) {
|
||||
setSaveLoading(true);
|
||||
}
|
||||
const ret = await addLlm({ ...payload, verify: isVerify });
|
||||
const ret = await submitProviderInstance(payload, isVerify);
|
||||
if (!isVerify) {
|
||||
setSaveLoading(false);
|
||||
if (ret.code === 0) {
|
||||
@@ -433,7 +465,7 @@ export const useSubmityiyan = () => {
|
||||
return res;
|
||||
}
|
||||
},
|
||||
[hideyiyanAddingModal, addLlm, setSaveLoading],
|
||||
[hideyiyanAddingModal, submitProviderInstance, setSaveLoading],
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -447,7 +479,7 @@ export const useSubmityiyan = () => {
|
||||
|
||||
export const useSubmitFishAudio = () => {
|
||||
const [saveLoading, setSaveLoading] = useState(false);
|
||||
const { addLlm } = useAddLlm();
|
||||
const submitProviderInstance = useSubmitProviderInstance();
|
||||
const {
|
||||
visible: FishAudioAddingVisible,
|
||||
hideModal: hideFishAudioAddingModal,
|
||||
@@ -455,11 +487,11 @@ export const useSubmitFishAudio = () => {
|
||||
} = useSetModalState();
|
||||
|
||||
const onFishAudioAddingOk = useCallback(
|
||||
async (payload: IAddLlmRequestBody, isVerify = false) => {
|
||||
async (payload: IAddProviderInstanceRequestBody, isVerify = false) => {
|
||||
if (!isVerify) {
|
||||
setSaveLoading(true);
|
||||
}
|
||||
const ret = await addLlm({ ...payload, verify: isVerify });
|
||||
const ret = await submitProviderInstance(payload, isVerify);
|
||||
if (!isVerify) {
|
||||
setSaveLoading(false);
|
||||
if (ret.code === 0) {
|
||||
@@ -482,7 +514,7 @@ export const useSubmitFishAudio = () => {
|
||||
return res;
|
||||
}
|
||||
},
|
||||
[hideFishAudioAddingModal, addLlm, setSaveLoading],
|
||||
[hideFishAudioAddingModal, submitProviderInstance, setSaveLoading],
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -496,7 +528,7 @@ export const useSubmitFishAudio = () => {
|
||||
|
||||
export const useSubmitGoogle = () => {
|
||||
const [saveLoading, setSaveLoading] = useState(false);
|
||||
const { addLlm } = useAddLlm();
|
||||
const submitProviderInstance = useSubmitProviderInstance();
|
||||
const {
|
||||
visible: GoogleAddingVisible,
|
||||
hideModal: hideGoogleAddingModal,
|
||||
@@ -504,11 +536,11 @@ export const useSubmitGoogle = () => {
|
||||
} = useSetModalState();
|
||||
|
||||
const onGoogleAddingOk = useCallback(
|
||||
async (payload: IAddLlmRequestBody, isVerify = false) => {
|
||||
async (payload: IAddProviderInstanceRequestBody, isVerify = false) => {
|
||||
if (!isVerify) {
|
||||
setSaveLoading(true);
|
||||
}
|
||||
const ret = await addLlm({ ...payload, verify: isVerify });
|
||||
const ret = await submitProviderInstance(payload, isVerify);
|
||||
if (!isVerify) {
|
||||
setSaveLoading(false);
|
||||
if (ret.code === 0) {
|
||||
@@ -531,7 +563,7 @@ export const useSubmitGoogle = () => {
|
||||
return res;
|
||||
}
|
||||
},
|
||||
[hideGoogleAddingModal, addLlm, setSaveLoading],
|
||||
[hideGoogleAddingModal, submitProviderInstance, setSaveLoading],
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -545,7 +577,7 @@ export const useSubmitGoogle = () => {
|
||||
|
||||
export const useSubmitBedrock = () => {
|
||||
const [saveLoading, setSaveLoading] = useState(false);
|
||||
const { addLlm } = useAddLlm();
|
||||
const submitProviderInstance = useSubmitProviderInstance();
|
||||
const {
|
||||
visible: bedrockAddingVisible,
|
||||
hideModal: hideBedrockAddingModal,
|
||||
@@ -553,11 +585,11 @@ export const useSubmitBedrock = () => {
|
||||
} = useSetModalState();
|
||||
|
||||
const onBedrockAddingOk = useCallback(
|
||||
async (payload: IAddLlmRequestBody, isVerify = false) => {
|
||||
async (payload: IAddProviderInstanceRequestBody, isVerify = false) => {
|
||||
if (!isVerify) {
|
||||
setSaveLoading(true);
|
||||
}
|
||||
const ret = await addLlm({ ...payload, verify: isVerify });
|
||||
const ret = await submitProviderInstance(payload, isVerify);
|
||||
if (!isVerify) {
|
||||
setSaveLoading(false);
|
||||
if (ret.code === 0) {
|
||||
@@ -580,7 +612,7 @@ export const useSubmitBedrock = () => {
|
||||
return res;
|
||||
}
|
||||
},
|
||||
[hideBedrockAddingModal, addLlm, setSaveLoading],
|
||||
[hideBedrockAddingModal, submitProviderInstance, setSaveLoading],
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -594,7 +626,7 @@ export const useSubmitBedrock = () => {
|
||||
|
||||
export const useSubmitAzure = () => {
|
||||
const [saveLoading, setSaveLoading] = useState(false);
|
||||
const { addLlm } = useAddLlm();
|
||||
const submitProviderInstance = useSubmitProviderInstance();
|
||||
const {
|
||||
visible: AzureAddingVisible,
|
||||
hideModal: hideAzureAddingModal,
|
||||
@@ -602,11 +634,11 @@ export const useSubmitAzure = () => {
|
||||
} = useSetModalState();
|
||||
|
||||
const onAzureAddingOk = useCallback(
|
||||
async (payload: IAddLlmRequestBody, isVerify = false) => {
|
||||
async (payload: IAddProviderInstanceRequestBody, isVerify = false) => {
|
||||
if (!isVerify) {
|
||||
setSaveLoading(true);
|
||||
}
|
||||
const ret = await addLlm({ ...payload, verify: isVerify });
|
||||
const ret = await submitProviderInstance(payload, isVerify);
|
||||
if (!isVerify) {
|
||||
setSaveLoading(false);
|
||||
if (ret.code === 0) {
|
||||
@@ -629,7 +661,7 @@ export const useSubmitAzure = () => {
|
||||
return res;
|
||||
}
|
||||
},
|
||||
[hideAzureAddingModal, addLlm, setSaveLoading],
|
||||
[hideAzureAddingModal, submitProviderInstance, setSaveLoading],
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -641,49 +673,9 @@ export const useSubmitAzure = () => {
|
||||
};
|
||||
};
|
||||
|
||||
export const useHandleDeleteLlm = (llmFactory: string) => {
|
||||
const { deleteLlm } = useDeleteLlm();
|
||||
const showDeleteConfirm = useShowDeleteConfirm();
|
||||
|
||||
const handleDeleteLlm = (name: string) => {
|
||||
showDeleteConfirm({
|
||||
onOk: async () => {
|
||||
deleteLlm({ llm_factory: llmFactory, llm_name: name });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return { handleDeleteLlm };
|
||||
};
|
||||
|
||||
export const useHandleEnableLlm = (llmFactory: string) => {
|
||||
const { enableLlm } = useEnableLlm();
|
||||
|
||||
const handleEnableLlm = (name: string, enable: boolean) => {
|
||||
enableLlm({ llm_factory: llmFactory, llm_name: name, enable });
|
||||
};
|
||||
|
||||
return { handleEnableLlm };
|
||||
};
|
||||
|
||||
export const useHandleDeleteFactory = (llmFactory: string) => {
|
||||
const { deleteFactory } = useDeleteFactory();
|
||||
const showDeleteConfirm = useShowDeleteConfirm();
|
||||
|
||||
const handleDeleteFactory = () => {
|
||||
showDeleteConfirm({
|
||||
onOk: async () => {
|
||||
deleteFactory({ llm_factory: llmFactory });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
return { handleDeleteFactory, deleteFactory };
|
||||
};
|
||||
|
||||
export const useSubmitMinerU = () => {
|
||||
const [saveLoading, setSaveLoading] = useState(false);
|
||||
const { addLlm } = useAddLlm();
|
||||
const submitProviderInstance = useSubmitProviderInstance();
|
||||
const {
|
||||
visible: mineruVisible,
|
||||
hideModal: hideMineruModal,
|
||||
@@ -691,7 +683,10 @@ export const useSubmitMinerU = () => {
|
||||
} = useSetModalState();
|
||||
|
||||
const onMineruOk = useCallback(
|
||||
async (payload: MinerUFormValues, isVerify = false) => {
|
||||
async (
|
||||
payload: MinerUFormValues & { instance_name: string },
|
||||
isVerify = false,
|
||||
) => {
|
||||
if (!isVerify) {
|
||||
setSaveLoading(true);
|
||||
}
|
||||
@@ -700,10 +695,12 @@ export const useSubmitMinerU = () => {
|
||||
mineru_delete_output:
|
||||
(payload.mineru_delete_output ?? true) ? '1' : '0',
|
||||
};
|
||||
delete cfg.instance_name;
|
||||
if (payload.mineru_backend !== 'vlm-http-client') {
|
||||
delete cfg.mineru_server_url;
|
||||
}
|
||||
const req: IAddLlmRequestBody = {
|
||||
const req: IAddProviderInstanceRequestBody = {
|
||||
instance_name: payload.instance_name,
|
||||
llm_factory: LLMFactory.MinerU,
|
||||
llm_name: payload.llm_name,
|
||||
model_type: 'ocr',
|
||||
@@ -711,7 +708,7 @@ export const useSubmitMinerU = () => {
|
||||
api_base: '',
|
||||
max_tokens: 0,
|
||||
};
|
||||
const ret = await addLlm({ ...req, verify: isVerify });
|
||||
const ret = await submitProviderInstance(req, isVerify);
|
||||
if (!isVerify) {
|
||||
setSaveLoading(false);
|
||||
if (ret.code === 0) {
|
||||
@@ -734,7 +731,7 @@ export const useSubmitMinerU = () => {
|
||||
return res;
|
||||
}
|
||||
},
|
||||
[addLlm, hideMineruModal, setSaveLoading],
|
||||
[submitProviderInstance, hideMineruModal, setSaveLoading],
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -748,7 +745,7 @@ export const useSubmitMinerU = () => {
|
||||
|
||||
export const useSubmitPaddleOCR = () => {
|
||||
const [saveLoading, setSaveLoading] = useState(false);
|
||||
const { addLlm } = useAddLlm();
|
||||
const submitProviderInstance = useSubmitProviderInstance();
|
||||
const {
|
||||
visible: paddleocrVisible,
|
||||
hideModal: hidePaddleOCRModal,
|
||||
@@ -763,7 +760,9 @@ export const useSubmitPaddleOCR = () => {
|
||||
const cfg: any = {
|
||||
...payload,
|
||||
};
|
||||
const req: IAddLlmRequestBody = {
|
||||
delete cfg.instance_name;
|
||||
const req: IAddProviderInstanceRequestBody = {
|
||||
instance_name: payload.instance_name,
|
||||
llm_factory: LLMFactory.PaddleOCR,
|
||||
llm_name: payload.llm_name,
|
||||
model_type: 'ocr',
|
||||
@@ -771,7 +770,7 @@ export const useSubmitPaddleOCR = () => {
|
||||
api_base: '',
|
||||
max_tokens: 0,
|
||||
};
|
||||
const ret = await addLlm({ ...req, verify: isVerify });
|
||||
const ret = await submitProviderInstance(req, isVerify);
|
||||
if (!isVerify) {
|
||||
setSaveLoading(false);
|
||||
if (ret.code === 0) {
|
||||
@@ -796,7 +795,7 @@ export const useSubmitPaddleOCR = () => {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[addLlm, hidePaddleOCRModal, setSaveLoading],
|
||||
[submitProviderInstance, hidePaddleOCRModal, setSaveLoading],
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -810,7 +809,7 @@ export const useSubmitPaddleOCR = () => {
|
||||
|
||||
export const useSubmitOpenDataLoader = () => {
|
||||
const [saveLoading, setSaveLoading] = useState(false);
|
||||
const { addLlm } = useAddLlm();
|
||||
const submitProviderInstance = useSubmitProviderInstance();
|
||||
const {
|
||||
visible: opendataloaderVisible,
|
||||
hideModal: hideOpenDataLoaderModal,
|
||||
@@ -822,15 +821,18 @@ export const useSubmitOpenDataLoader = () => {
|
||||
if (!isVerify) {
|
||||
setSaveLoading(true);
|
||||
}
|
||||
const req: IAddLlmRequestBody = {
|
||||
const cfg: any = { ...payload };
|
||||
delete cfg.instance_name;
|
||||
const req: IAddProviderInstanceRequestBody = {
|
||||
instance_name: payload.instance_name,
|
||||
llm_factory: LLMFactory.OpenDataLoader,
|
||||
llm_name: payload.llm_name,
|
||||
model_type: 'ocr',
|
||||
api_key: { ...payload },
|
||||
api_key: cfg,
|
||||
api_base: '',
|
||||
max_tokens: 0,
|
||||
};
|
||||
const ret = await addLlm({ ...req, verify: isVerify });
|
||||
const ret = await submitProviderInstance(req, isVerify);
|
||||
if (!isVerify) {
|
||||
setSaveLoading(false);
|
||||
if (ret.code === 0) {
|
||||
@@ -846,7 +848,7 @@ export const useSubmitOpenDataLoader = () => {
|
||||
}
|
||||
return false;
|
||||
},
|
||||
[addLlm, hideOpenDataLoaderModal, setSaveLoading],
|
||||
[submitProviderInstance, hideOpenDataLoaderModal, setSaveLoading],
|
||||
);
|
||||
|
||||
return {
|
||||
@@ -867,7 +869,7 @@ export const useVerifySettings = ({
|
||||
isVerify?: boolean,
|
||||
) => Promise<VerifyResult | undefined>)
|
||||
| ((
|
||||
payload: IAddLlmRequestBody,
|
||||
payload: IAddProviderInstanceRequestBody,
|
||||
isVerify?: boolean,
|
||||
) => Promise<VerifyResult | undefined>)
|
||||
| ((
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Spotlight from '@/components/spotlight';
|
||||
import { LLMFactory } from '@/constants/llm';
|
||||
import { LlmItem, useFetchMyLlmListDetailed } from '@/hooks/use-llm-request';
|
||||
// import { LlmItem, useFetchMyLlmListDetailed } from '@/hooks/use-llm-request';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { isLocalLlmFactory } from '../utils';
|
||||
import SystemSetting from './components/system-setting';
|
||||
@@ -17,7 +17,6 @@ import {
|
||||
useSubmitOpenDataLoader,
|
||||
useSubmitPaddleOCR,
|
||||
useSubmitSpark,
|
||||
useSubmitSystemModelSetting,
|
||||
useSubmitTencentCloud,
|
||||
useSubmitVolcEngine,
|
||||
useSubmityiyan,
|
||||
@@ -37,9 +36,7 @@ import SparkModal from './modal/spark-modal';
|
||||
import VolcEngineModal from './modal/volcengine-modal';
|
||||
import YiyanModal from './modal/yiyan-modal';
|
||||
const ModelProviders = () => {
|
||||
const { saveSystemModelSettingLoading, onSystemSettingSavingOk } =
|
||||
useSubmitSystemModelSetting();
|
||||
const { data: detailedLlmList } = useFetchMyLlmListDetailed();
|
||||
// const { data: detailedLlmList } = useFetchMyLlmListDetailed();
|
||||
const {
|
||||
saveApiKeyLoading,
|
||||
initialApiKey,
|
||||
@@ -192,31 +189,31 @@ const ModelProviders = () => {
|
||||
[showApiKeyModal, showLlmAddingModal, ModalMap],
|
||||
);
|
||||
|
||||
const handleEditModel = useCallback(
|
||||
(model: any, factory: LlmItem) => {
|
||||
if (factory) {
|
||||
const detailedFactory = detailedLlmList[factory.name];
|
||||
const detailedModel = detailedFactory?.llm?.find(
|
||||
(m: any) => m.name === model.name,
|
||||
);
|
||||
// const handleEditModel = useCallback(
|
||||
// (model: any, factory: LlmItem) => {
|
||||
// if (factory) {
|
||||
// const detailedFactory = detailedLlmList[factory.name];
|
||||
// const detailedModel = detailedFactory?.llm?.find(
|
||||
// (m: any) => m.name === model.name,
|
||||
// );
|
||||
|
||||
const editData = {
|
||||
llm_factory: factory.name,
|
||||
llm_name: model.name,
|
||||
model_type: model.type,
|
||||
};
|
||||
// const editData = {
|
||||
// llm_factory: factory.name,
|
||||
// llm_name: model.name,
|
||||
// model_type: model.type,
|
||||
// };
|
||||
|
||||
if (isLocalLlmFactory(factory.name)) {
|
||||
showLlmAddingModal(factory.name, true, editData, detailedModel);
|
||||
} else if (factory.name in ModalMap) {
|
||||
ModalMap[factory.name as keyof typeof ModalMap]();
|
||||
} else {
|
||||
showApiKeyModal(editData, true);
|
||||
}
|
||||
}
|
||||
},
|
||||
[showApiKeyModal, showLlmAddingModal, ModalMap, detailedLlmList],
|
||||
);
|
||||
// if (isLocalLlmFactory(factory.name)) {
|
||||
// showLlmAddingModal(factory.name, true, editData, detailedModel);
|
||||
// } else if (factory.name in ModalMap) {
|
||||
// ModalMap[factory.name as keyof typeof ModalMap]();
|
||||
// } else {
|
||||
// showApiKeyModal(editData, true);
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// [showApiKeyModal, showLlmAddingModal, ModalMap, detailedLlmList],
|
||||
// );
|
||||
|
||||
const handleOk = useMemo(() => {
|
||||
if (apiKeyVisible) {
|
||||
@@ -296,14 +293,8 @@ const ModelProviders = () => {
|
||||
<div className="flex w-full border-[0.5px] border-border-button rounded-lg relative ">
|
||||
<Spotlight />
|
||||
<section className="flex flex-col gap-4 w-3/5 px-5 border-r-[0.5px] border-border-button overflow-auto scrollbar-auto">
|
||||
<SystemSetting
|
||||
onOk={onSystemSettingSavingOk}
|
||||
loading={saveSystemModelSettingLoading}
|
||||
/>
|
||||
<UsedModel
|
||||
handleAddModel={handleAddModel}
|
||||
handleEditModel={handleEditModel}
|
||||
/>
|
||||
<SystemSetting />
|
||||
<UsedModel handleAddModel={handleAddModel} />
|
||||
</section>
|
||||
<section className="flex flex-col w-2/5 overflow-auto scrollbar-auto">
|
||||
<AvailableModels handleAddModel={handleAddModel} />
|
||||
|
||||
@@ -1,12 +1,6 @@
|
||||
import { IModalManagerChildrenProps } from '@/components/modal-manager';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { RAGFlowFormItem } from '@/components/ragflow-form';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { LLMFactory } from '@/constants/llm';
|
||||
@@ -31,6 +25,7 @@ interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
|
||||
}
|
||||
|
||||
type FieldType = {
|
||||
instance_name?: string;
|
||||
api_key?: string;
|
||||
base_url?: string;
|
||||
group_id?: string;
|
||||
@@ -50,7 +45,7 @@ const ApiKeyModal = ({
|
||||
llmFactory,
|
||||
loading,
|
||||
initialValue,
|
||||
editMode = false,
|
||||
// editMode = false,
|
||||
onOk,
|
||||
onVerify,
|
||||
}: IProps) => {
|
||||
@@ -92,108 +87,100 @@ const ApiKeyModal = ({
|
||||
>
|
||||
<Form {...form}>
|
||||
<div className="space-y-4 py-4">
|
||||
<FormField
|
||||
name="api_key"
|
||||
rules={{ required: t('apiKeyMessage') }}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel
|
||||
className="text-sm font-medium text-text-secondary"
|
||||
required
|
||||
>
|
||||
{t('apiKey')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
data-testid="apikey-input"
|
||||
onKeyDown={handleKeyDown}
|
||||
className="w-full"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
<RAGFlowFormItem
|
||||
name="instance_name"
|
||||
label={t('instanceName')}
|
||||
tooltip={t('instanceNameTip')}
|
||||
rules={{ required: t('instanceNameMessage') }}
|
||||
required
|
||||
labelClassName="text-sm font-medium text-text-secondary"
|
||||
>
|
||||
{(field) => (
|
||||
<Input
|
||||
{...field}
|
||||
placeholder={t('instanceNameMessage')}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="w-full"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</RAGFlowFormItem>
|
||||
|
||||
<RAGFlowFormItem
|
||||
name="api_key"
|
||||
label={t('apiKey')}
|
||||
rules={{ required: t('apiKeyMessage') }}
|
||||
required
|
||||
labelClassName="text-sm font-medium text-text-secondary"
|
||||
>
|
||||
{(field) => (
|
||||
<Input
|
||||
{...field}
|
||||
data-testid="apikey-input"
|
||||
onKeyDown={handleKeyDown}
|
||||
className="w-full"
|
||||
/>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
|
||||
{modelsWithBaseUrl.some((x) => x === llmFactory) && (
|
||||
<FormField
|
||||
<RAGFlowFormItem
|
||||
name="base_url"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel
|
||||
className="text-sm font-medium text-text-primary"
|
||||
tooltip={
|
||||
llmFactory === LLMFactory.MiniMax
|
||||
? t('minimaxBaseUrlTip')
|
||||
: llmFactory === LLMFactory.TongYiQianWen
|
||||
? t('tongyiBaseUrlTip')
|
||||
: llmFactory === LLMFactory.SILICONFLOW
|
||||
? t('siliconBaseUrlTip')
|
||||
: t('baseUrlTip')
|
||||
}
|
||||
>
|
||||
{t('baseUrl')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder={
|
||||
llmFactory === LLMFactory.TongYiQianWen
|
||||
? t('tongyiBaseUrlPlaceholder')
|
||||
: llmFactory === LLMFactory.MiniMax
|
||||
? t('minimaxBaseUrlPlaceholder')
|
||||
: llmFactory === LLMFactory.SILICONFLOW
|
||||
? 'https://api.siliconflow.cn/v1'
|
||||
: 'https://api.openai.com/v1'
|
||||
}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="w-full"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
label={t('baseUrl')}
|
||||
tooltip={
|
||||
llmFactory === LLMFactory.MiniMax
|
||||
? t('minimaxBaseUrlTip')
|
||||
: llmFactory === LLMFactory.TongYiQianWen
|
||||
? t('tongyiBaseUrlTip')
|
||||
: llmFactory === LLMFactory.SILICONFLOW
|
||||
? t('siliconBaseUrlTip')
|
||||
: t('baseUrlTip')
|
||||
}
|
||||
labelClassName="text-sm font-medium text-text-primary"
|
||||
>
|
||||
{(field) => (
|
||||
<Input
|
||||
{...field}
|
||||
placeholder={
|
||||
llmFactory === LLMFactory.TongYiQianWen
|
||||
? t('tongyiBaseUrlPlaceholder')
|
||||
: llmFactory === LLMFactory.MiniMax
|
||||
? t('minimaxBaseUrlPlaceholder')
|
||||
: llmFactory === LLMFactory.SILICONFLOW
|
||||
? 'https://api.siliconflow.cn/v1'
|
||||
: 'https://api.openai.com/v1'
|
||||
}
|
||||
onKeyDown={handleKeyDown}
|
||||
className="w-full"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</RAGFlowFormItem>
|
||||
)}
|
||||
|
||||
{llmFactory?.toLowerCase() === 'Anthropic'.toLowerCase() && (
|
||||
<FormField
|
||||
<RAGFlowFormItem
|
||||
name="base_url"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-sm font-medium text-text-primary">
|
||||
{t('baseUrl')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="https://api.anthropic.com/v1"
|
||||
onKeyDown={handleKeyDown}
|
||||
className="w-full"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
label={t('baseUrl')}
|
||||
labelClassName="text-sm font-medium text-text-primary"
|
||||
>
|
||||
{(field) => (
|
||||
<Input
|
||||
{...field}
|
||||
placeholder="https://api.anthropic.com/v1"
|
||||
onKeyDown={handleKeyDown}
|
||||
className="w-full"
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
</RAGFlowFormItem>
|
||||
)}
|
||||
|
||||
{llmFactory?.toLowerCase() === 'Minimax'.toLowerCase() && (
|
||||
<FormField
|
||||
<RAGFlowFormItem
|
||||
name="group_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel className="text-sm font-medium text-text-primary">
|
||||
Group ID
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} className="w-full" />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
label="Group ID"
|
||||
labelClassName="text-sm font-medium text-text-primary"
|
||||
>
|
||||
{(field) => <Input {...field} className="w-full" />}
|
||||
</RAGFlowFormItem>
|
||||
)}
|
||||
|
||||
<VerifyButton onVerify={onVerify} />
|
||||
|
||||
@@ -8,9 +8,13 @@ import { Modal } from '@/components/ui/modal/modal';
|
||||
import { useCommonTranslation, useTranslate } from '@/hooks/common-hooks';
|
||||
import { useBuildModelTypeOptions } from '@/hooks/logic-hooks/use-build-options';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { VerifyResult } from '@/pages/user-setting/setting-model/hooks';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { IAddProviderInstanceRequestBody } from '@/interfaces/request/llm';
|
||||
import {
|
||||
useFetchInstanceNameSet,
|
||||
useHideWhenInstanceExists,
|
||||
VerifyResult,
|
||||
} from '@/pages/user-setting/setting-model/hooks';
|
||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||
import { FieldValues } from 'react-hook-form';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
import VerifyButton from '../../modal/verify-button';
|
||||
@@ -22,7 +26,7 @@ const AzureOpenAIModal = ({
|
||||
onVerify,
|
||||
loading,
|
||||
llmFactory,
|
||||
}: IModalProps<IAddLlmRequestBody> & {
|
||||
}: IModalProps<IAddProviderInstanceRequestBody> & {
|
||||
llmFactory: string;
|
||||
onVerify?: (
|
||||
postBody: any,
|
||||
@@ -32,87 +36,107 @@ const AzureOpenAIModal = ({
|
||||
const { t: tg } = useCommonTranslation();
|
||||
const { buildModelTypeOptions } = useBuildModelTypeOptions();
|
||||
const formRef = useRef<DynamicFormRef>(null);
|
||||
const { instanceNameSet } = useFetchInstanceNameSet(llmFactory);
|
||||
|
||||
const fields: FormFieldConfig[] = [
|
||||
{
|
||||
name: 'model_type',
|
||||
label: t('modelType'),
|
||||
type: FormFieldType.Select,
|
||||
required: true,
|
||||
options: buildModelTypeOptions(['chat', 'embedding', 'image2text']),
|
||||
defaultValue: 'embedding',
|
||||
validation: {
|
||||
message: t('modelTypeMessage'),
|
||||
const hideWhenInstanceExists = useHideWhenInstanceExists(instanceNameSet);
|
||||
|
||||
const fields: FormFieldConfig[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
name: 'instance_name',
|
||||
label: t('instanceName'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('instanceNameMessage'),
|
||||
tooltip: t('instanceNameTip'),
|
||||
validation: {
|
||||
message: t('instanceNameMessage'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'api_base',
|
||||
label: t('addLlmBaseUrl'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('baseUrlNameMessage'),
|
||||
validation: {
|
||||
message: t('baseUrlNameMessage'),
|
||||
{
|
||||
name: 'model_type',
|
||||
label: t('modelType'),
|
||||
type: FormFieldType.MultiSelect,
|
||||
required: true,
|
||||
options: buildModelTypeOptions(['chat', 'embedding', 'image2text']),
|
||||
defaultValue: ['embedding'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'api_key',
|
||||
label: t('apiKey'),
|
||||
type: FormFieldType.Text,
|
||||
required: false,
|
||||
placeholder: t('apiKeyMessage'),
|
||||
},
|
||||
{
|
||||
name: 'llm_name',
|
||||
label: t('modelName'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('modelNameMessage'),
|
||||
defaultValue: 'gpt-3.5-turbo',
|
||||
validation: {
|
||||
message: t('modelNameMessage'),
|
||||
{
|
||||
name: 'api_base',
|
||||
label: t('addLlmBaseUrl'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('baseUrlNameMessage'),
|
||||
validation: {
|
||||
message: t('baseUrlNameMessage'),
|
||||
},
|
||||
shouldRender: hideWhenInstanceExists,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'api_version',
|
||||
label: t('apiVersion'),
|
||||
type: FormFieldType.Text,
|
||||
required: false,
|
||||
placeholder: t('apiVersionMessage'),
|
||||
defaultValue: '2024-02-01',
|
||||
},
|
||||
{
|
||||
name: 'max_tokens',
|
||||
label: t('maxTokens'),
|
||||
type: FormFieldType.Number,
|
||||
required: true,
|
||||
placeholder: t('maxTokensTip'),
|
||||
validation: {
|
||||
min: 0,
|
||||
message: t('maxTokensMessage'),
|
||||
{
|
||||
name: 'api_key',
|
||||
label: t('apiKey'),
|
||||
type: FormFieldType.Text,
|
||||
required: false,
|
||||
placeholder: t('apiKeyMessage'),
|
||||
shouldRender: hideWhenInstanceExists,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'vision',
|
||||
label: t('vision'),
|
||||
type: FormFieldType.Switch,
|
||||
defaultValue: false,
|
||||
dependencies: ['model_type'],
|
||||
shouldRender: (formValues: any) => {
|
||||
return formValues?.model_type === 'chat';
|
||||
{
|
||||
name: 'llm_name',
|
||||
label: t('modelName'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('modelNameMessage'),
|
||||
defaultValue: 'gpt-3.5-turbo',
|
||||
validation: {
|
||||
message: t('modelNameMessage'),
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
{
|
||||
name: 'api_version',
|
||||
label: t('apiVersion'),
|
||||
type: FormFieldType.Text,
|
||||
required: false,
|
||||
placeholder: t('apiVersionMessage'),
|
||||
defaultValue: '2024-02-01',
|
||||
},
|
||||
{
|
||||
name: 'max_tokens',
|
||||
label: t('maxTokens'),
|
||||
type: FormFieldType.Number,
|
||||
required: true,
|
||||
placeholder: t('maxTokensTip'),
|
||||
validation: {
|
||||
min: 0,
|
||||
message: t('maxTokensMessage'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'vision',
|
||||
label: t('vision'),
|
||||
type: FormFieldType.Switch,
|
||||
defaultValue: false,
|
||||
dependencies: ['model_type'],
|
||||
shouldRender: (formValues: any) => {
|
||||
const modelType = formValues?.model_type;
|
||||
if (Array.isArray(modelType)) {
|
||||
return modelType.includes('chat');
|
||||
}
|
||||
return modelType === 'chat';
|
||||
},
|
||||
},
|
||||
],
|
||||
[t, buildModelTypeOptions, hideWhenInstanceExists],
|
||||
);
|
||||
|
||||
const handleOk = async (values?: FieldValues) => {
|
||||
if (!values) return;
|
||||
|
||||
const modelType =
|
||||
values.model_type === 'chat' && values.vision
|
||||
? 'image2text'
|
||||
: values.model_type;
|
||||
const modelType = values.model_type.map((t: string) =>
|
||||
t === 'chat' && values.vision ? 'image2text' : t,
|
||||
);
|
||||
|
||||
const data: IAddLlmRequestBody & { api_version?: string } = {
|
||||
const data: IAddProviderInstanceRequestBody & { api_version?: string } = {
|
||||
instance_name: values.instance_name as string,
|
||||
llm_factory: llmFactory,
|
||||
llm_name: values.llm_name as string,
|
||||
model_type: modelType,
|
||||
@@ -127,13 +151,11 @@ const AzureOpenAIModal = ({
|
||||
|
||||
const verifyParamsFunc = useCallback(() => {
|
||||
const values = formRef.current?.getValues();
|
||||
const modelType =
|
||||
values.model_type === 'chat' && values.vision
|
||||
? 'image2text'
|
||||
: values.model_type;
|
||||
return {
|
||||
llm_factory: llmFactory,
|
||||
model_type: modelType,
|
||||
model_type: values.model_type.map((t: string) =>
|
||||
t === 'chat' && values.vision ? 'image2text' : t,
|
||||
),
|
||||
};
|
||||
}, [llmFactory]);
|
||||
|
||||
@@ -162,10 +184,12 @@ const AzureOpenAIModal = ({
|
||||
ref={formRef}
|
||||
defaultValues={
|
||||
{
|
||||
model_type: 'embedding',
|
||||
instance_name: '',
|
||||
model_type: ['embedding'],
|
||||
llm_name: 'gpt-3.5-turbo',
|
||||
api_version: '2024-02-01',
|
||||
vision: false,
|
||||
max_tokens: 8192,
|
||||
} as FieldValues
|
||||
}
|
||||
labelClassName="font-normal"
|
||||
|
||||
@@ -4,26 +4,31 @@ import { ButtonLoading } from '@/components/ui/button';
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { Modal } from '@/components/ui/modal/modal';
|
||||
import { MultiSelect } from '@/components/ui/multi-select';
|
||||
import { Segmented } from '@/components/ui/segmented';
|
||||
import { useCommonTranslation, useTranslate } from '@/hooks/common-hooks';
|
||||
import { useBuildModelTypeOptions } from '@/hooks/logic-hooks/use-build-options';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { VerifyResult } from '@/pages/user-setting/setting-model/hooks';
|
||||
import { IAddProviderInstanceRequestBody } from '@/interfaces/request/llm';
|
||||
import {
|
||||
useFetchInstanceNameSet,
|
||||
VerifyResult,
|
||||
} from '@/pages/user-setting/setting-model/hooks';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||
import { useForm, useWatch } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
import { BedrockRegionList } from '../../constant';
|
||||
import VerifyButton from '../../modal/verify-button';
|
||||
|
||||
type FieldType = IAddLlmRequestBody & {
|
||||
type FieldType = Omit<IAddProviderInstanceRequestBody, 'model_type'> & {
|
||||
auth_mode?: 'access_key_secret' | 'iam_role' | 'assume_role';
|
||||
bedrock_ak: string;
|
||||
bedrock_sk: string;
|
||||
bedrock_region: string;
|
||||
aws_role_arn?: string;
|
||||
model_type: string[];
|
||||
};
|
||||
|
||||
const BedrockModal = ({
|
||||
@@ -33,7 +38,7 @@ const BedrockModal = ({
|
||||
onVerify,
|
||||
loading,
|
||||
llmFactory,
|
||||
}: IModalProps<IAddLlmRequestBody> & {
|
||||
}: IModalProps<IAddProviderInstanceRequestBody> & {
|
||||
llmFactory: string;
|
||||
onVerify?: (
|
||||
postBody: any,
|
||||
@@ -42,61 +47,84 @@ const BedrockModal = ({
|
||||
const { t } = useTranslate('setting');
|
||||
const { t: ct } = useCommonTranslation();
|
||||
const { buildModelTypeOptions } = useBuildModelTypeOptions();
|
||||
const { instanceNameSet } = useFetchInstanceNameSet(llmFactory);
|
||||
const instanceExistsRef = useRef(false);
|
||||
|
||||
const FormSchema = z
|
||||
.object({
|
||||
model_type: z.enum(['chat', 'embedding'], {
|
||||
required_error: t('modelTypeMessage'),
|
||||
}),
|
||||
llm_name: z.string().min(1, { message: t('bedrockModelNameMessage') }),
|
||||
bedrock_region: z.string().min(1, { message: t('bedrockRegionMessage') }),
|
||||
max_tokens: z
|
||||
.number({
|
||||
required_error: t('maxTokensMessage'),
|
||||
invalid_type_error: t('maxTokensInvalidMessage'),
|
||||
const FormSchema = useMemo(
|
||||
() =>
|
||||
z
|
||||
.object({
|
||||
instance_name: z
|
||||
.string()
|
||||
.min(1, { message: t('instanceNameMessage') }),
|
||||
model_type: z
|
||||
.array(z.enum(['chat', 'embedding']))
|
||||
.min(1, { message: t('modelTypeMessage') }),
|
||||
llm_name: z
|
||||
.string()
|
||||
.min(1, { message: t('bedrockModelNameMessage') }),
|
||||
bedrock_region: z.string().optional(),
|
||||
max_tokens: z
|
||||
.number({
|
||||
required_error: t('maxTokensMessage'),
|
||||
invalid_type_error: t('maxTokensInvalidMessage'),
|
||||
})
|
||||
.nonnegative({ message: t('maxTokensMinMessage') }),
|
||||
auth_mode: z
|
||||
.enum(['access_key_secret', 'iam_role', 'assume_role'])
|
||||
.default('access_key_secret'),
|
||||
bedrock_ak: z.string().optional(),
|
||||
bedrock_sk: z.string().optional(),
|
||||
aws_role_arn: z.string().optional(),
|
||||
})
|
||||
.nonnegative({ message: t('maxTokensMinMessage') }),
|
||||
auth_mode: z
|
||||
.enum(['access_key_secret', 'iam_role', 'assume_role'])
|
||||
.default('access_key_secret'),
|
||||
bedrock_ak: z.string().optional(),
|
||||
bedrock_sk: z.string().optional(),
|
||||
aws_role_arn: z.string().optional(),
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (data.auth_mode === 'access_key_secret') {
|
||||
if (!data.bedrock_ak || data.bedrock_ak.trim() === '') {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: t('bedrockAKMessage'),
|
||||
path: ['bedrock_ak'],
|
||||
});
|
||||
}
|
||||
if (!data.bedrock_sk || data.bedrock_sk.trim() === '') {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: t('bedrockSKMessage'),
|
||||
path: ['bedrock_sk'],
|
||||
});
|
||||
}
|
||||
}
|
||||
.superRefine((data, ctx) => {
|
||||
if (instanceExistsRef.current) return;
|
||||
|
||||
if (data.auth_mode === 'iam_role') {
|
||||
if (!data.aws_role_arn || data.aws_role_arn.trim() === '') {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: t('awsRoleArnMessage'),
|
||||
path: ['aws_role_arn'],
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
if (!data.bedrock_region || data.bedrock_region.trim() === '') {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: t('bedrockRegionMessage'),
|
||||
path: ['bedrock_region'],
|
||||
});
|
||||
}
|
||||
|
||||
if (data.auth_mode === 'access_key_secret') {
|
||||
if (!data.bedrock_ak || data.bedrock_ak.trim() === '') {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: t('bedrockAKMessage'),
|
||||
path: ['bedrock_ak'],
|
||||
});
|
||||
}
|
||||
if (!data.bedrock_sk || data.bedrock_sk.trim() === '') {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: t('bedrockSKMessage'),
|
||||
path: ['bedrock_sk'],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (data.auth_mode === 'iam_role') {
|
||||
if (!data.aws_role_arn || data.aws_role_arn.trim() === '') {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: t('awsRoleArnMessage'),
|
||||
path: ['aws_role_arn'],
|
||||
});
|
||||
}
|
||||
}
|
||||
}),
|
||||
[t],
|
||||
);
|
||||
|
||||
const form = useForm<FieldType>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
model_type: 'chat',
|
||||
instance_name: '',
|
||||
model_type: ['chat'],
|
||||
auth_mode: 'access_key_secret',
|
||||
max_tokens: 8192,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -105,6 +133,17 @@ const BedrockModal = ({
|
||||
name: 'auth_mode',
|
||||
});
|
||||
|
||||
const instanceName = useWatch({
|
||||
control: form.control,
|
||||
name: 'instance_name',
|
||||
});
|
||||
|
||||
const instanceExists = useMemo(() => {
|
||||
const trimmed = (instanceName || '').trim();
|
||||
return !!trimmed && instanceNameSet.has(trimmed);
|
||||
}, [instanceName, instanceNameSet]);
|
||||
instanceExistsRef.current = instanceExists;
|
||||
|
||||
const options = useMemo(
|
||||
() => BedrockRegionList.map((x) => ({ value: x, label: t(x) })),
|
||||
[t],
|
||||
@@ -133,9 +172,10 @@ const BedrockModal = ({
|
||||
...cleanedValues,
|
||||
llm_factory: llmFactory,
|
||||
max_tokens: values.max_tokens,
|
||||
model_type: values.model_type,
|
||||
};
|
||||
|
||||
onOk?.(data as unknown as IAddLlmRequestBody);
|
||||
onOk?.(data as unknown as IAddProviderInstanceRequestBody);
|
||||
};
|
||||
|
||||
const verifyParamsFunc = useCallback(() => {
|
||||
@@ -160,6 +200,7 @@ const BedrockModal = ({
|
||||
...cleanedValues,
|
||||
llm_factory: llmFactory,
|
||||
max_tokens: values.max_tokens,
|
||||
model_type: values.model_type,
|
||||
};
|
||||
}, [llmFactory, authMode, form]);
|
||||
|
||||
@@ -199,13 +240,24 @@ const BedrockModal = ({
|
||||
className="space-y-6"
|
||||
id="bedrock-form"
|
||||
>
|
||||
<RAGFlowFormItem
|
||||
name="instance_name"
|
||||
label={t('instanceName')}
|
||||
tooltip={t('instanceNameTip')}
|
||||
required
|
||||
>
|
||||
<Input placeholder={t('instanceNameMessage')} />
|
||||
</RAGFlowFormItem>
|
||||
|
||||
<RAGFlowFormItem name="model_type" label={t('modelType')} required>
|
||||
{(field) => (
|
||||
<SelectWithSearch
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
<MultiSelect
|
||||
options={buildModelTypeOptions(['chat', 'embedding'])}
|
||||
placeholder={t('modelTypeMessage')}
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={field.value}
|
||||
variant="inverted"
|
||||
maxCount={100}
|
||||
/>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
@@ -214,86 +266,93 @@ const BedrockModal = ({
|
||||
<Input placeholder={t('bedrockModelNameMessage')} />
|
||||
</RAGFlowFormItem>
|
||||
|
||||
<div className="mb-4">
|
||||
<RAGFlowFormItem name="auth_mode">
|
||||
{(field) => (
|
||||
<Segmented
|
||||
value={field.value}
|
||||
onChange={(value) => {
|
||||
// Clear non-active fields so they won't be validated/submitted by accident.
|
||||
if (value !== 'access_key_secret') {
|
||||
form.setValue('bedrock_ak', '');
|
||||
form.setValue('bedrock_sk', '');
|
||||
}
|
||||
if (value !== 'iam_role') {
|
||||
form.setValue('aws_role_arn', '');
|
||||
}
|
||||
field.onChange(value);
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
label: t('awsAuthModeAccessKeySecret'),
|
||||
value: 'access_key_secret',
|
||||
},
|
||||
{ label: t('awsAuthModeIamRole'), value: 'iam_role' },
|
||||
{ label: t('awsAuthModeAssumeRole'), value: 'assume_role' },
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
</div>
|
||||
|
||||
{authMode === 'access_key_secret' && (
|
||||
{!instanceExists && (
|
||||
<>
|
||||
<div className="mb-4">
|
||||
<RAGFlowFormItem name="auth_mode">
|
||||
{(field) => (
|
||||
<Segmented
|
||||
value={field.value}
|
||||
onChange={(value) => {
|
||||
// Clear non-active fields so they won't be validated/submitted by accident.
|
||||
if (value !== 'access_key_secret') {
|
||||
form.setValue('bedrock_ak', '');
|
||||
form.setValue('bedrock_sk', '');
|
||||
}
|
||||
if (value !== 'iam_role') {
|
||||
form.setValue('aws_role_arn', '');
|
||||
}
|
||||
field.onChange(value);
|
||||
}}
|
||||
options={[
|
||||
{
|
||||
label: t('awsAuthModeAccessKeySecret'),
|
||||
value: 'access_key_secret',
|
||||
},
|
||||
{ label: t('awsAuthModeIamRole'), value: 'iam_role' },
|
||||
{
|
||||
label: t('awsAuthModeAssumeRole'),
|
||||
value: 'assume_role',
|
||||
},
|
||||
]}
|
||||
/>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
</div>
|
||||
|
||||
{authMode === 'access_key_secret' && (
|
||||
<>
|
||||
<RAGFlowFormItem
|
||||
name="bedrock_ak"
|
||||
label={t('awsAccessKeyId')}
|
||||
required
|
||||
>
|
||||
<Input placeholder={t('bedrockAKMessage')} />
|
||||
</RAGFlowFormItem>
|
||||
<RAGFlowFormItem
|
||||
name="bedrock_sk"
|
||||
label={t('awsSecretAccessKey')}
|
||||
required
|
||||
>
|
||||
<Input placeholder={t('bedrockSKMessage')} />
|
||||
</RAGFlowFormItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
{authMode === 'iam_role' && (
|
||||
<RAGFlowFormItem
|
||||
name="aws_role_arn"
|
||||
label={t('awsRoleArn')}
|
||||
required
|
||||
>
|
||||
<Input placeholder={t('awsRoleArnMessage')} />
|
||||
</RAGFlowFormItem>
|
||||
)}
|
||||
|
||||
{authMode === 'assume_role' && (
|
||||
<div className="text-sm text-text-secondary mt-2 mb-4">
|
||||
{t('awsAssumeRoleTip')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<RAGFlowFormItem
|
||||
name="bedrock_ak"
|
||||
label={t('awsAccessKeyId')}
|
||||
name="bedrock_region"
|
||||
label={t('bedrockRegion')}
|
||||
required
|
||||
>
|
||||
<Input placeholder={t('bedrockAKMessage')} />
|
||||
</RAGFlowFormItem>
|
||||
<RAGFlowFormItem
|
||||
name="bedrock_sk"
|
||||
label={t('awsSecretAccessKey')}
|
||||
required
|
||||
>
|
||||
<Input placeholder={t('bedrockSKMessage')} />
|
||||
{(field) => (
|
||||
<SelectWithSearch
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
options={options}
|
||||
placeholder={t('bedrockRegionMessage')}
|
||||
allowClear
|
||||
/>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
</>
|
||||
)}
|
||||
|
||||
{authMode === 'iam_role' && (
|
||||
<RAGFlowFormItem
|
||||
name="aws_role_arn"
|
||||
label={t('awsRoleArn')}
|
||||
required
|
||||
>
|
||||
<Input placeholder={t('awsRoleArnMessage')} />
|
||||
</RAGFlowFormItem>
|
||||
)}
|
||||
|
||||
{authMode === 'assume_role' && (
|
||||
<div className="text-sm text-text-secondary mt-2 mb-4">
|
||||
{t('awsAssumeRoleTip')}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<RAGFlowFormItem
|
||||
name="bedrock_region"
|
||||
label={t('bedrockRegion')}
|
||||
required
|
||||
>
|
||||
{(field) => (
|
||||
<SelectWithSearch
|
||||
value={field.value}
|
||||
onChange={field.onChange}
|
||||
options={options}
|
||||
placeholder={t('bedrockRegionMessage')}
|
||||
allowClear
|
||||
/>
|
||||
)}
|
||||
</RAGFlowFormItem>
|
||||
|
||||
<RAGFlowFormItem name="max_tokens" label={t('maxTokens')} required>
|
||||
{(field) => (
|
||||
<Input
|
||||
|
||||
@@ -7,9 +7,13 @@ import { Modal } from '@/components/ui/modal/modal';
|
||||
import { useCommonTranslation, useTranslate } from '@/hooks/common-hooks';
|
||||
import { useBuildModelTypeOptions } from '@/hooks/logic-hooks/use-build-options';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { VerifyResult } from '@/pages/user-setting/setting-model/hooks';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { IAddProviderInstanceRequestBody } from '@/interfaces/request/llm';
|
||||
import {
|
||||
useFetchInstanceNameSet,
|
||||
useHideWhenInstanceExists,
|
||||
VerifyResult,
|
||||
} from '@/pages/user-setting/setting-model/hooks';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { FieldValues } from 'react-hook-form';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
import VerifyButton from '../../modal/verify-button';
|
||||
@@ -21,7 +25,7 @@ const FishAudioModal = ({
|
||||
onVerify,
|
||||
loading,
|
||||
llmFactory,
|
||||
}: IModalProps<IAddLlmRequestBody> & {
|
||||
}: IModalProps<IAddProviderInstanceRequestBody> & {
|
||||
llmFactory: string;
|
||||
onVerify?: (
|
||||
postBody: any,
|
||||
@@ -30,58 +34,78 @@ const FishAudioModal = ({
|
||||
const { t } = useTranslate('setting');
|
||||
const { t: tc } = useCommonTranslation();
|
||||
const { buildModelTypeOptions } = useBuildModelTypeOptions();
|
||||
const { instanceNameSet } = useFetchInstanceNameSet(llmFactory);
|
||||
|
||||
const fields: FormFieldConfig[] = [
|
||||
{
|
||||
name: 'model_type',
|
||||
label: t('modelType'),
|
||||
type: FormFieldType.Select,
|
||||
required: true,
|
||||
options: buildModelTypeOptions(['tts']),
|
||||
defaultValue: 'tts',
|
||||
validation: { message: t('modelTypeMessage') },
|
||||
},
|
||||
{
|
||||
name: 'llm_name',
|
||||
label: t('modelName'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('FishAudioModelNameMessage'),
|
||||
validation: { message: t('FishAudioModelNameMessage') },
|
||||
},
|
||||
{
|
||||
name: 'fish_audio_ak',
|
||||
label: t('addFishAudioAK'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('FishAudioAKMessage'),
|
||||
validation: { message: t('FishAudioAKMessage') },
|
||||
},
|
||||
{
|
||||
name: 'fish_audio_refid',
|
||||
label: t('addFishAudioRefID'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('FishAudioRefIDMessage'),
|
||||
validation: { message: t('FishAudioRefIDMessage') },
|
||||
},
|
||||
{
|
||||
name: 'max_tokens',
|
||||
label: t('maxTokens'),
|
||||
type: FormFieldType.Number,
|
||||
required: true,
|
||||
placeholder: t('maxTokensTip'),
|
||||
validation: {
|
||||
min: 0,
|
||||
message: t('maxTokensInvalidMessage'),
|
||||
const hideWhenInstanceExists = useHideWhenInstanceExists(instanceNameSet);
|
||||
|
||||
const fields: FormFieldConfig[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
name: 'instance_name',
|
||||
label: t('instanceName'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('instanceNameMessage'),
|
||||
tooltip: t('instanceNameTip'),
|
||||
validation: { message: t('instanceNameMessage') },
|
||||
},
|
||||
},
|
||||
];
|
||||
{
|
||||
name: 'model_type',
|
||||
label: t('modelType'),
|
||||
type: FormFieldType.MultiSelect,
|
||||
required: true,
|
||||
options: buildModelTypeOptions(['tts']),
|
||||
defaultValue: ['tts'],
|
||||
},
|
||||
{
|
||||
name: 'llm_name',
|
||||
label: t('modelName'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('FishAudioModelNameMessage'),
|
||||
validation: { message: t('FishAudioModelNameMessage') },
|
||||
},
|
||||
{
|
||||
name: 'fish_audio_ak',
|
||||
label: t('addFishAudioAK'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('FishAudioAKMessage'),
|
||||
validation: { message: t('FishAudioAKMessage') },
|
||||
shouldRender: hideWhenInstanceExists,
|
||||
},
|
||||
{
|
||||
name: 'fish_audio_refid',
|
||||
label: t('addFishAudioRefID'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('FishAudioRefIDMessage'),
|
||||
validation: { message: t('FishAudioRefIDMessage') },
|
||||
shouldRender: hideWhenInstanceExists,
|
||||
},
|
||||
{
|
||||
name: 'max_tokens',
|
||||
label: t('maxTokens'),
|
||||
type: FormFieldType.Number,
|
||||
required: true,
|
||||
placeholder: t('maxTokensTip'),
|
||||
validation: {
|
||||
min: 0,
|
||||
message: t('maxTokensInvalidMessage'),
|
||||
},
|
||||
},
|
||||
],
|
||||
[t, buildModelTypeOptions, hideWhenInstanceExists],
|
||||
);
|
||||
|
||||
const handleOk = async (values?: FieldValues) => {
|
||||
if (!values) return;
|
||||
|
||||
const data: Record<string, any> = {
|
||||
const data: IAddProviderInstanceRequestBody & {
|
||||
fish_audio_ak: string;
|
||||
fish_audio_refid: string;
|
||||
} = {
|
||||
instance_name: values.instance_name as string,
|
||||
llm_factory: llmFactory,
|
||||
llm_name: values.llm_name as string,
|
||||
model_type: values.model_type,
|
||||
@@ -90,8 +114,7 @@ const FishAudioModal = ({
|
||||
max_tokens: values.max_tokens as number,
|
||||
};
|
||||
|
||||
console.info(data);
|
||||
await onOk?.(data as IAddLlmRequestBody);
|
||||
await onOk?.(data);
|
||||
};
|
||||
|
||||
const handleVerify = useCallback(
|
||||
@@ -114,7 +137,11 @@ const FishAudioModal = ({
|
||||
<DynamicForm.Root
|
||||
fields={fields}
|
||||
onSubmit={(data) => console.log(data)}
|
||||
defaultValues={{ model_type: 'tts' }}
|
||||
defaultValues={{
|
||||
instance_name: '',
|
||||
model_type: ['tts'],
|
||||
max_tokens: 8192,
|
||||
}}
|
||||
labelClassName="font-normal"
|
||||
>
|
||||
{onVerify && (
|
||||
|
||||
@@ -7,9 +7,13 @@ import { Modal } from '@/components/ui/modal/modal';
|
||||
import { useCommonTranslation, useTranslate } from '@/hooks/common-hooks';
|
||||
import { useBuildModelTypeOptions } from '@/hooks/logic-hooks/use-build-options';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { VerifyResult } from '@/pages/user-setting/setting-model/hooks';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { IAddProviderInstanceRequestBody } from '@/interfaces/request/llm';
|
||||
import {
|
||||
useFetchInstanceNameSet,
|
||||
useHideWhenInstanceExists,
|
||||
VerifyResult,
|
||||
} from '@/pages/user-setting/setting-model/hooks';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { FieldValues } from 'react-hook-form';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
import VerifyButton from '../../modal/verify-button';
|
||||
@@ -21,7 +25,7 @@ const GoogleModal = ({
|
||||
onVerify,
|
||||
loading,
|
||||
llmFactory,
|
||||
}: IModalProps<IAddLlmRequestBody> & {
|
||||
}: IModalProps<IAddProviderInstanceRequestBody> & {
|
||||
llmFactory: string;
|
||||
onVerify?: (
|
||||
postBody: any,
|
||||
@@ -30,85 +34,101 @@ const GoogleModal = ({
|
||||
const { t } = useTranslate('setting');
|
||||
const { t: tc } = useCommonTranslation();
|
||||
const { buildModelTypeOptions } = useBuildModelTypeOptions();
|
||||
const { instanceNameSet } = useFetchInstanceNameSet(llmFactory);
|
||||
|
||||
const fields: FormFieldConfig[] = [
|
||||
{
|
||||
name: 'model_type',
|
||||
label: t('modelType'),
|
||||
type: FormFieldType.Select,
|
||||
required: true,
|
||||
options: buildModelTypeOptions(['chat', 'image2text']),
|
||||
defaultValue: 'chat',
|
||||
validation: {
|
||||
message: t('modelTypeMessage'),
|
||||
const hideWhenInstanceExists = useHideWhenInstanceExists(instanceNameSet);
|
||||
|
||||
const fields: FormFieldConfig[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
name: 'instance_name',
|
||||
label: t('instanceName'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('instanceNameMessage'),
|
||||
tooltip: t('instanceNameTip'),
|
||||
validation: { message: t('instanceNameMessage') },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'llm_name',
|
||||
label: t('modelID'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('GoogleModelIDMessage'),
|
||||
validation: {
|
||||
message: t('GoogleModelIDMessage'),
|
||||
{
|
||||
name: 'model_type',
|
||||
label: t('modelType'),
|
||||
type: FormFieldType.MultiSelect,
|
||||
required: true,
|
||||
options: buildModelTypeOptions(['chat', 'image2text']),
|
||||
defaultValue: ['chat'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'google_project_id',
|
||||
label: t('addGoogleProjectID'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('GoogleProjectIDMessage'),
|
||||
validation: {
|
||||
message: t('GoogleProjectIDMessage'),
|
||||
{
|
||||
name: 'llm_name',
|
||||
label: t('modelID'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('GoogleModelIDMessage'),
|
||||
validation: {
|
||||
message: t('GoogleModelIDMessage'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'google_region',
|
||||
label: t('addGoogleRegion'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('GoogleRegionMessage'),
|
||||
validation: {
|
||||
message: t('GoogleRegionMessage'),
|
||||
{
|
||||
name: 'google_project_id',
|
||||
label: t('addGoogleProjectID'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('GoogleProjectIDMessage'),
|
||||
validation: {
|
||||
message: t('GoogleProjectIDMessage'),
|
||||
},
|
||||
shouldRender: hideWhenInstanceExists,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'google_service_account_key',
|
||||
label: t('addGoogleServiceAccountKey'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('GoogleServiceAccountKeyMessage'),
|
||||
validation: {
|
||||
message: t('GoogleServiceAccountKeyMessage'),
|
||||
{
|
||||
name: 'google_region',
|
||||
label: t('addGoogleRegion'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('GoogleRegionMessage'),
|
||||
validation: {
|
||||
message: t('GoogleRegionMessage'),
|
||||
},
|
||||
shouldRender: hideWhenInstanceExists,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'max_tokens',
|
||||
label: t('maxTokens'),
|
||||
type: FormFieldType.Number,
|
||||
required: true,
|
||||
placeholder: t('maxTokensTip'),
|
||||
validation: {
|
||||
min: 0,
|
||||
message: t('maxTokensMinMessage'),
|
||||
{
|
||||
name: 'google_service_account_key',
|
||||
label: t('addGoogleServiceAccountKey'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('GoogleServiceAccountKeyMessage'),
|
||||
validation: {
|
||||
message: t('GoogleServiceAccountKeyMessage'),
|
||||
},
|
||||
shouldRender: hideWhenInstanceExists,
|
||||
},
|
||||
customValidate: (value: any) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return t('maxTokensMessage');
|
||||
}
|
||||
if (value < 0) {
|
||||
return t('maxTokensMinMessage');
|
||||
}
|
||||
return true;
|
||||
{
|
||||
name: 'max_tokens',
|
||||
label: t('maxTokens'),
|
||||
type: FormFieldType.Number,
|
||||
required: true,
|
||||
placeholder: t('maxTokensTip'),
|
||||
validation: {
|
||||
min: 0,
|
||||
message: t('maxTokensMinMessage'),
|
||||
},
|
||||
customValidate: (value: any) => {
|
||||
if (value === undefined || value === null || value === '') {
|
||||
return t('maxTokensMessage');
|
||||
}
|
||||
if (value < 0) {
|
||||
return t('maxTokensMinMessage');
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
],
|
||||
[t, buildModelTypeOptions, hideWhenInstanceExists],
|
||||
);
|
||||
|
||||
const handleOk = async (values?: FieldValues) => {
|
||||
if (!values) return;
|
||||
|
||||
const data = {
|
||||
instance_name: values.instance_name as string,
|
||||
llm_factory: llmFactory,
|
||||
model_type: values.model_type,
|
||||
llm_name: values.llm_name,
|
||||
@@ -116,7 +136,7 @@ const GoogleModal = ({
|
||||
google_region: values.google_region,
|
||||
google_service_account_key: values.google_service_account_key,
|
||||
max_tokens: values.max_tokens,
|
||||
} as IAddLlmRequestBody;
|
||||
} as IAddProviderInstanceRequestBody;
|
||||
|
||||
await onOk?.(data);
|
||||
};
|
||||
@@ -150,7 +170,9 @@ const GoogleModal = ({
|
||||
}}
|
||||
defaultValues={
|
||||
{
|
||||
model_type: 'chat',
|
||||
instance_name: '',
|
||||
model_type: ['chat'],
|
||||
max_tokens: 8192,
|
||||
} as FieldValues
|
||||
}
|
||||
labelClassName="font-normal"
|
||||
|
||||
@@ -25,6 +25,9 @@ import { LLMHeader } from '../../components/llm-header';
|
||||
import VerifyButton from '../verify-button';
|
||||
|
||||
const FormSchema = z.object({
|
||||
instance_name: z.string().min(1, {
|
||||
message: t('setting.instanceNameMessage'),
|
||||
}),
|
||||
llm_name: z.string().min(1, {
|
||||
message: t('setting.mineru.modelNameRequired'),
|
||||
}),
|
||||
@@ -71,6 +74,7 @@ const MinerUModal = ({
|
||||
const form = useForm<MinerUFormValues>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
instance_name: '',
|
||||
mineru_backend: 'pipeline',
|
||||
mineru_delete_output: true,
|
||||
},
|
||||
@@ -102,6 +106,14 @@ const MinerUModal = ({
|
||||
className="space-y-6"
|
||||
id="mineru-form"
|
||||
>
|
||||
<RAGFlowFormItem
|
||||
name="instance_name"
|
||||
label={t('setting.instanceName')}
|
||||
tooltip={t('setting.instanceNameTip')}
|
||||
required
|
||||
>
|
||||
<Input placeholder={t('setting.instanceNameMessage')} />
|
||||
</RAGFlowFormItem>
|
||||
<RAGFlowFormItem
|
||||
name="llm_name"
|
||||
label={t('setting.modelName')}
|
||||
|
||||
@@ -7,9 +7,13 @@ import { Modal } from '@/components/ui/modal/modal';
|
||||
import { useCommonTranslation, useTranslate } from '@/hooks/common-hooks';
|
||||
import { useBuildModelTypeOptions } from '@/hooks/logic-hooks/use-build-options';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { VerifyResult } from '@/pages/user-setting/setting-model/hooks';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { IAddProviderInstanceRequestBody } from '@/interfaces/request/llm';
|
||||
import {
|
||||
useFetchInstanceNameSet,
|
||||
useHideWhenInstanceExists,
|
||||
VerifyResult,
|
||||
} from '@/pages/user-setting/setting-model/hooks';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { FieldValues } from 'react-hook-form';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
import VerifyButton from '../../modal/verify-button';
|
||||
@@ -21,7 +25,7 @@ const TencentCloudModal = ({
|
||||
onVerify,
|
||||
loading,
|
||||
llmFactory,
|
||||
}: IModalProps<Omit<IAddLlmRequestBody, 'max_tokens'>> & {
|
||||
}: IModalProps<Omit<IAddProviderInstanceRequestBody, 'max_tokens'>> & {
|
||||
llmFactory: string;
|
||||
onVerify?: (
|
||||
postBody: any,
|
||||
@@ -30,87 +34,100 @@ const TencentCloudModal = ({
|
||||
const { t } = useTranslate('setting');
|
||||
const { t: tc } = useCommonTranslation();
|
||||
const { buildModelTypeOptions } = useBuildModelTypeOptions();
|
||||
const { instanceNameSet } = useFetchInstanceNameSet(llmFactory);
|
||||
|
||||
const fields: FormFieldConfig[] = [
|
||||
{
|
||||
name: 'model_type',
|
||||
label: t('modelType'),
|
||||
type: FormFieldType.Select,
|
||||
required: true,
|
||||
options: buildModelTypeOptions(['speech2text']),
|
||||
defaultValue: 'speech2text',
|
||||
validation: {
|
||||
message: t('modelTypeMessage'),
|
||||
const hideWhenInstanceExists = useHideWhenInstanceExists(instanceNameSet);
|
||||
|
||||
const fields: FormFieldConfig[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
name: 'instance_name',
|
||||
label: t('instanceName'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('instanceNameMessage'),
|
||||
tooltip: t('instanceNameTip'),
|
||||
validation: { message: t('instanceNameMessage') },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'llm_name',
|
||||
label: t('modelName'),
|
||||
type: FormFieldType.Select,
|
||||
required: true,
|
||||
options: [
|
||||
{ label: '16k_zh', value: '16k_zh' },
|
||||
{ label: '16k_zh_large', value: '16k_zh_large' },
|
||||
{ label: '16k_multi_lang', value: '16k_multi_lang' },
|
||||
{ label: '16k_zh_dialect', value: '16k_zh_dialect' },
|
||||
{ label: '16k_en', value: '16k_en' },
|
||||
{ label: '16k_yue', value: '16k_yue' },
|
||||
{ label: '16k_zh-PY', value: '16k_zh-PY' },
|
||||
{ label: '16k_ja', value: '16k_ja' },
|
||||
{ label: '16k_ko', value: '16k_ko' },
|
||||
{ label: '16k_vi', value: '16k_vi' },
|
||||
{ label: '16k_ms', value: '16k_ms' },
|
||||
{ label: '16k_id', value: '16k_id' },
|
||||
{ label: '16k_fil', value: '16k_fil' },
|
||||
{ label: '16k_th', value: '16k_th' },
|
||||
{ label: '16k_pt', value: '16k_pt' },
|
||||
{ label: '16k_tr', value: '16k_tr' },
|
||||
{ label: '16k_ar', value: '16k_ar' },
|
||||
{ label: '16k_es', value: '16k_es' },
|
||||
{ label: '16k_hi', value: '16k_hi' },
|
||||
{ label: '16k_fr', value: '16k_fr' },
|
||||
{ label: '16k_zh_medical', value: '16k_zh_medical' },
|
||||
{ label: '16k_de', value: '16k_de' },
|
||||
],
|
||||
defaultValue: '16k_zh',
|
||||
validation: {
|
||||
message: t('SparkModelNameMessage'),
|
||||
{
|
||||
name: 'model_type',
|
||||
label: t('modelType'),
|
||||
type: FormFieldType.MultiSelect,
|
||||
required: true,
|
||||
options: buildModelTypeOptions(['speech2text']),
|
||||
defaultValue: ['speech2text'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'TencentCloud_sid',
|
||||
label: t('addTencentCloudSID'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('TencentCloudSIDMessage'),
|
||||
validation: {
|
||||
message: t('TencentCloudSIDMessage'),
|
||||
{
|
||||
name: 'llm_name',
|
||||
label: t('modelName'),
|
||||
type: FormFieldType.Select,
|
||||
required: true,
|
||||
options: [
|
||||
{ label: '16k_zh', value: '16k_zh' },
|
||||
{ label: '16k_zh_large', value: '16k_zh_large' },
|
||||
{ label: '16k_multi_lang', value: '16k_multi_lang' },
|
||||
{ label: '16k_zh_dialect', value: '16k_zh_dialect' },
|
||||
{ label: '16k_en', value: '16k_en' },
|
||||
{ label: '16k_yue', value: '16k_yue' },
|
||||
{ label: '16k_zh-PY', value: '16k_zh-PY' },
|
||||
{ label: '16k_ja', value: '16k_ja' },
|
||||
{ label: '16k_ko', value: '16k_ko' },
|
||||
{ label: '16k_vi', value: '16k_vi' },
|
||||
{ label: '16k_ms', value: '16k_ms' },
|
||||
{ label: '16k_id', value: '16k_id' },
|
||||
{ label: '16k_fil', value: '16k_fil' },
|
||||
{ label: '16k_th', value: '16k_th' },
|
||||
{ label: '16k_pt', value: '16k_pt' },
|
||||
{ label: '16k_tr', value: '16k_tr' },
|
||||
{ label: '16k_ar', value: '16k_ar' },
|
||||
{ label: '16k_es', value: '16k_es' },
|
||||
{ label: '16k_hi', value: '16k_hi' },
|
||||
{ label: '16k_fr', value: '16k_fr' },
|
||||
{ label: '16k_zh_medical', value: '16k_zh_medical' },
|
||||
{ label: '16k_de', value: '16k_de' },
|
||||
],
|
||||
defaultValue: '16k_zh',
|
||||
validation: {
|
||||
message: t('SparkModelNameMessage'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'TencentCloud_sk',
|
||||
label: t('addTencentCloudSK'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('TencentCloudSKMessage'),
|
||||
validation: {
|
||||
message: t('TencentCloudSKMessage'),
|
||||
{
|
||||
name: 'TencentCloud_sid',
|
||||
label: t('addTencentCloudSID'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('TencentCloudSIDMessage'),
|
||||
validation: {
|
||||
message: t('TencentCloudSIDMessage'),
|
||||
},
|
||||
shouldRender: hideWhenInstanceExists,
|
||||
},
|
||||
},
|
||||
];
|
||||
{
|
||||
name: 'TencentCloud_sk',
|
||||
label: t('addTencentCloudSK'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('TencentCloudSKMessage'),
|
||||
validation: {
|
||||
message: t('TencentCloudSKMessage'),
|
||||
},
|
||||
shouldRender: hideWhenInstanceExists,
|
||||
},
|
||||
],
|
||||
[t, buildModelTypeOptions, hideWhenInstanceExists],
|
||||
);
|
||||
|
||||
const handleOk = async (values?: FieldValues) => {
|
||||
if (!values) return;
|
||||
|
||||
const modelType = values.model_type;
|
||||
|
||||
const data = {
|
||||
model_type: modelType,
|
||||
instance_name: values.instance_name as string,
|
||||
model_type: values.model_type,
|
||||
llm_name: values.llm_name as string,
|
||||
TencentCloud_sid: values.TencentCloud_sid as string,
|
||||
TencentCloud_sk: values.TencentCloud_sk as string,
|
||||
llm_factory: llmFactory,
|
||||
} as Omit<IAddLlmRequestBody, 'max_tokens'>;
|
||||
} as Omit<IAddProviderInstanceRequestBody, 'max_tokens'>;
|
||||
|
||||
await onOk?.(data);
|
||||
};
|
||||
@@ -143,7 +160,8 @@ const TencentCloudModal = ({
|
||||
onSubmit={() => {}}
|
||||
defaultValues={
|
||||
{
|
||||
model_type: 'speech2text',
|
||||
instance_name: '',
|
||||
model_type: ['speech2text'],
|
||||
llm_name: '16k_zh',
|
||||
} as FieldValues
|
||||
}
|
||||
|
||||
@@ -9,8 +9,12 @@ import { LLMFactory } from '@/constants/llm';
|
||||
import { useCommonTranslation, useTranslate } from '@/hooks/common-hooks';
|
||||
import { useBuildModelTypeOptions } from '@/hooks/logic-hooks/use-build-options';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { VerifyResult } from '@/pages/user-setting/setting-model/hooks';
|
||||
import { IAddProviderInstanceRequestBody } from '@/interfaces/request/llm';
|
||||
import {
|
||||
useFetchInstanceNameSet,
|
||||
useHideWhenInstanceExists,
|
||||
VerifyResult,
|
||||
} from '@/pages/user-setting/setting-model/hooks';
|
||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||
import { FieldValues } from 'react-hook-form';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
@@ -47,7 +51,9 @@ const OllamaModal = ({
|
||||
llmFactory,
|
||||
editMode = false,
|
||||
initialValues,
|
||||
}: IModalProps<Partial<IAddLlmRequestBody> & { provider_order?: string }> & {
|
||||
}: IModalProps<
|
||||
Partial<IAddProviderInstanceRequestBody> & { provider_order?: string }
|
||||
> & {
|
||||
llmFactory: string;
|
||||
editMode?: boolean;
|
||||
onVerify?: (
|
||||
@@ -58,6 +64,9 @@ const OllamaModal = ({
|
||||
const { t: tc } = useCommonTranslation();
|
||||
const { buildModelTypeOptions } = useBuildModelTypeOptions();
|
||||
const formRef = useRef<DynamicFormRef>(null);
|
||||
const { instanceNameSet } = useFetchInstanceNameSet(llmFactory);
|
||||
|
||||
const hideWhenInstanceExists = useHideWhenInstanceExists(instanceNameSet);
|
||||
|
||||
const optionsMap: Partial<
|
||||
Record<LLMFactory, { label: string; value: string }[]>
|
||||
@@ -118,15 +127,23 @@ const OllamaModal = ({
|
||||
const defaultToolCallEnabled = initialValues?.is_tools ?? false;
|
||||
|
||||
const baseFields: FormFieldConfig[] = [
|
||||
{
|
||||
name: 'instance_name',
|
||||
label: t('instanceName'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('instanceNameMessage'),
|
||||
tooltip: t('instanceNameTip'),
|
||||
validation: {
|
||||
message: t('instanceNameMessage'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'model_type',
|
||||
label: t('modelType'),
|
||||
type: FormFieldType.Select,
|
||||
type: FormFieldType.MultiSelect,
|
||||
required: true,
|
||||
options: getOptions(llmFactory),
|
||||
validation: {
|
||||
message: t('modelTypeMessage'),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'llm_name',
|
||||
@@ -147,6 +164,7 @@ const OllamaModal = ({
|
||||
validation: {
|
||||
message: t('baseUrlNameMessage'),
|
||||
},
|
||||
shouldRender: hideWhenInstanceExists,
|
||||
},
|
||||
{
|
||||
name: 'api_key',
|
||||
@@ -154,6 +172,7 @@ const OllamaModal = ({
|
||||
type: FormFieldType.Text,
|
||||
required: false,
|
||||
placeholder: t('apiKeyMessage'),
|
||||
shouldRender: hideWhenInstanceExists,
|
||||
},
|
||||
{
|
||||
name: 'max_tokens',
|
||||
@@ -186,6 +205,9 @@ const OllamaModal = ({
|
||||
dependencies: ['model_type'],
|
||||
shouldRender: (formValues: any) => {
|
||||
const modelType = formValues?.model_type;
|
||||
if (Array.isArray(modelType)) {
|
||||
return modelType.includes('chat') || modelType.includes('image2text');
|
||||
}
|
||||
return modelType === 'chat' || modelType === 'image2text';
|
||||
},
|
||||
tooltip: t('enableToolCallTip'),
|
||||
@@ -212,18 +234,25 @@ const OllamaModal = ({
|
||||
required: false,
|
||||
dependencies: ['model_type'],
|
||||
shouldRender: (formValues: any) => {
|
||||
return formValues?.model_type === 'chat';
|
||||
const modelType = formValues?.model_type;
|
||||
if (Array.isArray(modelType)) {
|
||||
return modelType.includes('chat');
|
||||
}
|
||||
return modelType === 'chat';
|
||||
},
|
||||
});
|
||||
|
||||
return baseFields;
|
||||
}, [llmFactory, t]);
|
||||
}, [llmFactory, t, hideWhenInstanceExists, initialValues?.is_tools]);
|
||||
|
||||
const defaultValues: FieldValues = useMemo(() => {
|
||||
if (editMode && initialValues) {
|
||||
return {
|
||||
instance_name: initialValues.instance_name || '',
|
||||
llm_name: initialValues.llm_name || '',
|
||||
model_type: initialValues.model_type || 'chat',
|
||||
model_type: initialValues.model_type
|
||||
? initialValues.model_type.split(',').filter(Boolean)
|
||||
: ['chat'],
|
||||
api_base: initialValues.api_base || '',
|
||||
max_tokens: initialValues.max_tokens || 8192,
|
||||
api_key: '',
|
||||
@@ -233,34 +262,42 @@ const OllamaModal = ({
|
||||
};
|
||||
}
|
||||
return {
|
||||
model_type:
|
||||
instance_name: '',
|
||||
model_type: [
|
||||
llmFactory === LLMFactory.Ollama || llmFactory === LLMFactory.VLLM
|
||||
? 'chat'
|
||||
: llmFactory in optionsMap
|
||||
? optionsMap[llmFactory as LLMFactory]?.at(0)?.value
|
||||
: 'embedding',
|
||||
],
|
||||
vision: false,
|
||||
is_tools: false,
|
||||
max_tokens: 8192,
|
||||
};
|
||||
}, [editMode, initialValues, llmFactory]);
|
||||
|
||||
const handleOk = async (values?: FieldValues) => {
|
||||
if (!values) return;
|
||||
|
||||
const modelType =
|
||||
values.model_type === 'chat' && values.vision
|
||||
? 'image2text'
|
||||
: values.model_type;
|
||||
const supportsToolCall = modelType === 'chat' || modelType === 'image2text';
|
||||
const modelType = values.model_type.map((t: string) =>
|
||||
t === 'chat' && values.vision ? 'image2text' : t,
|
||||
);
|
||||
const modelTypeArray: string[] = Array.isArray(values.model_type)
|
||||
? values.model_type
|
||||
: [values.model_type];
|
||||
const supportsToolCall =
|
||||
modelTypeArray.includes('chat') || modelTypeArray.includes('image2text');
|
||||
|
||||
const data: IAddLlmRequestBody & { provider_order?: string } = {
|
||||
llm_factory: llmFactory,
|
||||
llm_name: values.llm_name as string,
|
||||
model_type: modelType,
|
||||
api_base: values.api_base as string,
|
||||
api_key: values.api_key as string,
|
||||
max_tokens: values.max_tokens as number,
|
||||
};
|
||||
const data: IAddProviderInstanceRequestBody & { provider_order?: string } =
|
||||
{
|
||||
instance_name: values.instance_name as string,
|
||||
llm_factory: llmFactory,
|
||||
llm_name: values.llm_name as string,
|
||||
model_type: modelType,
|
||||
api_base: values.api_base as string,
|
||||
api_key: values.api_key as string,
|
||||
max_tokens: values.max_tokens as number,
|
||||
};
|
||||
if (supportsToolCall) {
|
||||
data.is_tools = Boolean(values.is_tools);
|
||||
}
|
||||
@@ -275,13 +312,11 @@ const OllamaModal = ({
|
||||
|
||||
const verifyParamsFunc = useCallback(() => {
|
||||
const values = formRef.current?.getValues();
|
||||
const modelType =
|
||||
values.model_type === 'chat' && values.vision
|
||||
? 'image2text'
|
||||
: values.model_type;
|
||||
return {
|
||||
llm_factory: llmFactory,
|
||||
model_type: modelType,
|
||||
model_type: values.model_type.map((t: string) =>
|
||||
t === 'chat' && values.vision ? 'image2text' : t,
|
||||
),
|
||||
};
|
||||
}, [llmFactory]);
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import { LLMHeader } from '../../components/llm-header';
|
||||
import VerifyButton from '../verify-button';
|
||||
|
||||
export type OpenDataLoaderFormValues = {
|
||||
instance_name: string;
|
||||
llm_name: string;
|
||||
opendataloader_apiserver: string;
|
||||
opendataloader_api_key?: string;
|
||||
@@ -47,6 +48,9 @@ const OpenDataLoaderModal = ({
|
||||
const FormSchema = useMemo(
|
||||
() =>
|
||||
z.object({
|
||||
instance_name: z.string().min(1, {
|
||||
message: t('setting.instanceNameMessage'),
|
||||
}),
|
||||
llm_name: z.string().min(1, {
|
||||
message: t('setting.modelNameMessage'),
|
||||
}),
|
||||
@@ -61,6 +65,7 @@ const OpenDataLoaderModal = ({
|
||||
const form = useForm<OpenDataLoaderFormValues>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
instance_name: '',
|
||||
opendataloader_apiserver: '',
|
||||
opendataloader_api_key: '',
|
||||
},
|
||||
@@ -87,6 +92,14 @@ const OpenDataLoaderModal = ({
|
||||
className="space-y-6"
|
||||
id="opendataloader-form"
|
||||
>
|
||||
<RAGFlowFormItem
|
||||
name="instance_name"
|
||||
label={t('setting.instanceName')}
|
||||
tooltip={t('setting.instanceNameTip')}
|
||||
required
|
||||
>
|
||||
<Input placeholder={t('setting.instanceNameMessage')} />
|
||||
</RAGFlowFormItem>
|
||||
<RAGFlowFormItem
|
||||
name="llm_name"
|
||||
label={t('setting.modelName')}
|
||||
|
||||
@@ -22,6 +22,9 @@ import { LLMHeader } from '../../components/llm-header';
|
||||
import VerifyButton from '../verify-button';
|
||||
|
||||
const FormSchema = z.object({
|
||||
instance_name: z.string().min(1, {
|
||||
message: t('setting.instanceNameMessage'),
|
||||
}),
|
||||
llm_name: z.string().min(1, {
|
||||
message: t('setting.paddleocr.modelNameRequired'),
|
||||
}),
|
||||
@@ -63,6 +66,7 @@ const PaddleOCRModal = ({
|
||||
const form = useForm<PaddleOCRFormValues>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
instance_name: '',
|
||||
paddleocr_algorithm: 'PaddleOCR-VL',
|
||||
},
|
||||
});
|
||||
@@ -88,6 +92,14 @@ const PaddleOCRModal = ({
|
||||
className="space-y-6"
|
||||
id="paddleocr-form"
|
||||
>
|
||||
<RAGFlowFormItem
|
||||
name="instance_name"
|
||||
label={t('setting.instanceName')}
|
||||
tooltip={t('setting.instanceNameTip')}
|
||||
required
|
||||
>
|
||||
<Input placeholder={t('setting.instanceNameMessage')} />
|
||||
</RAGFlowFormItem>
|
||||
<RAGFlowFormItem
|
||||
name="llm_name"
|
||||
label={t('setting.modelName')}
|
||||
|
||||
@@ -8,10 +8,13 @@ import { Modal } from '@/components/ui/modal/modal';
|
||||
import { useCommonTranslation, useTranslate } from '@/hooks/common-hooks';
|
||||
import { useBuildModelTypeOptions } from '@/hooks/logic-hooks/use-build-options';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { VerifyResult } from '@/pages/user-setting/setting-model/hooks';
|
||||
import omit from 'lodash/omit';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { IAddProviderInstanceRequestBody } from '@/interfaces/request/llm';
|
||||
import {
|
||||
useFetchInstanceNameSet,
|
||||
useHideWhenInstanceExists,
|
||||
VerifyResult,
|
||||
} from '@/pages/user-setting/setting-model/hooks';
|
||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||
import { FieldValues } from 'react-hook-form';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
import VerifyButton from '../../modal/verify-button';
|
||||
@@ -23,7 +26,7 @@ const SparkModal = ({
|
||||
onVerify,
|
||||
loading,
|
||||
llmFactory,
|
||||
}: IModalProps<IAddLlmRequestBody> & {
|
||||
}: IModalProps<IAddProviderInstanceRequestBody> & {
|
||||
llmFactory: string;
|
||||
onVerify?: (
|
||||
postBody: any,
|
||||
@@ -33,120 +36,140 @@ const SparkModal = ({
|
||||
const { t: tc } = useCommonTranslation();
|
||||
const { buildModelTypeOptions } = useBuildModelTypeOptions();
|
||||
const formRef = useRef<DynamicFormRef>(null);
|
||||
const fields: FormFieldConfig[] = [
|
||||
{
|
||||
name: 'model_type',
|
||||
label: t('modelType'),
|
||||
type: FormFieldType.Select,
|
||||
required: true,
|
||||
options: buildModelTypeOptions(['chat', 'tts']),
|
||||
defaultValue: 'chat',
|
||||
validation: {
|
||||
message: t('modelTypeMessage'),
|
||||
const { instanceNameSet } = useFetchInstanceNameSet(llmFactory);
|
||||
|
||||
const hideWhenInstanceExists = useHideWhenInstanceExists(instanceNameSet);
|
||||
|
||||
const fields: FormFieldConfig[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
name: 'instance_name',
|
||||
label: t('instanceName'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('instanceNameMessage'),
|
||||
tooltip: t('instanceNameTip'),
|
||||
validation: { message: t('instanceNameMessage') },
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'llm_name',
|
||||
label: t('modelName'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('modelNameMessage'),
|
||||
validation: {
|
||||
message: t('SparkModelNameMessage'),
|
||||
{
|
||||
name: 'model_type',
|
||||
label: t('modelType'),
|
||||
type: FormFieldType.MultiSelect,
|
||||
required: true,
|
||||
options: buildModelTypeOptions(['chat', 'tts']),
|
||||
defaultValue: ['chat'],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'spark_api_password',
|
||||
label: t('addSparkAPIPassword'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('SparkAPIPasswordMessage'),
|
||||
validation: {
|
||||
message: t('SparkAPIPasswordMessage'),
|
||||
{
|
||||
name: 'llm_name',
|
||||
label: t('modelName'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('modelNameMessage'),
|
||||
validation: {
|
||||
message: t('SparkModelNameMessage'),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'spark_app_id',
|
||||
label: t('addSparkAPPID'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('SparkAPPIDMessage'),
|
||||
validation: {
|
||||
message: t('SparkAPPIDMessage'),
|
||||
{
|
||||
name: 'spark_api_password',
|
||||
label: t('addSparkAPIPassword'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('SparkAPIPasswordMessage'),
|
||||
validation: {
|
||||
message: t('SparkAPIPasswordMessage'),
|
||||
},
|
||||
shouldRender: hideWhenInstanceExists,
|
||||
},
|
||||
dependencies: ['model_type'],
|
||||
shouldRender: (formValues: any) => {
|
||||
return formValues?.model_type === 'tts';
|
||||
{
|
||||
name: 'spark_app_id',
|
||||
label: t('addSparkAPPID'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('SparkAPPIDMessage'),
|
||||
validation: {
|
||||
message: t('SparkAPPIDMessage'),
|
||||
},
|
||||
dependencies: ['model_type', 'instance_name'],
|
||||
shouldRender: (formValues: any) => {
|
||||
if (!hideWhenInstanceExists(formValues)) return false;
|
||||
const modelType = formValues?.model_type;
|
||||
if (Array.isArray(modelType)) {
|
||||
return modelType.includes('tts');
|
||||
}
|
||||
return modelType === 'tts';
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'spark_api_secret',
|
||||
label: t('addSparkAPISecret'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('SparkAPISecretMessage'),
|
||||
validation: {
|
||||
message: t('SparkAPISecretMessage'),
|
||||
{
|
||||
name: 'spark_api_secret',
|
||||
label: t('addSparkAPISecret'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('SparkAPISecretMessage'),
|
||||
validation: {
|
||||
message: t('SparkAPISecretMessage'),
|
||||
},
|
||||
dependencies: ['model_type', 'instance_name'],
|
||||
shouldRender: (formValues: any) => {
|
||||
if (!hideWhenInstanceExists(formValues)) return false;
|
||||
const modelType = formValues?.model_type;
|
||||
if (Array.isArray(modelType)) {
|
||||
return modelType.includes('tts');
|
||||
}
|
||||
return modelType === 'tts';
|
||||
},
|
||||
},
|
||||
dependencies: ['model_type'],
|
||||
shouldRender: (formValues: any) => {
|
||||
return formValues?.model_type === 'tts';
|
||||
{
|
||||
name: 'spark_api_key',
|
||||
label: t('addSparkAPIKey'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('SparkAPIKeyMessage'),
|
||||
validation: {
|
||||
message: t('SparkAPIKeyMessage'),
|
||||
},
|
||||
dependencies: ['model_type', 'instance_name'],
|
||||
shouldRender: (formValues: any) => {
|
||||
if (!hideWhenInstanceExists(formValues)) return false;
|
||||
const modelType = formValues?.model_type;
|
||||
if (Array.isArray(modelType)) {
|
||||
return modelType.includes('tts');
|
||||
}
|
||||
return modelType === 'tts';
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'spark_api_key',
|
||||
label: t('addSparkAPIKey'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('SparkAPIKeyMessage'),
|
||||
validation: {
|
||||
message: t('SparkAPIKeyMessage'),
|
||||
{
|
||||
name: 'max_tokens',
|
||||
label: t('maxTokens'),
|
||||
type: FormFieldType.Number,
|
||||
required: true,
|
||||
placeholder: t('maxTokensTip'),
|
||||
validation: {
|
||||
min: 0,
|
||||
message: t('maxTokensInvalidMessage'),
|
||||
},
|
||||
},
|
||||
dependencies: ['model_type'],
|
||||
shouldRender: (formValues: any) => {
|
||||
return formValues?.model_type === 'tts';
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'max_tokens',
|
||||
label: t('maxTokens'),
|
||||
type: FormFieldType.Number,
|
||||
required: true,
|
||||
placeholder: t('maxTokensTip'),
|
||||
validation: {
|
||||
min: 0,
|
||||
message: t('maxTokensInvalidMessage'),
|
||||
},
|
||||
},
|
||||
];
|
||||
],
|
||||
[t, buildModelTypeOptions, hideWhenInstanceExists],
|
||||
);
|
||||
|
||||
const handleOk = async (values?: FieldValues) => {
|
||||
if (!values) return;
|
||||
|
||||
const modelType =
|
||||
values.model_type === 'chat' && values.vision
|
||||
? 'image2text'
|
||||
: values.model_type;
|
||||
|
||||
const data = {
|
||||
...omit(values, ['vision']),
|
||||
model_type: modelType,
|
||||
instance_name: values.instance_name as string,
|
||||
model_type: values.model_type,
|
||||
llm_factory: llmFactory,
|
||||
max_tokens: values.max_tokens,
|
||||
};
|
||||
|
||||
await onOk?.(data as IAddLlmRequestBody);
|
||||
await onOk?.(data as IAddProviderInstanceRequestBody);
|
||||
};
|
||||
|
||||
const verifyParamsFunc = useCallback(() => {
|
||||
const values = formRef.current?.getValues();
|
||||
const modelType =
|
||||
values.model_type === 'chat' && values.vision
|
||||
? 'image2text'
|
||||
: values.model_type;
|
||||
return {
|
||||
llm_factory: llmFactory,
|
||||
model_type: modelType,
|
||||
model_type: values.model_type,
|
||||
};
|
||||
}, [llmFactory]);
|
||||
|
||||
@@ -175,8 +198,9 @@ const SparkModal = ({
|
||||
ref={formRef}
|
||||
defaultValues={
|
||||
{
|
||||
model_type: 'chat',
|
||||
vision: false,
|
||||
instance_name: '',
|
||||
model_type: ['chat'],
|
||||
max_tokens: 8192,
|
||||
} as FieldValues
|
||||
}
|
||||
labelClassName="font-normal"
|
||||
|
||||
@@ -8,14 +8,18 @@ import { Modal } from '@/components/ui/modal/modal';
|
||||
import { useCommonTranslation, useTranslate } from '@/hooks/common-hooks';
|
||||
import { useBuildModelTypeOptions } from '@/hooks/logic-hooks/use-build-options';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { VerifyResult } from '@/pages/user-setting/setting-model/hooks';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { IAddProviderInstanceRequestBody } from '@/interfaces/request/llm';
|
||||
import {
|
||||
useFetchInstanceNameSet,
|
||||
useHideWhenInstanceExists,
|
||||
VerifyResult,
|
||||
} from '@/pages/user-setting/setting-model/hooks';
|
||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||
import { FieldValues } from 'react-hook-form';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
import VerifyButton from '../../modal/verify-button';
|
||||
|
||||
type VolcEngineLlmRequest = IAddLlmRequestBody & {
|
||||
type VolcEngineLlmRequest = IAddProviderInstanceRequestBody & {
|
||||
endpoint_id: string;
|
||||
ark_api_key: string;
|
||||
};
|
||||
@@ -27,7 +31,7 @@ const VolcEngineModal = ({
|
||||
onVerify,
|
||||
loading,
|
||||
llmFactory,
|
||||
}: IModalProps<IAddLlmRequestBody> & {
|
||||
}: IModalProps<IAddProviderInstanceRequestBody> & {
|
||||
llmFactory: string;
|
||||
onVerify?: (
|
||||
postBody: any,
|
||||
@@ -37,79 +41,87 @@ const VolcEngineModal = ({
|
||||
const { t: tc } = useCommonTranslation();
|
||||
const { buildModelTypeOptions } = useBuildModelTypeOptions();
|
||||
const formRef = useRef<DynamicFormRef>(null);
|
||||
const fields: FormFieldConfig[] = [
|
||||
{
|
||||
name: 'model_type',
|
||||
label: t('modelType'),
|
||||
type: FormFieldType.Select,
|
||||
required: true,
|
||||
options: buildModelTypeOptions(['chat', 'embedding', 'image2text']),
|
||||
defaultValue: 'chat',
|
||||
},
|
||||
{
|
||||
name: 'llm_name',
|
||||
label: t('modelName'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('volcModelNameMessage'),
|
||||
},
|
||||
{
|
||||
name: 'endpoint_id',
|
||||
label: t('addEndpointID'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('endpointIDMessage'),
|
||||
},
|
||||
{
|
||||
name: 'ark_api_key',
|
||||
label: t('addArkApiKey'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('ArkApiKeyMessage'),
|
||||
},
|
||||
{
|
||||
name: 'max_tokens',
|
||||
label: t('maxTokens'),
|
||||
type: FormFieldType.Number,
|
||||
required: true,
|
||||
placeholder: t('maxTokensTip'),
|
||||
validation: {
|
||||
min: 0,
|
||||
const { instanceNameSet } = useFetchInstanceNameSet(llmFactory);
|
||||
|
||||
const hideWhenInstanceExists = useHideWhenInstanceExists(instanceNameSet);
|
||||
|
||||
const fields: FormFieldConfig[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
name: 'instance_name',
|
||||
label: t('instanceName'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('instanceNameMessage'),
|
||||
tooltip: t('instanceNameTip'),
|
||||
validation: { message: t('instanceNameMessage') },
|
||||
},
|
||||
},
|
||||
];
|
||||
{
|
||||
name: 'model_type',
|
||||
label: t('modelType'),
|
||||
type: FormFieldType.MultiSelect,
|
||||
required: true,
|
||||
options: buildModelTypeOptions(['chat', 'embedding', 'image2text']),
|
||||
defaultValue: ['chat'],
|
||||
},
|
||||
{
|
||||
name: 'llm_name',
|
||||
label: t('modelName'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('volcModelNameMessage'),
|
||||
},
|
||||
{
|
||||
name: 'endpoint_id',
|
||||
label: t('addEndpointID'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('endpointIDMessage'),
|
||||
shouldRender: hideWhenInstanceExists,
|
||||
},
|
||||
{
|
||||
name: 'ark_api_key',
|
||||
label: t('addArkApiKey'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('ArkApiKeyMessage'),
|
||||
shouldRender: hideWhenInstanceExists,
|
||||
},
|
||||
{
|
||||
name: 'max_tokens',
|
||||
label: t('maxTokens'),
|
||||
type: FormFieldType.Number,
|
||||
required: true,
|
||||
placeholder: t('maxTokensTip'),
|
||||
validation: {
|
||||
min: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
[t, buildModelTypeOptions, hideWhenInstanceExists],
|
||||
);
|
||||
|
||||
const handleOk = async (values?: FieldValues) => {
|
||||
if (!values) return;
|
||||
|
||||
const modelType =
|
||||
values.model_type === 'chat' && values.vision
|
||||
? 'image2text'
|
||||
: values.model_type;
|
||||
|
||||
const data: VolcEngineLlmRequest = {
|
||||
instance_name: values.instance_name as string,
|
||||
llm_factory: llmFactory,
|
||||
llm_name: values.llm_name as string,
|
||||
model_type: modelType,
|
||||
model_type: values.model_type,
|
||||
endpoint_id: values.endpoint_id as string,
|
||||
ark_api_key: values.ark_api_key as string,
|
||||
max_tokens: values.max_tokens as number,
|
||||
};
|
||||
|
||||
console.info(data);
|
||||
|
||||
await onOk?.(data);
|
||||
};
|
||||
|
||||
const verifyParamsFunc = useCallback(() => {
|
||||
const values = formRef.current?.getValues();
|
||||
const modelType =
|
||||
values.model_type === 'chat' && values.vision
|
||||
? 'image2text'
|
||||
: values.model_type;
|
||||
return {
|
||||
llm_factory: llmFactory,
|
||||
model_type: modelType,
|
||||
model_type: values.model_type,
|
||||
};
|
||||
}, [llmFactory]);
|
||||
|
||||
@@ -138,8 +150,9 @@ const VolcEngineModal = ({
|
||||
ref={formRef}
|
||||
defaultValues={
|
||||
{
|
||||
model_type: 'chat',
|
||||
vision: false,
|
||||
instance_name: '',
|
||||
model_type: ['chat'],
|
||||
max_tokens: 8192,
|
||||
} as FieldValues
|
||||
}
|
||||
labelClassName="font-normal"
|
||||
|
||||
@@ -8,8 +8,12 @@ import { Modal } from '@/components/ui/modal/modal';
|
||||
import { useCommonTranslation, useTranslate } from '@/hooks/common-hooks';
|
||||
import { useBuildModelTypeOptions } from '@/hooks/logic-hooks/use-build-options';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
|
||||
import { VerifyResult } from '@/pages/user-setting/setting-model/hooks';
|
||||
import { IAddProviderInstanceRequestBody } from '@/interfaces/request/llm';
|
||||
import {
|
||||
useFetchInstanceNameSet,
|
||||
useHideWhenInstanceExists,
|
||||
VerifyResult,
|
||||
} from '@/pages/user-setting/setting-model/hooks';
|
||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||
import { FieldValues } from 'react-hook-form';
|
||||
import { LLMHeader } from '../../components/llm-header';
|
||||
@@ -22,7 +26,7 @@ const YiyanModal = ({
|
||||
onVerify,
|
||||
loading,
|
||||
llmFactory,
|
||||
}: IModalProps<IAddLlmRequestBody> & {
|
||||
}: IModalProps<IAddProviderInstanceRequestBody> & {
|
||||
llmFactory: string;
|
||||
onVerify?: (
|
||||
postBody: any,
|
||||
@@ -32,16 +36,28 @@ const YiyanModal = ({
|
||||
const { t: tc } = useCommonTranslation();
|
||||
const { buildModelTypeOptions } = useBuildModelTypeOptions();
|
||||
const formRef = useRef<DynamicFormRef>(null);
|
||||
const { instanceNameSet } = useFetchInstanceNameSet(llmFactory);
|
||||
|
||||
const hideWhenInstanceExists = useHideWhenInstanceExists(instanceNameSet);
|
||||
|
||||
const fields = useMemo<FormFieldConfig[]>(
|
||||
() => [
|
||||
{
|
||||
name: 'instance_name',
|
||||
label: t('instanceName'),
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('instanceNameMessage'),
|
||||
tooltip: t('instanceNameTip'),
|
||||
validation: { message: t('instanceNameMessage') },
|
||||
},
|
||||
{
|
||||
name: 'model_type',
|
||||
label: t('modelType'),
|
||||
type: FormFieldType.Select,
|
||||
type: FormFieldType.MultiSelect,
|
||||
required: true,
|
||||
options: buildModelTypeOptions(['chat', 'embedding', 'rerank']),
|
||||
defaultValue: 'chat',
|
||||
defaultValue: ['chat'],
|
||||
},
|
||||
{
|
||||
name: 'llm_name',
|
||||
@@ -56,6 +72,7 @@ const YiyanModal = ({
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('yiyanAKMessage'),
|
||||
shouldRender: hideWhenInstanceExists,
|
||||
},
|
||||
{
|
||||
name: 'yiyan_sk',
|
||||
@@ -63,6 +80,7 @@ const YiyanModal = ({
|
||||
type: FormFieldType.Text,
|
||||
required: true,
|
||||
placeholder: t('yiyanSKMessage'),
|
||||
shouldRender: hideWhenInstanceExists,
|
||||
},
|
||||
{
|
||||
name: 'max_tokens',
|
||||
@@ -75,21 +93,17 @@ const YiyanModal = ({
|
||||
},
|
||||
},
|
||||
],
|
||||
[t, buildModelTypeOptions],
|
||||
[t, buildModelTypeOptions, hideWhenInstanceExists],
|
||||
);
|
||||
|
||||
const handleOk = async (values?: FieldValues) => {
|
||||
if (!values) return;
|
||||
|
||||
const modelType =
|
||||
values.model_type === 'chat' && values.vision
|
||||
? 'image2text'
|
||||
: values.model_type;
|
||||
|
||||
const data: IAddLlmRequestBody = {
|
||||
const data: IAddProviderInstanceRequestBody = {
|
||||
instance_name: values.instance_name as string,
|
||||
llm_factory: llmFactory,
|
||||
llm_name: values.llm_name as string,
|
||||
model_type: modelType,
|
||||
model_type: values.model_type,
|
||||
api_key: {
|
||||
yiyan_ak: values.yiyan_ak,
|
||||
yiyan_sk: values.yiyan_sk,
|
||||
@@ -97,21 +111,15 @@ const YiyanModal = ({
|
||||
max_tokens: values.max_tokens as number,
|
||||
};
|
||||
|
||||
console.info(data);
|
||||
|
||||
await onOk?.(data);
|
||||
};
|
||||
|
||||
const verifyParamsFunc = useCallback(() => {
|
||||
const values = formRef.current?.getValues();
|
||||
const modelType =
|
||||
values.model_type === 'chat' && values.vision
|
||||
? 'image2text'
|
||||
: values.model_type;
|
||||
return {
|
||||
llm_factory: llmFactory,
|
||||
llm_name: values.llm_name as string,
|
||||
model_type: modelType,
|
||||
model_type: values.model_type,
|
||||
api_key: {
|
||||
yiyan_ak: values.yiyan_ak,
|
||||
yiyan_sk: values.yiyan_sk,
|
||||
@@ -148,8 +156,9 @@ const YiyanModal = ({
|
||||
}}
|
||||
defaultValues={
|
||||
{
|
||||
model_type: 'chat',
|
||||
vision: false,
|
||||
instance_name: '',
|
||||
model_type: ['chat'],
|
||||
max_tokens: 8192,
|
||||
} as FieldValues
|
||||
}
|
||||
labelClassName="font-normal"
|
||||
|
||||
109
web/src/pages/user-setting/setting-model/payload-utils.ts
Normal file
109
web/src/pages/user-setting/setting-model/payload-utils.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
IAddInstanceModelRequestBody,
|
||||
IAddProviderInstanceRequestBody,
|
||||
} from '@/interfaces/request/llm';
|
||||
|
||||
const MODEL_RESERVED_KEYS = new Set([
|
||||
'llm_name',
|
||||
'model_name',
|
||||
'model_type',
|
||||
'max_tokens',
|
||||
]);
|
||||
|
||||
const INSTANCE_RESERVED_KEYS = new Set([
|
||||
'instance_name',
|
||||
'llm_factory',
|
||||
'provider_name',
|
||||
'api_base',
|
||||
'base_url',
|
||||
'region',
|
||||
'verify',
|
||||
]);
|
||||
|
||||
export const MODEL_EXTRA_KEYS = new Set([
|
||||
'is_tools',
|
||||
'vision',
|
||||
'provider_order',
|
||||
'api_version',
|
||||
]);
|
||||
|
||||
export const MODEL_FIELD_NAMES = new Set<string>([
|
||||
...MODEL_RESERVED_KEYS,
|
||||
...MODEL_EXTRA_KEYS,
|
||||
]);
|
||||
|
||||
export const isModelField = (fieldName: string) =>
|
||||
MODEL_FIELD_NAMES.has(fieldName);
|
||||
|
||||
type FlatPayload = Record<string, any>;
|
||||
|
||||
export type SplitResult = {
|
||||
instancePayload: Omit<
|
||||
IAddProviderInstanceRequestBody,
|
||||
'llm_name' | 'model_type' | 'max_tokens'
|
||||
> & {
|
||||
base_url?: string;
|
||||
region?: string;
|
||||
};
|
||||
modelPayload: IAddInstanceModelRequestBody;
|
||||
};
|
||||
|
||||
const collectApiKeyExtras = (payload: FlatPayload) => {
|
||||
const extras: Record<string, any> = {};
|
||||
let apiKeyValue: any = undefined;
|
||||
for (const [key, value] of Object.entries(payload)) {
|
||||
if (value === undefined) continue;
|
||||
if (key === 'api_key') {
|
||||
apiKeyValue = value;
|
||||
continue;
|
||||
}
|
||||
if (INSTANCE_RESERVED_KEYS.has(key)) continue;
|
||||
if (MODEL_RESERVED_KEYS.has(key)) continue;
|
||||
if (MODEL_EXTRA_KEYS.has(key)) continue;
|
||||
extras[key] = value;
|
||||
}
|
||||
if (apiKeyValue && typeof apiKeyValue === 'object') {
|
||||
return { ...apiKeyValue, ...extras };
|
||||
}
|
||||
if (Object.keys(extras).length === 0) {
|
||||
return apiKeyValue ?? '';
|
||||
}
|
||||
if (apiKeyValue !== undefined && apiKeyValue !== '') {
|
||||
return { api_key: apiKeyValue, ...extras };
|
||||
}
|
||||
return extras;
|
||||
};
|
||||
|
||||
const collectModelExtras = (payload: FlatPayload) => {
|
||||
const extras: Record<string, any> = {};
|
||||
for (const key of MODEL_EXTRA_KEYS) {
|
||||
if (payload[key] !== undefined && payload[key] !== '') {
|
||||
extras[key] = payload[key];
|
||||
}
|
||||
}
|
||||
return extras;
|
||||
};
|
||||
|
||||
export const splitProviderPayload = (payload: FlatPayload): SplitResult => {
|
||||
const instancePayload = {
|
||||
instance_name: payload.instance_name as string,
|
||||
llm_factory: payload.llm_factory as string,
|
||||
api_key: collectApiKeyExtras(payload),
|
||||
base_url: (payload.base_url ?? payload.api_base) as string | undefined,
|
||||
region: (payload.region as string | undefined) || 'default',
|
||||
};
|
||||
|
||||
const modelExtra = collectModelExtras(payload);
|
||||
|
||||
const modelPayload = {
|
||||
model_name: (payload.model_name ?? payload.llm_name) as string,
|
||||
model_type: payload.model_type,
|
||||
max_tokens: payload.max_tokens as number,
|
||||
...(Object.keys(modelExtra).length > 0 ? { extra: modelExtra } : {}),
|
||||
};
|
||||
|
||||
return {
|
||||
instancePayload: instancePayload as SplitResult['instancePayload'],
|
||||
modelPayload,
|
||||
};
|
||||
};
|
||||
71
web/src/services/llm-service.ts
Normal file
71
web/src/services/llm-service.ts
Normal file
@@ -0,0 +1,71 @@
|
||||
import api from '@/utils/api';
|
||||
import { registerNextServer } from '@/utils/register-server';
|
||||
|
||||
const {
|
||||
listAllAddedModels,
|
||||
defaultModel,
|
||||
listProviders,
|
||||
addProvider,
|
||||
addProviderInstance,
|
||||
listProviderInstances,
|
||||
listInstanceModels,
|
||||
showProviderInstance,
|
||||
addInstanceModel,
|
||||
deleteProviderInstance,
|
||||
updateModelStatus,
|
||||
} = api;
|
||||
|
||||
const methods = {
|
||||
listAllAddedModels: {
|
||||
url: listAllAddedModels,
|
||||
method: 'get',
|
||||
},
|
||||
listDefaultModels: {
|
||||
url: defaultModel,
|
||||
method: 'get',
|
||||
},
|
||||
setDefaultModel: {
|
||||
url: defaultModel,
|
||||
method: 'patch',
|
||||
},
|
||||
listProviders: {
|
||||
url: listProviders,
|
||||
method: 'get',
|
||||
},
|
||||
addProvider: {
|
||||
url: addProvider,
|
||||
method: 'put',
|
||||
},
|
||||
addProviderInstance: {
|
||||
url: addProviderInstance,
|
||||
method: 'post',
|
||||
},
|
||||
listProviderInstances: {
|
||||
url: listProviderInstances,
|
||||
method: 'get',
|
||||
},
|
||||
listInstanceModels: {
|
||||
url: listInstanceModels,
|
||||
method: 'get',
|
||||
},
|
||||
showProviderInstance: {
|
||||
url: showProviderInstance,
|
||||
method: 'get',
|
||||
},
|
||||
addInstanceModel: {
|
||||
url: addInstanceModel,
|
||||
method: 'post',
|
||||
},
|
||||
deleteProviderInstance: {
|
||||
url: deleteProviderInstance,
|
||||
method: 'delete',
|
||||
},
|
||||
updateModelStatus: {
|
||||
url: updateModelStatus,
|
||||
method: 'patch',
|
||||
},
|
||||
} as const;
|
||||
|
||||
const llmService = registerNextServer<keyof typeof methods>(methods);
|
||||
|
||||
export default llmService;
|
||||
@@ -9,15 +9,6 @@ const {
|
||||
setting,
|
||||
userInfo,
|
||||
tenantInfo,
|
||||
factoriesList,
|
||||
llmList,
|
||||
myLlm,
|
||||
setApiKey,
|
||||
setTenantInfo,
|
||||
addLlm,
|
||||
deleteLlm,
|
||||
enableLlm,
|
||||
deleteFactory,
|
||||
getSystemVersion,
|
||||
getSystemTokenList,
|
||||
removeSystemToken,
|
||||
@@ -51,46 +42,10 @@ const methods = {
|
||||
url: tenantInfo,
|
||||
method: 'get',
|
||||
},
|
||||
setTenantInfo: {
|
||||
url: setTenantInfo,
|
||||
method: 'patch',
|
||||
},
|
||||
factoriesList: {
|
||||
url: factoriesList,
|
||||
method: 'get',
|
||||
},
|
||||
llmList: {
|
||||
url: llmList,
|
||||
method: 'get',
|
||||
},
|
||||
myLlm: {
|
||||
url: myLlm,
|
||||
method: 'get',
|
||||
},
|
||||
setApiKey: {
|
||||
url: setApiKey,
|
||||
method: 'post',
|
||||
},
|
||||
addLlm: {
|
||||
url: addLlm,
|
||||
method: 'post',
|
||||
},
|
||||
deleteLlm: {
|
||||
url: deleteLlm,
|
||||
method: 'post',
|
||||
},
|
||||
enableLlm: {
|
||||
url: enableLlm,
|
||||
method: 'post',
|
||||
},
|
||||
getSystemVersion: {
|
||||
url: getSystemVersion,
|
||||
method: 'get',
|
||||
},
|
||||
deleteFactory: {
|
||||
url: deleteFactory,
|
||||
method: 'post',
|
||||
},
|
||||
listToken: {
|
||||
url: getSystemTokenList,
|
||||
method: 'get',
|
||||
|
||||
@@ -11,7 +11,6 @@ export default {
|
||||
setting: `${restAPIv1}/users/me`,
|
||||
userInfo: `${restAPIv1}/users/me`,
|
||||
tenantInfo: `${restAPIv1}/users/me/models`,
|
||||
setTenantInfo: `${restAPIv1}/users/me/models`,
|
||||
loginChannels: `${restAPIv1}/auth/login/channels`,
|
||||
loginChannel: (channel: string) => `${restAPIv1}/auth/login/${channel}`,
|
||||
|
||||
@@ -25,14 +24,49 @@ export default {
|
||||
agreeTenant: (tenantId: string) => `${restAPIv1}/tenants/${tenantId}`,
|
||||
|
||||
// llm model
|
||||
factoriesList: `${webAPI}/llm/factories`,
|
||||
llmList: `${webAPI}/llm/list`,
|
||||
myLlm: `${webAPI}/llm/my_llms`,
|
||||
setApiKey: `${webAPI}/llm/set_api_key`,
|
||||
addLlm: `${webAPI}/llm/add_llm`,
|
||||
deleteLlm: `${webAPI}/llm/delete_llm`,
|
||||
enableLlm: `${webAPI}/llm/enable_llm`,
|
||||
deleteFactory: `${webAPI}/llm/delete_factory`,
|
||||
listAllAddedModels: `${restAPIv1}/models`,
|
||||
defaultModel: `${restAPIv1}/models/default`,
|
||||
listProviders: `${restAPIv1}/providers`,
|
||||
addProvider: `${restAPIv1}/providers/`,
|
||||
addProviderInstance: ({ llm_factory }: { llm_factory: string }) =>
|
||||
`${restAPIv1}/providers/${llm_factory}/instances`,
|
||||
listProviderInstances: ({ provider_name }: { provider_name: string }) =>
|
||||
`${restAPIv1}/providers/${provider_name}/instances`,
|
||||
listInstanceModels: ({
|
||||
provider_name,
|
||||
instance_name,
|
||||
}: {
|
||||
provider_name: string;
|
||||
instance_name: string;
|
||||
}) =>
|
||||
`${restAPIv1}/providers/${provider_name}/instances/${instance_name}/models`,
|
||||
showProviderInstance: ({
|
||||
provider_name,
|
||||
instance_name,
|
||||
}: {
|
||||
provider_name: string;
|
||||
instance_name: string;
|
||||
}) => `${restAPIv1}/providers/${provider_name}/instances/${instance_name}`,
|
||||
addInstanceModel: ({
|
||||
provider_name,
|
||||
instance_name,
|
||||
}: {
|
||||
provider_name: string;
|
||||
instance_name: string;
|
||||
}) =>
|
||||
`${restAPIv1}/providers/${provider_name}/instances/${instance_name}/models`,
|
||||
deleteProviderInstance: ({ provider_name }: { provider_name: string }) =>
|
||||
`${restAPIv1}/providers/${provider_name}/instances`,
|
||||
updateModelStatus: ({
|
||||
provider_name,
|
||||
instance_name,
|
||||
model_name,
|
||||
}: {
|
||||
provider_name: string;
|
||||
instance_name: string;
|
||||
model_name: string;
|
||||
}) =>
|
||||
`${restAPIv1}/providers/${provider_name}/instances/${instance_name}/models/${model_name}`,
|
||||
|
||||
// data source
|
||||
dataSourceUpdate: (id: string) => `${restAPIv1}/connectors/${id}`,
|
||||
|
||||
@@ -1,29 +1,10 @@
|
||||
import { IThirdOAIModel } from '@/interfaces/database/llm';
|
||||
import { getCachedLlmList } from './llm-cache';
|
||||
|
||||
export const getLLMIconName = (fid: string, llm_name: string) => {
|
||||
if (fid === 'FastEmbed') {
|
||||
return llm_name.split('/').at(0) ?? '';
|
||||
}
|
||||
|
||||
return fid;
|
||||
};
|
||||
|
||||
export const getLlmNameAndFIdByLlmId = (llmId?: string) => {
|
||||
const [llmName, fId] = llmId?.split('@') || [];
|
||||
|
||||
return { fId, llmName };
|
||||
};
|
||||
|
||||
// The names of the large models returned by the interface are similar to "deepseek-r1___OpenAI-API"
|
||||
export function getRealModelName(llmName: string) {
|
||||
return llmName.split('__').at(0) ?? '';
|
||||
}
|
||||
|
||||
export function buildLlmUuid(llm: IThirdOAIModel) {
|
||||
return `${llm.llm_name}@${llm.fid}`;
|
||||
}
|
||||
|
||||
// Get tenant model ID from LLM list by model name and factory ID
|
||||
export function getTenantModelId(
|
||||
llmList: Record<string, any>,
|
||||
@@ -53,12 +34,37 @@ export function getTenantModelId(
|
||||
return '';
|
||||
}
|
||||
|
||||
// Extract model name and factory ID from a model UUID (e.g., "model_name@factory_id")
|
||||
/** Build "modelName@instanceName@providerName" */
|
||||
export function buildModelValue(model: {
|
||||
model_name: string;
|
||||
model_instance: string;
|
||||
model_provider: string;
|
||||
}) {
|
||||
return `${model.model_name}@${model.model_instance}@${model.model_provider}`;
|
||||
}
|
||||
|
||||
/** Parse "modelName@instanceName@providerName" */
|
||||
export function parseModelValue(val: string) {
|
||||
if (!val) return null;
|
||||
const firstAt = val.indexOf('@');
|
||||
const lastAt = val.lastIndexOf('@');
|
||||
if (firstAt === -1 || firstAt === lastAt) return null;
|
||||
return {
|
||||
model_name: val.substring(0, firstAt),
|
||||
model_instance: val.substring(firstAt + 1, lastAt),
|
||||
model_provider: val.substring(lastAt + 1),
|
||||
};
|
||||
}
|
||||
|
||||
// Extract model name and factory ID from a model UUID
|
||||
// Supports both "model_name@factory_id" and "model_name@factory_id#instance_name"
|
||||
export function parseModelUuid(uuid: string): {
|
||||
modelName: string;
|
||||
factoryId: string;
|
||||
} {
|
||||
const [modelName, factoryId] = uuid.split('@');
|
||||
const hashIndex = uuid.indexOf('#');
|
||||
const core = hashIndex === -1 ? uuid : uuid.slice(0, hashIndex);
|
||||
const [modelName, factoryId] = core.split('@');
|
||||
return { modelName, factoryId };
|
||||
}
|
||||
|
||||
|
||||
@@ -148,8 +148,6 @@ request.interceptors.response.use(
|
||||
return response;
|
||||
},
|
||||
function (error) {
|
||||
console.log('🚀 ~ error:', error);
|
||||
|
||||
// Handle HTTP 401 (token expired / invalid)
|
||||
const status = error?.response?.status;
|
||||
if (status === 401) {
|
||||
|
||||
Reference in New Issue
Block a user