Compare commits

...

6 Commits

Author SHA1 Message Date
e9debfd74d Fix: The nodes on the canvas were not updated in time after the operator name was modified. #10866 (#10911)
### What problem does this PR solve?

Fix: The nodes on the canvas were not updated in time after the operator
name was modified. #10866

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-10-31 14:46:03 +08:00
d8a7fb6f2b Fix: Fixed the styling and logic issues on the model provider page #10703 (#10909)
### What problem does this PR solve?

Fix: Fixed the styling and logic issues on the model provider page

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-10-31 13:42:28 +08:00
c8a82da722 Feat: Rename the files in the jsonjoy-builder directory to lowercase. #10866 (#10908)
### What problem does this PR solve?

Feat: Rename the files in the jsonjoy-builder directory to lowercase.
#10866

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
2025-10-31 13:42:11 +08:00
09dd786674 Fix:KeyError: 'table_body' of mineru parser (#10773)
### What problem does this PR solve?
https://github.com/infiniflow/ragflow/issues/10769

### Type of change

- [x] Bug Fix (non-breaking change which fixes an issue)
2025-10-31 10:07:56 +08:00
0ecccd27eb Refactor:improve the logic for rerank models to cal the total token count (#10882)
### What problem does this PR solve?

improve the logic for rerank models to cal the total token count

### Type of change

- [x] Refactoring
2025-10-31 09:46:16 +08:00
5a830ea68b Refactor(setting-model): Refactor the model management interface and optimize the component structure. #10703 (#10905)
### What problem does this PR solve?

Refactor(setting-model): Refactor the model management interface and
optimize the component structure. #10703

### Type of change

- [x] Refactoring
2025-10-31 09:27:30 +08:00
67 changed files with 1519 additions and 1129 deletions

View File

@ -394,7 +394,9 @@ class MinerUParser(RAGFlowPdfParser):
case MinerUContentType.TEXT:
section = output["text"]
case MinerUContentType.TABLE:
section = output["table_body"] if "table_body" in output else "" + "\n".join(output["table_caption"]) + "\n".join(output["table_footnote"])
section = output.get("table_body", "") + "\n".join(output.get("table_caption", [])) + "\n".join(output.get("table_footnote", []))
if not section.strip():
section = "FAILED TO PARSE TABLE"
case MinerUContentType.IMAGE:
section = "".join(output["image_caption"]) + "\n" + "".join(output["image_footnote"])
case MinerUContentType.EQUATION:

View File

@ -36,9 +36,6 @@ class Base(ABC):
def similarity(self, query: str, texts: list):
raise NotImplementedError("Please implement encode method!")
def total_token_count(self, resp):
return total_token_count_from_response(resp)
class JinaRerank(Base):
_FACTORY_NAME = "Jina"
@ -58,7 +55,7 @@ class JinaRerank(Base):
rank[d["index"]] = d["relevance_score"]
except Exception as _e:
log_exception(_e, res)
return rank, self.total_token_count(res)
return rank, total_token_count_from_response(res)
class XInferenceRerank(Base):
@ -301,7 +298,7 @@ class SILICONFLOWRerank(Base):
log_exception(_e, response)
return (
rank,
response["meta"]["tokens"]["input_tokens"] + response["meta"]["tokens"]["output_tokens"],
total_token_count_from_response(response),
)
@ -330,7 +327,7 @@ class BaiduYiyanRerank(Base):
rank[d["index"]] = d["relevance_score"]
except Exception as _e:
log_exception(_e, res)
return rank, self.total_token_count(res)
return rank, total_token_count_from_response(res)
class VoyageRerank(Base):
@ -378,7 +375,7 @@ class QWenRerank(Base):
rank[r.index] = r.relevance_score
except Exception as _e:
log_exception(_e, resp)
return rank, resp.usage.total_tokens
return rank, total_token_count_from_response(resp)
else:
raise ValueError(f"Error calling QWenRerank model {self.model_name}: {resp.status_code} - {resp.text}")

View File

@ -69,6 +69,12 @@ def total_token_count_from_response(resp):
return resp["usage"]["input_tokens"] + resp["usage"]["output_tokens"]
except Exception:
pass
if 'meta' in resp and 'tokens' in resp['meta'] and 'input_tokens' in resp['meta']['tokens'] and 'output_tokens' in resp['meta']['tokens']:
try:
return resp["meta"]["tokens"]["input_tokens"] + resp["meta"]["tokens"]["output_tokens"]
except Exception:
pass
return 0

View File

@ -11,8 +11,11 @@ import type * as Monaco from 'monaco-editor';
import { useCallback, useEffect, useRef, useState } from 'react';
import { useMonacoTheme } from '../../hooks/use-monaco-theme';
import { formatTranslation, useTranslation } from '../../hooks/use-translation';
import type { JSONSchema } from '../../types/jsonSchema';
import { validateJson, type ValidationResult } from '../../utils/jsonValidator';
import type { JSONSchema } from '../../types/json-schema';
import {
validateJson,
type ValidationResult,
} from '../../utils/json-validator';
/** @public */
export interface JsonValidatorProps {

View File

@ -13,7 +13,7 @@ import { useRef, useState } from 'react';
import { useMonacoTheme } from '../../hooks/use-monaco-theme';
import { useTranslation } from '../../hooks/use-translation';
import { createSchemaFromJson } from '../../lib/schema-inference';
import type { JSONSchema } from '../../types/jsonSchema';
import type { JSONSchema } from '../../types/json-schema';
/** @public */
export interface SchemaInferencerProps {

View File

@ -18,8 +18,8 @@ import {
import { CirclePlus, HelpCircle, Info } from 'lucide-react';
import { useId, useState, type FC, type FormEvent } from 'react';
import { useTranslation } from '../../hooks/use-translation';
import type { NewField, SchemaType } from '../../types/jsonSchema';
import SchemaTypeSelector from './SchemaTypeSelector';
import type { NewField, SchemaType } from '../../types/json-schema';
import SchemaTypeSelector from './schema-type-selector';
interface AddFieldButtonProps {
onAddField: (field: NewField) => void;

View File

@ -8,9 +8,9 @@ import {
} from 'react';
import { useTranslation } from '../../hooks/use-translation';
import { cn } from '../../lib/utils';
import type { JSONSchema } from '../../types/jsonSchema';
import JsonSchemaVisualizer from './JsonSchemaVisualizer';
import SchemaVisualEditor from './SchemaVisualEditor';
import type { JSONSchema } from '../../types/json-schema';
import JsonSchemaVisualizer from './json-schema-visualizer';
import SchemaVisualEditor from './schema-visual-editor';
/** @public */
export interface JsonSchemaEditorProps {

View File

@ -4,7 +4,7 @@ import { useRef, type FC } from 'react';
import { useMonacoTheme } from '../../hooks/use-monaco-theme';
import { useTranslation } from '../../hooks/use-translation';
import { cn } from '../../lib/utils';
import type { JSONSchema } from '../../types/jsonSchema';
import type { JSONSchema } from '../../types/json-schema';
/** @public */
export interface JsonSchemaVisualizerProps {

View File

@ -1,14 +1,14 @@
import { useMemo, type FC } from 'react';
import { useTranslation } from '../../hooks/use-translation';
import { getSchemaProperties } from '../../lib/schemaEditor';
import { getSchemaProperties } from '../../lib/schema-editor';
import type {
JSONSchema as JSONSchemaType,
NewField,
ObjectJSONSchema,
SchemaType,
} from '../../types/jsonSchema';
} from '../../types/json-schema';
import { buildValidationTree } from '../../types/validation';
import SchemaPropertyEditor from './SchemaPropertyEditor';
import SchemaPropertyEditor from './schema-property-editor';
interface SchemaFieldListProps {
schema: JSONSchemaType;

View File

@ -5,13 +5,13 @@ import type {
NewField,
ObjectJSONSchema,
SchemaType,
} from '../../types/jsonSchema';
} from '../../types/json-schema';
import {
asObjectSchema,
getSchemaDescription,
withObjectSchema,
} from '../../types/jsonSchema';
import SchemaPropertyEditor from './SchemaPropertyEditor';
} from '../../types/json-schema';
import SchemaPropertyEditor from './schema-property-editor';
// This component is now just a simple wrapper around SchemaPropertyEditor
// to maintain backward compatibility during migration

View File

@ -1,3 +1,4 @@
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { ChevronDown, ChevronRight, X } from 'lucide-react';
import { useEffect, useState } from 'react';
@ -7,16 +8,15 @@ import type {
JSONSchema,
ObjectJSONSchema,
SchemaType,
} from '../../types/jsonSchema';
} from '../../types/json-schema';
import {
asObjectSchema,
getSchemaDescription,
withObjectSchema,
} from '../../types/jsonSchema';
} from '../../types/json-schema';
import type { ValidationTreeNode } from '../../types/validation';
import { Badge } from '../ui/badge';
import TypeDropdown from './TypeDropdown';
import TypeEditor from './TypeEditor';
import TypeDropdown from './type-dropdown';
import TypeEditor from './type-editor';
export interface SchemaPropertyEditorProps {
name: string;

View File

@ -2,7 +2,7 @@ import type { FC } from 'react';
import { useTranslation } from '../../hooks/use-translation';
import type { Translation } from '../../i18n/translation-keys';
import { cn } from '../../lib/utils';
import type { SchemaType } from '../../types/jsonSchema';
import type { SchemaType } from '../../types/json-schema';
interface SchemaTypeSelectorProps {
id?: string;

View File

@ -4,11 +4,11 @@ import {
createFieldSchema,
updateObjectProperty,
updatePropertyRequired,
} from '../../lib/schemaEditor';
import type { JSONSchema, NewField } from '../../types/jsonSchema';
import { asObjectSchema, isBooleanSchema } from '../../types/jsonSchema';
import AddFieldButton from './AddFieldButton';
import SchemaFieldList from './SchemaFieldList';
} from '../../lib/schema-editor';
import type { JSONSchema, NewField } from '../../types/json-schema';
import { asObjectSchema, isBooleanSchema } from '../../types/json-schema';
import AddFieldButton from './add-field-button';
import SchemaFieldList from './schema-field-list';
/** @public */
export interface SchemaVisualEditorProps {

View File

@ -2,7 +2,7 @@ import { Check, ChevronDown } from 'lucide-react';
import { useEffect, useRef, useState } from 'react';
import { useTranslation } from '../../hooks/use-translation';
import { cn, getTypeColor, getTypeLabel } from '../../lib/utils';
import type { SchemaType } from '../../types/jsonSchema';
import type { SchemaType } from '../../types/json-schema';
export interface TypeDropdownProps {
value: SchemaType;

View File

@ -1,18 +1,18 @@
import { lazy, Suspense } from 'react';
import { withObjectSchema } from '../../types/jsonSchema';
import type {
JSONSchema,
ObjectJSONSchema,
SchemaType,
} from '../../types/jsonSchema.ts';
} from '../../types/json-schema';
import { withObjectSchema } from '../../types/json-schema';
import type { ValidationTreeNode } from '../../types/validation';
// Lazy load specific type editors to avoid circular dependencies
const StringEditor = lazy(() => import('./types/StringEditor'));
const NumberEditor = lazy(() => import('./types/NumberEditor'));
const BooleanEditor = lazy(() => import('./types/BooleanEditor'));
const ObjectEditor = lazy(() => import('./types/ObjectEditor'));
const ArrayEditor = lazy(() => import('./types/ArrayEditor'));
const StringEditor = lazy(() => import('./types/string-editor'));
const NumberEditor = lazy(() => import('./types/number-editor'));
const BooleanEditor = lazy(() => import('./types/boolean-editor'));
const ObjectEditor = lazy(() => import('./types/object-editor'));
const ArrayEditor = lazy(() => import('./types/array-editor'));
export interface TypeEditorProps {
schema: JSONSchema;

View File

@ -3,13 +3,13 @@ import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import { useId, useMemo, useState } from 'react';
import { useTranslation } from '../../../hooks/use-translation';
import { getArrayItemsSchema } from '../../../lib/schemaEditor';
import { getArrayItemsSchema } from '../../../lib/schema-editor';
import { cn } from '../../../lib/utils';
import type { ObjectJSONSchema, SchemaType } from '../../../types/jsonSchema';
import { isBooleanSchema, withObjectSchema } from '../../../types/jsonSchema';
import TypeDropdown from '../TypeDropdown';
import type { TypeEditorProps } from '../TypeEditor';
import TypeEditor from '../TypeEditor';
import type { ObjectJSONSchema, SchemaType } from '../../../types/json-schema';
import { isBooleanSchema, withObjectSchema } from '../../../types/json-schema';
import TypeDropdown from '../type-dropdown';
import type { TypeEditorProps } from '../type-editor';
import TypeEditor from '../type-editor';
const ArrayEditor: React.FC<TypeEditorProps> = ({
schema,

View File

@ -2,9 +2,9 @@ import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import { useId } from 'react';
import { useTranslation } from '../../../hooks/use-translation';
import type { ObjectJSONSchema } from '../../../types/jsonSchema';
import { withObjectSchema } from '../../../types/jsonSchema';
import type { TypeEditorProps } from '../TypeEditor';
import type { ObjectJSONSchema } from '../../../types/json-schema';
import { withObjectSchema } from '../../../types/json-schema';
import type { TypeEditorProps } from '../type-editor';
const BooleanEditor: React.FC<TypeEditorProps> = ({ schema, onChange }) => {
const t = useTranslation();

View File

@ -4,9 +4,9 @@ import { X } from 'lucide-react';
import { useId, useMemo, useState } from 'react';
import { useTranslation } from '../../../hooks/use-translation';
import { cn } from '../../../lib/utils';
import type { ObjectJSONSchema } from '../../../types/jsonSchema';
import { isBooleanSchema, withObjectSchema } from '../../../types/jsonSchema';
import type { TypeEditorProps } from '../TypeEditor';
import type { ObjectJSONSchema } from '../../../types/json-schema';
import { isBooleanSchema, withObjectSchema } from '../../../types/json-schema';
import type { TypeEditorProps } from '../type-editor';
interface NumberEditorProps extends TypeEditorProps {
integer?: boolean;

View File

@ -4,12 +4,12 @@ import {
removeObjectProperty,
updateObjectProperty,
updatePropertyRequired,
} from '../../../lib/schemaEditor';
import type { NewField, ObjectJSONSchema } from '../../../types/jsonSchema';
import { asObjectSchema, isBooleanSchema } from '../../../types/jsonSchema';
import AddFieldButton from '../AddFieldButton';
import SchemaPropertyEditor from '../SchemaPropertyEditor';
import type { TypeEditorProps } from '../TypeEditor';
} from '../../../lib/schema-editor';
import type { NewField, ObjectJSONSchema } from '../../../types/json-schema';
import { asObjectSchema, isBooleanSchema } from '../../../types/json-schema';
import AddFieldButton from '../add-field-button';
import SchemaPropertyEditor from '../schema-property-editor';
import type { TypeEditorProps } from '../type-editor';
const ObjectEditor: React.FC<TypeEditorProps> = ({
schema,

View File

@ -11,9 +11,9 @@ import { X } from 'lucide-react';
import { useId, useMemo, useState } from 'react';
import { useTranslation } from '../../../hooks/use-translation';
import { cn } from '../../../lib/utils';
import type { ObjectJSONSchema } from '../../../types/jsonSchema';
import { isBooleanSchema, withObjectSchema } from '../../../types/jsonSchema';
import type { TypeEditorProps } from '../TypeEditor';
import type { ObjectJSONSchema } from '../../../types/json-schema';
import { isBooleanSchema, withObjectSchema } from '../../../types/json-schema';
import type { TypeEditorProps } from '../type-editor';
type Property = 'enum' | 'minLength' | 'maxLength' | 'pattern' | 'format';

View File

@ -1,36 +0,0 @@
import { cva, type VariantProps } from 'class-variance-authority';
import type { HTMLAttributes } from 'react';
import { cn } from '../../lib/utils.ts';
const badgeVariants = cva(
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2',
{
variants: {
variant: {
default:
'border-transparent bg-primary text-primary-foreground hover:bg-primary/80',
secondary:
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
destructive:
'border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80',
outline: 'text-foreground',
},
},
defaultVariants: {
variant: 'default',
},
},
);
export interface BadgeProps
extends HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
);
}
export { Badge, badgeVariants };

View File

@ -1,56 +0,0 @@
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';
import { forwardRef, type ButtonHTMLAttributes } from 'react';
import { cn } from '../../lib/utils.ts';
const buttonVariants = cva(
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive:
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline:
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
secondary:
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'text-primary underline-offset-4 hover:underline',
},
size: {
default: 'h-10 px-4 py-2',
sm: 'h-9 rounded-md px-3',
lg: 'h-11 rounded-md px-8',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
},
);
export interface ButtonProps
extends ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : 'button';
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
},
);
Button.displayName = 'Button';
export { Button, buttonVariants };

View File

@ -1,136 +0,0 @@
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { X } from 'lucide-react';
import {
forwardRef,
useId,
type ComponentPropsWithoutRef,
type ComponentRef,
type HTMLAttributes,
} from 'react';
import { cn } from '../../lib/utils.ts';
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close;
const DialogOverlay = forwardRef<
ComponentRef<typeof DialogPrimitive.Overlay>,
ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 jsonjoy',
className,
)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = forwardRef<
ComponentRef<typeof DialogPrimitive.Content>,
ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => {
const dialogDescriptionId = useId();
return (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className,
)}
aria-describedby={dialogDescriptionId}
{...props}
>
{children}
<DialogPrimitive.Description
id={dialogDescriptionId}
className="sr-only"
>
Dialog content
</DialogPrimitive.Description>
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
);
});
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({
className,
...props
}: HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col space-y-1.5 text-center sm:text-left',
className,
)}
{...props}
/>
);
DialogHeader.displayName = 'DialogHeader';
const DialogFooter = ({
className,
...props
}: HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
'flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2',
className,
)}
{...props}
/>
);
DialogFooter.displayName = 'DialogFooter';
const DialogTitle = forwardRef<
ComponentRef<typeof DialogPrimitive.Title>,
ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
'text-lg font-semibold leading-none tracking-tight',
className,
)}
{...props}
/>
));
DialogTitle.displayName = DialogPrimitive.Title.displayName;
const DialogDescription = forwardRef<
ComponentRef<typeof DialogPrimitive.Description>,
ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn('text-sm text-muted-foreground', className)}
{...props}
/>
));
DialogDescription.displayName = DialogPrimitive.Description.displayName;
export {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogOverlay,
DialogPortal,
DialogTitle,
DialogTrigger,
};

View File

@ -1,21 +0,0 @@
import { forwardRef, type ComponentProps } from 'react';
import { cn } from '../../lib/utils.ts';
const Input = forwardRef<HTMLInputElement, ComponentProps<'input'>>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
'flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-base ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
className,
)}
ref={ref}
{...props}
/>
);
},
);
Input.displayName = 'Input';
export { Input };

View File

@ -1,28 +0,0 @@
import * as LabelPrimitive from '@radix-ui/react-label';
import { cva, type VariantProps } from 'class-variance-authority';
import {
forwardRef,
type ComponentPropsWithoutRef,
type ComponentRef,
} from 'react';
import { cn } from '../../lib/utils.ts';
const labelVariants = cva(
'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70',
);
const Label = forwardRef<
ComponentRef<typeof LabelPrimitive.Root>,
ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
));
Label.displayName = LabelPrimitive.Root.displayName;
export { Label };

View File

@ -1,163 +0,0 @@
import * as SelectPrimitive from '@radix-ui/react-select';
import { Check, ChevronDown, ChevronUp } from 'lucide-react';
import {
forwardRef,
type ComponentPropsWithoutRef,
type ComponentRef,
} from 'react';
import { cn } from '../../lib/utils.ts';
const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value;
const SelectTrigger = forwardRef<
ComponentRef<typeof SelectPrimitive.Trigger>,
ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
'flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-hidden focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1',
className,
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectScrollUpButton = forwardRef<
ComponentRef<typeof SelectPrimitive.ScrollUpButton>,
ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollUpButton
ref={ref}
className={cn(
'flex cursor-default items-center justify-center py-1',
className,
)}
{...props}
>
<ChevronUp className="h-4 w-4" />
</SelectPrimitive.ScrollUpButton>
));
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
const SelectScrollDownButton = forwardRef<
ComponentRef<typeof SelectPrimitive.ScrollDownButton>,
ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
>(({ className, ...props }, ref) => (
<SelectPrimitive.ScrollDownButton
ref={ref}
className={cn(
'flex cursor-default items-center justify-center py-1',
className,
)}
{...props}
>
<ChevronDown className="h-4 w-4" />
</SelectPrimitive.ScrollDownButton>
));
SelectScrollDownButton.displayName =
SelectPrimitive.ScrollDownButton.displayName;
const SelectContent = forwardRef<
ComponentRef<typeof SelectPrimitive.Content>,
ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
>(({ className, children, position = 'popper', ...props }, ref) => (
<SelectPrimitive.Portal>
<SelectPrimitive.Content
ref={ref}
className={cn(
'relative z-50 max-h-96 min-w-32 overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
position === 'popper' &&
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
className,
'jsonjoy',
)}
position={position}
{...props}
>
<SelectScrollUpButton />
<SelectPrimitive.Viewport
className={cn(
'p-1',
position === 'popper' &&
'h-(--radix-select-trigger-height) w-full min-w-(--radix-select-trigger-width)',
)}
>
{children}
</SelectPrimitive.Viewport>
<SelectScrollDownButton />
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
));
SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = forwardRef<
ComponentRef<typeof SelectPrimitive.Label>,
ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Label
ref={ref}
className={cn('py-1.5 pl-8 pr-2 text-sm font-semibold', className)}
{...props}
/>
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = forwardRef<
ComponentRef<typeof SelectPrimitive.Item>,
ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
>(({ className, children, ...props }, ref) => (
<SelectPrimitive.Item
ref={ref}
className={cn(
'relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-hidden focus:bg-accent focus:text-accent-foreground data-disabled:pointer-events-none data-disabled:opacity-50',
className,
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<SelectPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</SelectPrimitive.ItemIndicator>
</span>
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
));
SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = forwardRef<
ComponentRef<typeof SelectPrimitive.Separator>,
ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
>(({ className, ...props }, ref) => (
<SelectPrimitive.Separator
ref={ref}
className={cn('-mx-1 my-1 h-px bg-muted', className)}
{...props}
/>
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export {
Select,
SelectContent,
SelectGroup,
SelectItem,
SelectLabel,
SelectScrollDownButton,
SelectScrollUpButton,
SelectSeparator,
SelectTrigger,
SelectValue,
};

View File

@ -1,31 +0,0 @@
import * as SwitchPrimitives from '@radix-ui/react-switch';
import {
forwardRef,
type ComponentPropsWithoutRef,
type ComponentRef,
} from 'react';
import { cn } from '../../lib/utils.ts';
const Switch = forwardRef<
ComponentRef<typeof SwitchPrimitives.Root>,
ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
>(({ className, ...props }, ref) => (
<SwitchPrimitives.Root
className={cn(
'peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input',
className,
)}
{...props}
ref={ref}
>
<SwitchPrimitives.Thumb
className={cn(
'pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0',
)}
/>
</SwitchPrimitives.Root>
));
Switch.displayName = SwitchPrimitives.Root.displayName;
export { Switch };

View File

@ -1,57 +0,0 @@
import * as TabsPrimitive from '@radix-ui/react-tabs';
import {
forwardRef,
type ComponentPropsWithoutRef,
type ComponentRef,
} from 'react';
import { cn } from '../../lib/utils.ts';
const Tabs = TabsPrimitive.Root;
const TabsList = forwardRef<
ComponentRef<typeof TabsPrimitive.List>,
ComponentPropsWithoutRef<typeof TabsPrimitive.List>
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
'inline-flex h-10 items-center justify-center rounded-md bg-muted p-1 text-muted-foreground',
className,
)}
{...props}
/>
));
TabsList.displayName = TabsPrimitive.List.displayName;
const TabsTrigger = forwardRef<
ComponentRef<typeof TabsPrimitive.Trigger>,
ComponentPropsWithoutRef<typeof TabsPrimitive.Trigger>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Trigger
ref={ref}
className={cn(
'inline-flex items-center justify-center whitespace-nowrap rounded-sm px-3 py-1.5 text-sm font-medium ring-offset-background transition-all focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:bg-background data-[state=active]:text-foreground data-[state=active]:shadow-xs',
className,
)}
{...props}
/>
));
TabsTrigger.displayName = TabsPrimitive.Trigger.displayName;
const TabsContent = forwardRef<
ComponentRef<typeof TabsPrimitive.Content>,
ComponentPropsWithoutRef<typeof TabsPrimitive.Content>
>(({ className, ...props }, ref) => (
<TabsPrimitive.Content
ref={ref}
className={cn(
'mt-2 ring-offset-background focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2',
className,
)}
{...props}
/>
));
TabsContent.displayName = TabsPrimitive.Content.displayName;
export { Tabs, TabsContent, TabsList, TabsTrigger };

View File

@ -1,32 +0,0 @@
import * as TooltipPrimitive from '@radix-ui/react-tooltip';
import {
forwardRef,
type ComponentPropsWithoutRef,
type ComponentRef,
} from 'react';
import { cn } from '../../lib/utils.ts';
const TooltipProvider = TooltipPrimitive.Provider;
const Tooltip = TooltipPrimitive.Root;
const TooltipTrigger = TooltipPrimitive.Trigger;
const TooltipContent = forwardRef<
ComponentRef<typeof TooltipPrimitive.Content>,
ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<TooltipPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className,
)}
{...props}
/>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;
export { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger };

View File

@ -1,6 +1,6 @@
import type * as Monaco from 'monaco-editor';
import { useEffect, useState } from 'react';
import type { JSONSchema } from '../types/jsonSchema.ts';
import type { JSONSchema } from '../types/json-schema.js';
export interface MonacoEditorOptions {
minimap?: { enabled: boolean };

View File

@ -3,21 +3,21 @@
import JsonSchemaEditor, {
type JsonSchemaEditorProps,
} from './components/SchemaEditor/JsonSchemaEditor';
} from './components/schema-editor/json-schema-editor';
import JsonSchemaVisualizer, {
type JsonSchemaVisualizerProps,
} from './components/SchemaEditor/JsonSchemaVisualizer';
} from './components/schema-editor/json-schema-visualizer';
import SchemaVisualEditor, {
type SchemaVisualEditorProps,
} from './components/SchemaEditor/SchemaVisualEditor';
} from './components/schema-editor/schema-visual-editor';
export * from './i18n/locales/de';
export * from './i18n/locales/en';
export * from './i18n/translation-context';
export * from './i18n/translation-keys';
export * from './components/features/JsonValidator';
export * from './components/features/SchemaInferencer';
export * from './components/features/json-validator';
export * from './components/features/schema-inferencer';
export {
JsonSchemaEditor,
@ -28,4 +28,4 @@ export {
type SchemaVisualEditorProps,
};
export type { JSONSchema, baseSchema } from './types/jsonSchema.ts';
export type { JSONSchema, baseSchema } from './types/json-schema';

View File

@ -2,8 +2,8 @@ import type {
JSONSchema,
NewField,
ObjectJSONSchema,
} from '../types/jsonSchema.ts';
import { isBooleanSchema, isObjectSchema } from '../types/jsonSchema.ts';
} from '../types/json-schema';
import { isBooleanSchema, isObjectSchema } from '../types/json-schema';
export type Property = {
name: string;

View File

@ -1,4 +1,4 @@
import { asObjectSchema, type JSONSchema } from '../types/jsonSchema.ts';
import { asObjectSchema, type JSONSchema } from '../types/json-schema';
/**
* Merges two JSON schemas.

View File

@ -1,7 +1,7 @@
import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';
import type { Translation } from '../i18n/translation-keys.ts';
import type { SchemaType } from '../types/jsonSchema.ts';
import type { SchemaType } from '../types/json-schema';
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));

View File

@ -1,6 +1,6 @@
import z from 'zod';
import type { Translation } from '../i18n/translation-keys.ts';
import { baseSchema, type JSONSchema } from './jsonSchema';
import type { Translation } from '../i18n/translation-keys';
import { baseSchema, type JSONSchema } from './json-schema';
function refineRangeConsistency(
min: number | undefined,

View File

@ -1,6 +1,6 @@
import Ajv from 'ajv';
import addFormats from 'ajv-formats';
import type { JSONSchema } from '../types/jsonSchema.ts';
import type { JSONSchema } from '../types/json-schema.js';
// Initialize Ajv with all supported formats and meta-schemas
const ajv = new Ajv({

View File

@ -1,4 +1,5 @@
import { LlmIcon } from '@/components/svg-icon';
import message from '@/components/ui/message';
import { LlmModelType } from '@/constants/knowledge';
import { ResponseGetType } from '@/interfaces/database/base';
import {
@ -16,7 +17,6 @@ import userService from '@/services/user-service';
import { sortLLmFactoryListBySpecifiedOrder } from '@/utils/common-util';
import { getLLMIconName, getRealModelName } from '@/utils/llm-util';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Flex, message } from 'antd';
import { DefaultOptionType } from 'antd/es/select';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
@ -59,7 +59,7 @@ export const useSelectLlmOptions = () => {
function buildLlmOptionsWithIcon(x: IThirdOAIModel) {
return {
label: (
<Flex align="center" gap={6}>
<div className="flex items-center justify-center gap-6">
<LlmIcon
name={getLLMIconName(x.fid, x.llm_name)}
width={26}
@ -67,7 +67,7 @@ function buildLlmOptionsWithIcon(x: IThirdOAIModel) {
size={'small'}
/>
<span>{getRealModelName(x.llm_name)}</span>
</Flex>
</div>
),
value: `${x.llm_name}@${x.fid}`,
disabled: !x.available,
@ -81,7 +81,6 @@ export const useSelectLlmOptionsByModelType = () => {
const groupImage2TextOptions = useCallback(() => {
const modelType = LlmModelType.Image2text;
const modelTag = modelType.toUpperCase();
return Object.entries(llmInfo)
.map(([key, value]) => {
return {
@ -91,7 +90,8 @@ export const useSelectLlmOptionsByModelType = () => {
(x) =>
(x.model_type.includes(modelType) ||
(x.tags && x.tags.includes(modelTag))) &&
x.available,
x.available &&
x.status === '1',
)
.map(buildLlmOptionsWithIcon),
};
@ -141,7 +141,6 @@ export const useComposeLlmOptionsByModelTypes = (
modelTypes: LlmModelType[],
) => {
const allOptions = useSelectLlmOptionsByModelType();
return modelTypes.reduce<
(DefaultOptionType & {
options: {
@ -359,6 +358,35 @@ export const useDeleteLlm = () => {
return { data, loading, deleteLlm: mutateAsync };
};
export const useEnableLlm = () => {
const queryClient = useQueryClient();
const { t } = useTranslation();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: ['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.enable_llm(reqParam);
if (data.code === 0) {
queryClient.invalidateQueries({ queryKey: ['myLlmList'] });
queryClient.invalidateQueries({ queryKey: ['myLlmListDetailed'] });
queryClient.invalidateQueries({ queryKey: ['factoryList'] });
message.success(t('message.modified'));
}
return data.code;
},
});
return { data, loading, enableLlm: mutateAsync };
};
export const useDeleteFactory = () => {
const queryClient = useQueryClient();
const { t } = useTranslation();

View File

@ -104,6 +104,7 @@ export interface ITenantInfo {
tenant_id: string;
chat_id: string;
speech2text_id: string;
rerank_id?: string;
tts_id: string;
}

View File

@ -37,5 +37,6 @@ export interface IMyLlmValue {
export interface Llm {
name: string;
type: string;
status: '0' | '1';
used_token: number;
}

View File

@ -680,6 +680,9 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
tocEnhanceTip: ` During the parsing of the document, table of contents information was generated (see the 'Enable Table of Contents Extraction' option in the General method). This allows the large model to return table of contents items relevant to the user's query, thereby using these items to retrieve related chunks and apply weighting to these chunks during the sorting process. This approach is derived from mimicking the behavioral logic of how humans search for knowledge in books.`,
},
setting: {
save: 'Save',
search: 'Search',
availableModels: 'Available models',
profile: 'Profile',
avatar: 'Avatar',
avatarTip: 'This will be displayed on your profile.',
@ -693,7 +696,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
passwordDescription:
'Please enter your current password to change your password.',
model: 'Model providers',
modelDescription: 'Configure model parameters and API KEY here.',
systemModelDescription: 'Please complete these settings before beginning',
team: 'Team',
system: 'System',
logout: 'Log out',
@ -726,7 +729,7 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
cancel: 'Cancel',
addedModels: 'Added models',
modelsToBeAdded: 'Models to be added',
addTheModel: 'Add Model',
addTheModel: 'Add',
apiKey: 'API-Key',
apiKeyMessage:
'Please enter the API key (for locally deployed model,ignore this).',
@ -742,21 +745,20 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
tongyiBaseUrlPlaceholder: '(International users only, please see tip)',
modify: 'Modify',
systemModelSettings: 'Set default models',
chatModel: 'Chat model',
chatModelTip:
'The default chat model for each newly created knowledge base.',
embeddingModel: 'Embedding model',
chatModel: 'LLM',
chatModelTip: 'The default LLM for each newly created knowledge base.',
embeddingModel: 'Embedding',
embeddingModelTip:
'The default embedding model for each newly created knowledge base. If you cannot find an embedding model from the dropdown, check if you are using RAGFlow slim edition (which does not include embedding models) or check https://ragflow.io/docs/dev/supported_models to see if your model provider supports this model.',
img2txtModel: 'Img2txt model',
img2txtModel: 'VLM',
img2txtModelTip:
'The default img2txt model for each newly created knowledge base. It describes a picture or video. If you cannot find a model from the dropdown, check https://ragflow.io/docs/dev/supported_models to see if your model provider supports this model.',
sequence2txtModel: 'Speech2txt model',
'The default VLM for each newly created knowledge base. It describes a picture or video. If you cannot find a model from the dropdown, check https://ragflow.io/docs/dev/supported_models to see if your model provider supports this model.',
sequence2txtModel: 'ASR',
sequence2txtModelTip:
'The default ASR model for each newly created knowledgebase. Use this model to translate voices to corresponding text.',
rerankModel: 'Rerank model',
rerankModel: 'Rerank',
rerankModelTip: `The default rerank model for reranking chunks. If you cannot find a model from the dropdown, check https://ragflow.io/docs/dev/supported_models to see if your model provider supports this model.`,
ttsModel: 'TTS Model',
ttsModel: 'TTS',
ttsModelTip:
'The default text-to-speech model. If you cannot find a model from the dropdown, check https://ragflow.io/docs/dev/supported_models to see if your model provider supports this model.',
workspace: 'Workspace',

View File

@ -671,6 +671,9 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
tocEnhanceTip: `解析文档时生成了目录信息见General方法的启用目录抽取让大模型返回和用户问题相关的目录项从而利用目录项拿到相关chunk对这些chunk在排序中进行加权。这种方法来源于模仿人类查询书本中知识的行为逻辑`,
},
setting: {
save: '保存',
search: '搜索',
availableModels: '可选模型',
profile: '概要',
avatar: '头像',
avatarTip: '这会在你的个人主页展示',
@ -684,7 +687,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
password: '密码',
passwordDescription: '请输入您当前的密码以更改您的密码。',
model: '模型提供商',
modelDescription: '在此设置模型参数和 API KEY。',
systemModelDescription: '请在开始之前完成这些设置',
team: '团队',
system: '系统',
logout: '登出',
@ -715,7 +718,7 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
cancel: '取消',
addedModels: '添加了的模型',
modelsToBeAdded: '待添加的模型',
addTheModel: '添加模型',
addTheModel: '添加',
apiKey: 'API-Key',
apiKeyMessage: '请输入api key如果是本地部署的模型请忽略它',
apiKeyTip: 'API key可以通过注册相应的LLM供应商来获取。',
@ -729,21 +732,21 @@ General实体和关系提取提示来自 GitHub - microsoft/graphrag基于
tongyiBaseUrlPlaceholder: '(仅国际用户需要)',
modify: '修改',
systemModelSettings: '设置默认模型',
chatModel: '聊天模型',
chatModel: 'LLM',
chatModelTip: '所有新创建的知识库都会使用默认的聊天模型。',
ttsModel: 'TTS模型',
ttsModel: 'TTS',
ttsModelTip:
'默认的tts模型会被用于在对话过程中请求语音生成时使用。如未显示可选模型请根据 https://ragflow.io/docs/dev/supported_models 确认你的模型供应商是否提供该模型。',
embeddingModel: '嵌入模型',
embeddingModel: 'Embedding',
embeddingModelTip:
'所有新创建的知识库使用的默认嵌入模型。如未显示可选模型,请检查你是否在使用 RAGFlow slim 版(不含嵌入模型);或根据 https://ragflow.io/docs/dev/supported_models 确认你的模型供应商是否提供该模型。',
img2txtModel: 'Img2txt模型',
img2txtModel: 'VLM',
img2txtModelTip:
'所有新创建的知识库都将使用默认的 img2txt 模型。 它可以描述图片或视频。如未显示可选模型,请根据 https://ragflow.io/docs/dev/supported_models 确认你的模型供应商是否提供该模型。',
sequence2txtModel: 'Speech2txt模型',
sequence2txtModel: 'ASR',
sequence2txtModelTip:
'所有新创建的知识库都将使用默认的 ASR 模型。 使用此模型将语音翻译为相应的文本。如未显示可选模型,请根据 https://ragflow.io/docs/dev/supported_models 确认你的模型供应商是否提供该模型。',
rerankModel: 'Rerank模型',
rerankModel: 'Rerank',
rerankModelTip: `默认的 reranking 模型。如未显示可选模型,请根据 https://ragflow.io/docs/dev/supported_models 确认你的模型供应商是否提供该模型。`,
workspace: '工作空间',
upgrade: '升级',

View File

@ -476,14 +476,13 @@ const useGraphStore = create<RFState>()(
},
updateNodeName: (id, name) => {
if (id) {
set({
nodes: get().nodes.map((node) => {
set((state) => {
for (const node of state.nodes) {
if (node.id === id) {
node.data.name = name;
break;
}
return node;
}),
}
});
}
},

View File

@ -31,9 +31,16 @@ export function TemplateCard({ data, showModal }: IProps) {
avatar={data.avatar ? data.avatar : 'https://github.com/shadcn.png'}
name={data?.title[language] || 'CN'}
></RAGFlowAvatar>
<div className="text-[18px] font-bold break-words hyphens-auto overflow-hidden"lang={language}>{data?.title[language]}</div>
<div
className="text-[18px] font-bold break-words hyphens-auto overflow-hidden"
lang={language}
>
{data?.title[language]}
</div>
</div>
<p className="break-words hypens-auto"lang={language}>{data?.description[language]}</p>
<p className="break-words hypens-auto" lang={language}>
{data?.description[language]}
</p>
<div className="group-hover:bg-gradient-to-t from-black/70 from-10% via-black/0 via-50% to-black/0 w-full h-full group-hover:block absolute top-0 left-0 hidden rounded-xl">
<Button
variant="default"

View File

@ -1,129 +0,0 @@
import { IModalManagerChildrenProps } from '@/components/modal-manager';
import { LLMFactory } from '@/constants/llm';
import { useTranslate } from '@/hooks/common-hooks';
import { Form, Input, Modal } from 'antd';
import { KeyboardEventHandler, useCallback, useEffect } from 'react';
import { ApiKeyPostBody } from '../../interface';
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
loading: boolean;
initialValue: string;
llmFactory: string;
editMode?: boolean;
onOk: (postBody: ApiKeyPostBody) => void;
showModal?(): void;
}
type FieldType = {
api_key?: string;
base_url?: string;
group_id?: string;
};
const modelsWithBaseUrl = [
LLMFactory.OpenAI,
LLMFactory.AzureOpenAI,
LLMFactory.TongYiQianWen,
];
const ApiKeyModal = ({
visible,
hideModal,
llmFactory,
loading,
initialValue,
editMode = false,
onOk,
}: IProps) => {
const [form] = Form.useForm();
const { t } = useTranslate('setting');
const handleOk = useCallback(async () => {
const ret = await form.validateFields();
return onOk(ret);
}, [form, onOk]);
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = useCallback(
async (e) => {
if (e.key === 'Enter') {
await handleOk();
}
},
[handleOk],
);
useEffect(() => {
if (visible) {
form.setFieldValue('api_key', initialValue);
}
}, [initialValue, form, visible]);
return (
<Modal
title={editMode ? t('editModel') : t('modify')}
open={visible}
onOk={handleOk}
onCancel={hideModal}
okButtonProps={{ loading }}
confirmLoading={loading}
>
<Form
name="basic"
labelCol={{ span: 6 }}
wrapperCol={{ span: 18 }}
style={{ maxWidth: 600 }}
autoComplete="off"
form={form}
>
<Form.Item<FieldType>
label={t('apiKey')}
name="api_key"
tooltip={t('apiKeyTip')}
rules={[{ required: true, message: t('apiKeyMessage') }]}
>
<Input onKeyDown={handleKeyDown} />
</Form.Item>
{modelsWithBaseUrl.some((x) => x === llmFactory) && (
<Form.Item<FieldType>
label={t('baseUrl')}
name="base_url"
tooltip={
llmFactory === LLMFactory.TongYiQianWen
? t('tongyiBaseUrlTip')
: t('baseUrlTip')
}
>
<Input
placeholder={
llmFactory === LLMFactory.TongYiQianWen
? t('tongyiBaseUrlPlaceholder')
: 'https://api.openai.com/v1'
}
onKeyDown={handleKeyDown}
/>
</Form.Item>
)}
{llmFactory?.toLowerCase() === 'Anthropic'.toLowerCase() && (
<Form.Item<FieldType>
label={t('baseUrl')}
name="base_url"
tooltip={t('baseUrlTip')}
>
<Input
placeholder="https://api.anthropic.com/v1"
onKeyDown={handleKeyDown}
/>
</Form.Item>
)}
{llmFactory?.toLowerCase() === 'Minimax'.toLowerCase() && (
<Form.Item<FieldType> label={'Group ID'} name="group_id">
<Input />
</Form.Item>
)}
</Form>
</Modal>
);
};
export default ApiKeyModal;

View File

@ -0,0 +1,170 @@
// src/components/ModelProviderCard.tsx
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/llm-hooks';
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, useHandleEnableLlm } from '../hooks';
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 { handleDeleteFactory } = useHandleDeleteFactory(item.name);
const handleApiKeyClick = () => {
clickApiKey(item.name);
};
const handleShowMoreClick = () => {
switchVisible();
};
return (
<div className={`w-full rounded-lg border border-border-default`}>
{/* Header */}
<div className="flex h-16 items-center justify-between p-4 cursor-pointer transition-colors">
<div className="flex items-center space-x-3">
<LlmIcon name={item.name} />
<div>
<h3 className="font-medium">{item.name}</h3>
</div>
</div>
<div className="flex items-center space-x-2">
<Button
onClick={(e) => {
e.stopPropagation();
handleApiKeyClick();
}}
className="px-3 py-1 text-sm bg-bg-input hover:bg-bg-input text-text-primary rounded-md transition-colors flex items-center space-x-1"
>
<SettingOutlined />
<span>
{isLocalLlmFactory(item.name) ? t('addTheModel') : 'API-Key'}
</span>
</Button>
<Button
onClick={(e) => {
e.stopPropagation();
handleShowMoreClick();
}}
className="px-3 py-1 text-sm bg-bg-input hover:bg-bg-input text-text-primary rounded-md transition-colors flex items-center space-x-1"
>
<span>{visible ? t('hideModels') : t('showMoreModels')}</span>
{visible ? <ChevronsDown /> : <ChevronsUp />}
</Button>
<Button
variant={'secondary'}
onClick={(e) => {
e.stopPropagation();
handleDeleteFactory();
}}
className="p-1 text-text-primary hover:text-state-error transition-colors"
>
<Trash2 />
</Button>
</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"
>
{tag}
</span>
))}
</div>
<div className="m-4 bg-bg-card rounded-lg max-h-96 overflow-auto scrollbar-auto">
<div className="">
{item.llm.map((model) => (
<div
key={model.name}
className="flex items-center border-b border-border-default 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 space-x-2">
{isLocalLlmFactory(item.name) && (
<Button
variant={'secondary'}
onClick={() => handleEditModel(model, item)}
className="p-1 text-text-primary transition-colors"
>
<EditOutlined />
</Button>
)}
<Switch
checked={model.status === '1'}
onCheckedChange={(value) => {
handleEnableLlm(model.name, value);
}}
/>
</div>
</div>
))}
</div>
</div>
</div>
)}
</div>
);
};

View File

@ -0,0 +1,194 @@
import {
SelectWithSearch,
SelectWithSearchFlagOptionType,
} from '@/components/originui/select-with-search';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { LlmModelType } from '@/constants/knowledge';
import { useTranslate } from '@/hooks/common-hooks';
import {
ISystemModelSettingSavingParams,
useComposeLlmOptionsByModelTypes,
} from '@/hooks/llm-hooks';
import { CircleQuestionMark } from 'lucide-react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useFetchSystemModelSettingOnMount } from '../hooks';
interface IProps {
loading: boolean;
onOk: (
payload: Omit<ISystemModelSettingSavingParams, 'tenant_id' | 'name'>,
) => void;
}
const SystemSetting = ({ onOk, loading }: IProps) => {
const { systemSetting: initialValues, allOptions } =
useFetchSystemModelSettingOnMount();
const { t } = useTranslate('setting');
const [formData, setFormData] = useState({
llm_id: '',
embd_id: '',
img2txt_id: '',
asr_id: '',
rerank_id: '',
tts_id: '',
});
const handleFieldChange = useCallback(
(field: string, value: string) => {
const updatedData = { ...formData, [field]: value || '' };
setFormData(updatedData);
console.log('updatedData', updatedData);
onOk(updatedData);
},
[formData, onOk],
);
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[],
tooltip: t('chatModelTip'),
},
{
id: 'embd_id',
label: t('embeddingModel'),
value: formData.embd_id,
options: allOptions[
LlmModelType.Embedding
] as SelectWithSearchFlagOptionType[],
tooltip: t('embeddingModelTip'),
},
{
id: 'img2txt_id',
label: t('img2txtModel'),
value: formData.img2txt_id,
options: allOptions[
LlmModelType.Image2text
] as SelectWithSearchFlagOptionType[],
tooltip: t('img2txtModelTip'),
},
{
id: 'asr_id',
label: t('sequence2txtModel'),
value: formData.asr_id,
options: allOptions[
LlmModelType.Speech2text
] as SelectWithSearchFlagOptionType[],
tooltip: t('sequence2txtModelTip'),
},
{
id: 'rerank_id',
label: t('rerankModel'),
value: formData.rerank_id,
options: allOptions[
LlmModelType.Rerank
] as SelectWithSearchFlagOptionType[],
tooltip: t('rerankModelTip'),
},
{
id: 'tts_id',
label: t('ttsModel'),
value: formData.tts_id,
options: allOptions[
LlmModelType.TTS
] as SelectWithSearchFlagOptionType[],
tooltip: t('ttsModelTip'),
},
];
}, [formData, modelOptions, t, allOptions]);
const Items = ({
label,
value,
options,
tooltip,
id,
isRequired,
}: {
id: string;
label: string;
value: string;
options: SelectWithSearchFlagOptionType[];
tooltip?: string;
isRequired?: boolean;
}) => {
return (
<div className="flex gap-3">
<label className="block text-sm font-medium text-text-primary 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-disabled text-xs"
/>
</TooltipTrigger>
</Tooltip>
)}
</label>
<SelectWithSearch
triggerClassName="w-3/4"
value={value}
options={options}
onChange={(value) => handleFieldChange(id, value)}
placeholder={t('common:selectPlaceholder')}
/>
</div>
);
};
return (
<div className="rounded-lg w-full">
<div className="flex flex-col py-4">
<div className="text-2xl font-semibold">{t('systemModelSettings')}</div>
<div className="text-sm text-text-secondary">
{t('systemModelDescription')}
</div>
</div>
<div className="px-7 py-6 space-y-6 max-h-[70vh] overflow-y-auto border rounded-lg">
{llmList.map((item) => (
<Items key={item.id} {...item} />
))}
</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> */}
</div>
);
};
export default SystemSetting;

View File

@ -0,0 +1,157 @@
// src/components/AvailableModels.tsx
import { LlmIcon } from '@/components/svg-icon';
import { Button } from '@/components/ui/button';
import { useTranslate } from '@/hooks/common-hooks';
import { useSelectLlmList } from '@/hooks/llm-hooks';
import { Plus, Search } from 'lucide-react';
import { FC, useMemo, useState } from 'react';
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 AvailableModels: FC<{
handleAddModel: (factory: string) => void;
}> = ({ handleAddModel }) => {
const { t } = useTranslate('setting');
const { factoryList } = useSelectLlmList();
const [searchTerm, setSearchTerm] = useState('');
const [selectedTag, setSelectedTag] = useState<string | null>(null);
// 过滤模型列表
const filteredModels = useMemo(() => {
return factoryList.filter((model) => {
const matchesSearch = model.name
.toLowerCase()
.includes(searchTerm.toLowerCase());
const matchesTag =
selectedTag === null ||
model.tags.split(',').some((tag) => tag.trim() === selectedTag);
return matchesSearch && matchesTag;
});
}, [factoryList, searchTerm, selectedTag]);
// 获取所有唯一的标签
const allTags = useMemo(() => {
const tagsSet = new Set<string>();
factoryList.forEach((model) => {
model.tags.split(',').forEach((tag) => tagsSet.add(tag.trim()));
});
return Array.from(tagsSet).sort();
}, [factoryList]);
const handleTagClick = (tag: string) => {
setSelectedTag(selectedTag === tag ? null : tag);
};
return (
<div className=" text-text-primary h-full p-4">
<div className="text-text-primary text-base mb-4">
{t('availableModels')}
</div>
{/* Search Bar */}
<div className="mb-6">
<div className="relative">
<input
type="text"
placeholder={t('search')}
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="w-full px-4 py-3 pl-10 bg-bg-input border border-border-default rounded-lg focus:outline-none focus:ring-1 focus:ring-border-button transition-colors"
/>
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 w-5 h-5 text-text-secondary" />
</div>
</div>
{/* Tags Filter */}
<div className="flex flex-wrap gap-2 mb-6">
<Button
variant={'secondary'}
onClick={() => setSelectedTag(null)}
className={`px-1 py-1 text-xs rounded-md bg-bg-card bg-bg-card h-5 transition-colors ${
selectedTag === null
? ' text-text-primary border border-text-primary'
: 'text-text-secondary bg-bg-input border-none'
}`}
>
All
</Button>
{allTags.map((tag) => (
<Button
variant={'secondary'}
key={tag}
onClick={() => handleTagClick(tag)}
className={`px-1 py-1 text-xs rounded-md bg-bg-card h-5 transition-colors ${
selectedTag === tag
? ' text-text-primary border border-text-primary'
: 'text-text-secondary border-none'
}`}
>
{tag}
</Button>
))}
</div>
{/* Models List */}
<div className="flex flex-col gap-4 overflow-auto h-[calc(100vh-300px)] scrollbar-auto">
{filteredModels.map((model) => (
<div
key={model.name}
className=" border border-border-default rounded-lg p-3 hover:bg-bg-input transition-colors"
>
<div className="flex items-center space-x-3 mb-3">
<LlmIcon name={model.name} imgClass="h-8 w-auto" />
<div className="flex-1">
<h3 className="font-medium truncate">{model.name}</h3>
</div>
<Button
className=" px-2 flex items-center gap-0 text-xs h-6 rounded-md transition-colors"
onClick={() => handleAddModel(model.name)}
>
<Plus size={12} />
{t('addTheModel')}
</Button>
</div>
<div className="flex flex-wrap gap-1 mb-3">
{sortTags(model.tags).map((tag, index) => (
<span
key={index}
className="px-1 flex items-center h-5 text-xs bg-bg-card text-text-secondary rounded-md"
>
{tag}
</span>
))}
</div>
</div>
))}
</div>
</div>
);
};

View File

@ -0,0 +1,27 @@
import { LlmItem, useSelectLlmList } from '@/hooks/llm-hooks';
import { ModelProviderCard } from './modal-card';
export const UsedModel = ({
handleAddModel,
handleEditModel,
}: {
handleAddModel: (factory: string) => void;
handleEditModel: (model: any, factory: LlmItem) => void;
}) => {
const { factoryList, myLlmList: llmList, loading } = useSelectLlmList();
return (
<div className="flex flex-col w-full gap-4 mb-4">
<div className="text-text-primary text-2xl mb-2 mt-4">Added models</div>
{llmList.map((llm) => {
return (
<ModelProviderCard
key={llm.name}
item={llm}
clickApiKey={handleAddModel}
handleEditModel={handleEditModel}
/>
);
})}
</div>
);
};

View File

@ -5,6 +5,7 @@ import {
useAddLlm,
useDeleteFactory,
useDeleteLlm,
useEnableLlm,
useSaveApiKey,
useSaveTenantInfo,
useSelectLlmOptionsByModelType,
@ -421,7 +422,7 @@ export const useHandleDeleteLlm = (llmFactory: string) => {
const { deleteLlm } = useDeleteLlm();
const showDeleteConfirm = useShowDeleteConfirm();
const handleDeleteLlm = (name: string) => () => {
const handleDeleteLlm = (name: string) => {
showDeleteConfirm({
onOk: async () => {
deleteLlm({ llm_factory: llmFactory, llm_name: name });
@ -432,6 +433,16 @@ export const useHandleDeleteLlm = (llmFactory: string) => {
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();

View File

@ -3,8 +3,6 @@
.factoryOperationWrapper {
text-align: right;
}
.modelItem {
}
.llmList {
padding-top: 10px;
}

View File

@ -1,47 +1,11 @@
import { ReactComponent as MoreModelIcon } from '@/assets/svg/more-model.svg';
import { LlmIcon } from '@/components/svg-icon';
import { useTheme } from '@/components/theme-provider';
import { LLMFactory } from '@/constants/llm';
import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
import {
LlmItem,
useFetchMyLlmListDetailed,
useSelectLlmList,
} from '@/hooks/llm-hooks';
import { getRealModelName } from '@/utils/llm-util';
import {
CloseCircleOutlined,
EditOutlined,
SettingOutlined,
} from '@ant-design/icons';
import {
Button,
Card,
Col,
Collapse,
CollapseProps,
Divider,
Flex,
List,
Row,
Space,
Spin,
Tag,
Tooltip,
Typography,
} from 'antd';
import { CircleHelp } from 'lucide-react';
import { LlmItem, useFetchMyLlmListDetailed } from '@/hooks/llm-hooks';
import { useCallback, useMemo } from 'react';
import SettingTitle from '../components/setting-title';
import { isLocalLlmFactory } from '../utils';
import ApiKeyModal from './api-key-modal';
import AzureOpenAIModal from './azure-openai-modal';
import BedrockModal from './bedrock-modal';
import FishAudioModal from './fish-audio-modal';
import GoogleModal from './google-modal';
import SystemSetting from './components/system-setting';
import { AvailableModels } from './components/un-add-model';
import { UsedModel } from './components/used-model';
import {
useHandleDeleteFactory,
useHandleDeleteLlm,
useSubmitApiKey,
useSubmitAzure,
useSubmitBedrock,
@ -55,165 +19,21 @@ import {
useSubmitVolcEngine,
useSubmityiyan,
} from './hooks';
import HunyuanModal from './hunyuan-modal';
import styles from './index.less';
import TencentCloudModal from './next-tencent-modal';
import OllamaModal from './ollama-modal';
import SparkModal from './spark-modal';
import SystemModelSettingModal from './system-model-setting-modal';
import VolcEngineModal from './volcengine-modal';
import YiyanModal from './yiyan-modal';
const { Text } = Typography;
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),
);
};
const ModelCard = ({ item, clickApiKey, handleEditModel }: IModelCardProps) => {
const { visible, switchVisible } = useSetModalState();
const { t } = useTranslate('setting');
const { theme } = useTheme();
const { handleDeleteLlm } = useHandleDeleteLlm(item.name);
const { handleDeleteFactory } = useHandleDeleteFactory(item.name);
const handleApiKeyClick = () => {
clickApiKey(item.name);
};
const handleShowMoreClick = () => {
switchVisible();
};
return (
<List.Item>
<Card
className={theme === 'dark' ? styles.addedCardDark : styles.addedCard}
>
<Row align={'middle'}>
<Col span={12}>
<Flex gap={'middle'} align="center">
<LlmIcon name={item.name} />
<Flex vertical gap={'small'}>
<b>{item.name}</b>
<Flex wrap="wrap">
{sortTags(item.tags).map((tag, index) => (
<Tag
key={index}
style={{
fontSize: '12px',
margin: '1px',
paddingInline: '4px',
}}
>
{tag}
</Tag>
))}
</Flex>
</Flex>
</Flex>
</Col>
<Col span={12} className={styles.factoryOperationWrapper}>
<Space size={'middle'}>
<Button onClick={handleApiKeyClick}>
<Flex align="center" gap={4}>
{isLocalLlmFactory(item.name) ||
item.name === LLMFactory.VolcEngine ||
item.name === LLMFactory.TencentHunYuan ||
item.name === LLMFactory.XunFeiSpark ||
item.name === LLMFactory.BaiduYiYan ||
item.name === LLMFactory.FishAudio ||
item.name === LLMFactory.TencentCloud ||
item.name === LLMFactory.GoogleCloud ||
item.name === LLMFactory.AzureOpenAI
? t('addTheModel')
: 'API-Key'}
<SettingOutlined />
</Flex>
</Button>
<Button onClick={handleShowMoreClick}>
<Flex align="center" gap={4}>
{visible ? t('hideModels') : t('showMoreModels')}
<MoreModelIcon />
</Flex>
</Button>
<Button type={'text'} onClick={handleDeleteFactory}>
<Flex align="center">
<CloseCircleOutlined style={{ color: '#D92D20' }} />
</Flex>
</Button>
</Space>
</Col>
</Row>
{visible && (
<List
size="small"
dataSource={item.llm}
className={styles.llmList}
renderItem={(model) => (
<List.Item>
<Space>
{getRealModelName(model.name)}
<Tag color="#b8b8b8">{model.type}</Tag>
{isLocalLlmFactory(item.name) && (
<Tooltip title={t('edit', { keyPrefix: 'common' })}>
<Button
type={'text'}
onClick={() => handleEditModel(model, item)}
>
<EditOutlined style={{ color: '#1890ff' }} />
</Button>
</Tooltip>
)}
<Tooltip title={t('delete', { keyPrefix: 'common' })}>
<Button type={'text'} onClick={handleDeleteLlm(model.name)}>
<CloseCircleOutlined style={{ color: '#D92D20' }} />
</Button>
</Tooltip>
</Space>
</List.Item>
)}
/>
)}
</Card>
</List.Item>
);
};
const UserSettingModel = () => {
const { factoryList, myLlmList: llmList, loading } = useSelectLlmList();
import ApiKeyModal from './modal/api-key-modal';
import AzureOpenAIModal from './modal/azure-openai-modal';
import BedrockModal from './modal/bedrock-modal';
import FishAudioModal from './modal/fish-audio-modal';
import GoogleModal from './modal/google-modal';
import HunyuanModal from './modal/hunyuan-modal';
import TencentCloudModal from './modal/next-tencent-modal';
import OllamaModal from './modal/ollama-modal';
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 { theme } = useTheme();
const {
saveApiKeyLoading,
initialApiKey,
@ -224,14 +44,6 @@ const UserSettingModel = () => {
hideApiKeyModal,
showApiKeyModal,
} = useSubmitApiKey();
const {
saveSystemModelSettingLoading,
onSystemSettingSavingOk,
systemSettingVisible,
hideSystemSettingModal,
showSystemSettingModal,
} = useSubmitSystemModelSetting();
const { t } = useTranslate('setting');
const {
llmAddingVisible,
hideLlmAddingModal,
@ -342,6 +154,7 @@ const UserSettingModel = () => {
const handleAddModel = useCallback(
(llmFactory: string) => {
console.log('handleAddModel', llmFactory);
if (isLocalLlmFactory(llmFactory)) {
showLlmAddingModal(llmFactory);
} else if (llmFactory in ModalMap) {
@ -378,116 +191,21 @@ const UserSettingModel = () => {
},
[showApiKeyModal, showLlmAddingModal, ModalMap, detailedLlmList],
);
const items: CollapseProps['items'] = [
{
key: '1',
label: t('addedModels'),
children: (
<List
grid={{ gutter: 16, column: 1 }}
dataSource={llmList}
renderItem={(item) => (
<ModelCard
item={item}
clickApiKey={handleAddModel}
handleEditModel={handleEditModel}
></ModelCard>
)}
/>
),
},
{
key: '2',
label: (
<div className="flex items-center gap-2">
{t('modelsToBeAdded')}
<Tooltip title={t('modelsToBeAddedTooltip')}>
<CircleHelp className="size-4" />
</Tooltip>
</div>
),
children: (
<List
grid={{
gutter: {
xs: 8,
sm: 10,
md: 12,
lg: 16,
xl: 20,
xxl: 24,
},
xs: 1,
sm: 1,
md: 2,
lg: 3,
xl: 4,
xxl: 8,
}}
dataSource={factoryList}
renderItem={(item) => (
<List.Item>
<Card
className={
theme === 'dark'
? styles.toBeAddedCardDark
: styles.toBeAddedCard
}
>
<Flex vertical gap={'middle'}>
<LlmIcon name={item.name} imgClass="h-12 w-auto" />
<Flex vertical gap={'middle'}>
<b>
<Text ellipsis={{ tooltip: item.name }}>{item.name}</Text>
</b>
<Flex wrap="wrap" style={{ minHeight: '50px' }}>
{sortTags(item.tags).map((tag, index) => (
<Tag
key={index}
style={{
fontSize: '8px',
margin: '1px',
paddingInline: '4px',
height: '22px',
}}
>
{tag}
</Tag>
))}
</Flex>
</Flex>
</Flex>
<Divider className={styles.modelDivider}></Divider>
<Button
type="link"
onClick={() => handleAddModel(item.name)}
className={styles.addButton}
>
{t('addTheModel')}
</Button>
</Card>
</List.Item>
)}
/>
),
},
];
return (
<section id="xx" className="w-full space-y-6">
<Spin spinning={loading}>
<section className={styles.modelContainer}>
<SettingTitle
title={t('model')}
description={t('modelDescription')}
showRightButton
clickButton={showSystemSettingModal}
></SettingTitle>
<Divider></Divider>
<Collapse defaultActiveKey={['1', '2']} ghost items={items} />
</section>
</Spin>
<div className="flex w-full">
<section className="flex flex-col gap-4 w-3/5 px-5 border-r border-border-button overflow-auto scrollbar-auto">
<SystemSetting
onOk={onSystemSettingSavingOk}
loading={saveSystemModelSettingLoading}
/>
<UsedModel
handleAddModel={handleAddModel}
handleEditModel={handleEditModel}
/>
</section>
<section className="flex flex-col w-2/5 overflow-auto scrollbar-auto">
<AvailableModels handleAddModel={handleAddModel} />
</section>
<ApiKeyModal
visible={apiKeyVisible}
hideModal={hideApiKeyModal}
@ -497,14 +215,6 @@ const UserSettingModel = () => {
onOk={onApiKeySavingOk}
llmFactory={llmFactory}
></ApiKeyModal>
{systemSettingVisible && (
<SystemModelSettingModal
visible={systemSettingVisible}
onOk={onSystemSettingSavingOk}
hideModal={hideSystemSettingModal}
loading={saveSystemModelSettingLoading}
></SystemModelSettingModal>
)}
<OllamaModal
visible={llmAddingVisible}
hideModal={hideLlmAddingModal}
@ -577,8 +287,7 @@ const UserSettingModel = () => {
loading={AzureAddingLoading}
llmFactory={LLMFactory.AzureOpenAI}
></AzureOpenAIModal>
</section>
</div>
);
};
export default UserSettingModel;
export default ModelProviders;

View File

@ -0,0 +1,584 @@
import { ReactComponent as MoreModelIcon } from '@/assets/svg/more-model.svg';
import { LlmIcon } from '@/components/svg-icon';
import { useTheme } from '@/components/theme-provider';
import { LLMFactory } from '@/constants/llm';
import { useSetModalState, useTranslate } from '@/hooks/common-hooks';
import {
LlmItem,
useFetchMyLlmListDetailed,
useSelectLlmList,
} from '@/hooks/llm-hooks';
import { getRealModelName } from '@/utils/llm-util';
import {
CloseCircleOutlined,
EditOutlined,
SettingOutlined,
} from '@ant-design/icons';
import {
Button,
Card,
Col,
Collapse,
CollapseProps,
Divider,
Flex,
List,
Row,
Space,
Spin,
Tag,
Tooltip,
Typography,
} from 'antd';
import { CircleHelp } from 'lucide-react';
import { useCallback, useMemo } from 'react';
import SettingTitle from '../components/setting-title';
import { isLocalLlmFactory } from '../utils';
import {
useHandleDeleteFactory,
useHandleDeleteLlm,
useSubmitApiKey,
useSubmitAzure,
useSubmitBedrock,
useSubmitFishAudio,
useSubmitGoogle,
useSubmitHunyuan,
useSubmitOllama,
useSubmitSpark,
useSubmitSystemModelSetting,
useSubmitTencentCloud,
useSubmitVolcEngine,
useSubmityiyan,
} from './hooks';
import styles from './index.less';
import ApiKeyModal from './modal/api-key-modal';
import AzureOpenAIModal from './modal/azure-openai-modal';
import BedrockModal from './modal/bedrock-modal';
import FishAudioModal from './modal/fish-audio-modal';
import GoogleModal from './modal/google-modal';
import HunyuanModal from './modal/hunyuan-modal';
import TencentCloudModal from './modal/next-tencent-modal';
import OllamaModal from './modal/ollama-modal';
import SparkModal from './modal/spark-modal';
import SystemModelSettingModal from './modal/system-model-setting-modal';
import VolcEngineModal from './modal/volcengine-modal';
import YiyanModal from './modal/yiyan-modal';
const { Text } = Typography;
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),
);
};
const ModelCard = ({ item, clickApiKey, handleEditModel }: IModelCardProps) => {
const { visible, switchVisible } = useSetModalState();
const { t } = useTranslate('setting');
const { theme } = useTheme();
const { handleDeleteLlm } = useHandleDeleteLlm(item.name);
const { handleDeleteFactory } = useHandleDeleteFactory(item.name);
const handleApiKeyClick = () => {
clickApiKey(item.name);
};
const handleShowMoreClick = () => {
switchVisible();
};
return (
<List.Item>
<Card
className={theme === 'dark' ? styles.addedCardDark : styles.addedCard}
>
<Row align={'middle'}>
<Col span={12}>
<Flex gap={'middle'} align="center">
<LlmIcon name={item.name} />
<Flex vertical gap={'small'}>
<b>{item.name}</b>
<Flex wrap="wrap">
{sortTags(item.tags).map((tag, index) => (
<Tag
key={index}
style={{
fontSize: '12px',
margin: '1px',
paddingInline: '4px',
}}
>
{tag}
</Tag>
))}
</Flex>
</Flex>
</Flex>
</Col>
<Col span={12} className={styles.factoryOperationWrapper}>
<Space size={'middle'}>
<Button onClick={handleApiKeyClick}>
<Flex align="center" gap={4}>
{isLocalLlmFactory(item.name) ||
item.name === LLMFactory.VolcEngine ||
item.name === LLMFactory.TencentHunYuan ||
item.name === LLMFactory.XunFeiSpark ||
item.name === LLMFactory.BaiduYiYan ||
item.name === LLMFactory.FishAudio ||
item.name === LLMFactory.TencentCloud ||
item.name === LLMFactory.GoogleCloud ||
item.name === LLMFactory.AzureOpenAI
? t('addTheModel')
: 'API-Key'}
<SettingOutlined />
</Flex>
</Button>
<Button onClick={handleShowMoreClick}>
<Flex align="center" gap={4}>
{visible ? t('hideModels') : t('showMoreModels')}
<MoreModelIcon />
</Flex>
</Button>
<Button type={'text'} onClick={handleDeleteFactory}>
<Flex align="center">
<CloseCircleOutlined style={{ color: '#D92D20' }} />
</Flex>
</Button>
</Space>
</Col>
</Row>
{visible && (
<List
size="small"
dataSource={item.llm}
className={styles.llmList}
renderItem={(model) => (
<List.Item>
<Space>
{getRealModelName(model.name)}
<Tag color="#b8b8b8">{model.type}</Tag>
{isLocalLlmFactory(item.name) && (
<Tooltip title={t('edit', { keyPrefix: 'common' })}>
<Button
type={'text'}
onClick={() => handleEditModel(model, item)}
>
<EditOutlined style={{ color: '#1890ff' }} />
</Button>
</Tooltip>
)}
<Tooltip title={t('delete', { keyPrefix: 'common' })}>
<Button type={'text'} onClick={handleDeleteLlm(model.name)}>
<CloseCircleOutlined style={{ color: '#D92D20' }} />
</Button>
</Tooltip>
</Space>
</List.Item>
)}
/>
)}
</Card>
</List.Item>
);
};
const UserSettingModel = () => {
const { factoryList, myLlmList: llmList, loading } = useSelectLlmList();
const { data: detailedLlmList } = useFetchMyLlmListDetailed();
const { theme } = useTheme();
const {
saveApiKeyLoading,
initialApiKey,
llmFactory,
editMode,
onApiKeySavingOk,
apiKeyVisible,
hideApiKeyModal,
showApiKeyModal,
} = useSubmitApiKey();
const {
saveSystemModelSettingLoading,
onSystemSettingSavingOk,
systemSettingVisible,
hideSystemSettingModal,
showSystemSettingModal,
} = useSubmitSystemModelSetting();
const { t } = useTranslate('setting');
const {
llmAddingVisible,
hideLlmAddingModal,
showLlmAddingModal,
onLlmAddingOk,
llmAddingLoading,
editMode: llmEditMode,
initialValues: llmInitialValues,
selectedLlmFactory,
} = useSubmitOllama();
const {
volcAddingVisible,
hideVolcAddingModal,
showVolcAddingModal,
onVolcAddingOk,
volcAddingLoading,
} = useSubmitVolcEngine();
const {
HunyuanAddingVisible,
hideHunyuanAddingModal,
showHunyuanAddingModal,
onHunyuanAddingOk,
HunyuanAddingLoading,
} = useSubmitHunyuan();
const {
GoogleAddingVisible,
hideGoogleAddingModal,
showGoogleAddingModal,
onGoogleAddingOk,
GoogleAddingLoading,
} = useSubmitGoogle();
const {
TencentCloudAddingVisible,
hideTencentCloudAddingModal,
showTencentCloudAddingModal,
onTencentCloudAddingOk,
TencentCloudAddingLoading,
} = useSubmitTencentCloud();
const {
SparkAddingVisible,
hideSparkAddingModal,
showSparkAddingModal,
onSparkAddingOk,
SparkAddingLoading,
} = useSubmitSpark();
const {
yiyanAddingVisible,
hideyiyanAddingModal,
showyiyanAddingModal,
onyiyanAddingOk,
yiyanAddingLoading,
} = useSubmityiyan();
const {
FishAudioAddingVisible,
hideFishAudioAddingModal,
showFishAudioAddingModal,
onFishAudioAddingOk,
FishAudioAddingLoading,
} = useSubmitFishAudio();
const {
bedrockAddingLoading,
onBedrockAddingOk,
bedrockAddingVisible,
hideBedrockAddingModal,
showBedrockAddingModal,
} = useSubmitBedrock();
const {
AzureAddingVisible,
hideAzureAddingModal,
showAzureAddingModal,
onAzureAddingOk,
AzureAddingLoading,
} = useSubmitAzure();
const ModalMap = useMemo(
() => ({
[LLMFactory.Bedrock]: showBedrockAddingModal,
[LLMFactory.VolcEngine]: showVolcAddingModal,
[LLMFactory.TencentHunYuan]: showHunyuanAddingModal,
[LLMFactory.XunFeiSpark]: showSparkAddingModal,
[LLMFactory.BaiduYiYan]: showyiyanAddingModal,
[LLMFactory.FishAudio]: showFishAudioAddingModal,
[LLMFactory.TencentCloud]: showTencentCloudAddingModal,
[LLMFactory.GoogleCloud]: showGoogleAddingModal,
[LLMFactory.AzureOpenAI]: showAzureAddingModal,
}),
[
showBedrockAddingModal,
showVolcAddingModal,
showHunyuanAddingModal,
showTencentCloudAddingModal,
showSparkAddingModal,
showyiyanAddingModal,
showFishAudioAddingModal,
showGoogleAddingModal,
showAzureAddingModal,
],
);
const handleAddModel = useCallback(
(llmFactory: string) => {
if (isLocalLlmFactory(llmFactory)) {
showLlmAddingModal(llmFactory);
} else if (llmFactory in ModalMap) {
ModalMap[llmFactory as keyof typeof ModalMap]();
} else {
showApiKeyModal({ llm_factory: llmFactory });
}
},
[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 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],
);
const items: CollapseProps['items'] = [
{
key: '1',
label: t('addedModels'),
children: (
<List
grid={{ gutter: 16, column: 1 }}
dataSource={llmList}
renderItem={(item) => (
<ModelCard
item={item}
clickApiKey={handleAddModel}
handleEditModel={handleEditModel}
></ModelCard>
)}
/>
),
},
{
key: '2',
label: (
<div className="flex items-center gap-2">
{t('modelsToBeAdded')}
<Tooltip title={t('modelsToBeAddedTooltip')}>
<CircleHelp className="size-4" />
</Tooltip>
</div>
),
children: (
<List
grid={{
gutter: {
xs: 8,
sm: 10,
md: 12,
lg: 16,
xl: 20,
xxl: 24,
},
xs: 1,
sm: 1,
md: 2,
lg: 3,
xl: 4,
xxl: 8,
}}
dataSource={factoryList}
renderItem={(item) => (
<List.Item>
<Card
className={
theme === 'dark'
? styles.toBeAddedCardDark
: styles.toBeAddedCard
}
>
<Flex vertical gap={'middle'}>
<LlmIcon name={item.name} imgClass="h-12 w-auto" />
<Flex vertical gap={'middle'}>
<b>
<Text ellipsis={{ tooltip: item.name }}>{item.name}</Text>
</b>
<Flex wrap="wrap" style={{ minHeight: '50px' }}>
{sortTags(item.tags).map((tag, index) => (
<Tag
key={index}
style={{
fontSize: '8px',
margin: '1px',
paddingInline: '4px',
height: '22px',
}}
>
{tag}
</Tag>
))}
</Flex>
</Flex>
</Flex>
<Divider className={styles.modelDivider}></Divider>
<Button
type="link"
onClick={() => handleAddModel(item.name)}
className={styles.addButton}
>
{t('addTheModel')}
</Button>
</Card>
</List.Item>
)}
/>
),
},
];
return (
<section id="xx" className="w-full space-y-6">
<Spin spinning={loading}>
<section className={styles.modelContainer}>
<SettingTitle
title={t('model')}
description={t('modelDescription')}
showRightButton
clickButton={showSystemSettingModal}
></SettingTitle>
<Divider></Divider>
<Collapse defaultActiveKey={['1', '2']} ghost items={items} />
</section>
</Spin>
<ApiKeyModal
visible={apiKeyVisible}
hideModal={hideApiKeyModal}
loading={saveApiKeyLoading}
initialValue={initialApiKey}
editMode={editMode}
onOk={onApiKeySavingOk}
llmFactory={llmFactory}
></ApiKeyModal>
{systemSettingVisible && (
<SystemModelSettingModal
visible={systemSettingVisible}
onOk={onSystemSettingSavingOk}
hideModal={hideSystemSettingModal}
loading={saveSystemModelSettingLoading}
></SystemModelSettingModal>
)}
<OllamaModal
visible={llmAddingVisible}
hideModal={hideLlmAddingModal}
onOk={onLlmAddingOk}
loading={llmAddingLoading}
editMode={llmEditMode}
initialValues={llmInitialValues}
llmFactory={selectedLlmFactory}
></OllamaModal>
<VolcEngineModal
visible={volcAddingVisible}
hideModal={hideVolcAddingModal}
onOk={onVolcAddingOk}
loading={volcAddingLoading}
llmFactory={LLMFactory.VolcEngine}
></VolcEngineModal>
<HunyuanModal
visible={HunyuanAddingVisible}
hideModal={hideHunyuanAddingModal}
onOk={onHunyuanAddingOk}
loading={HunyuanAddingLoading}
llmFactory={LLMFactory.TencentHunYuan}
></HunyuanModal>
<GoogleModal
visible={GoogleAddingVisible}
hideModal={hideGoogleAddingModal}
onOk={onGoogleAddingOk}
loading={GoogleAddingLoading}
llmFactory={LLMFactory.GoogleCloud}
></GoogleModal>
<TencentCloudModal
visible={TencentCloudAddingVisible}
hideModal={hideTencentCloudAddingModal}
onOk={onTencentCloudAddingOk}
loading={TencentCloudAddingLoading}
llmFactory={LLMFactory.TencentCloud}
></TencentCloudModal>
<SparkModal
visible={SparkAddingVisible}
hideModal={hideSparkAddingModal}
onOk={onSparkAddingOk}
loading={SparkAddingLoading}
llmFactory={LLMFactory.XunFeiSpark}
></SparkModal>
<YiyanModal
visible={yiyanAddingVisible}
hideModal={hideyiyanAddingModal}
onOk={onyiyanAddingOk}
loading={yiyanAddingLoading}
llmFactory={LLMFactory.BaiduYiYan}
></YiyanModal>
<FishAudioModal
visible={FishAudioAddingVisible}
hideModal={hideFishAudioAddingModal}
onOk={onFishAudioAddingOk}
loading={FishAudioAddingLoading}
llmFactory={LLMFactory.FishAudio}
></FishAudioModal>
<BedrockModal
visible={bedrockAddingVisible}
hideModal={hideBedrockAddingModal}
onOk={onBedrockAddingOk}
loading={bedrockAddingLoading}
llmFactory={LLMFactory.Bedrock}
></BedrockModal>
<AzureOpenAIModal
visible={AzureAddingVisible}
hideModal={hideAzureAddingModal}
onOk={onAzureAddingOk}
loading={AzureAddingLoading}
llmFactory={LLMFactory.AzureOpenAI}
></AzureOpenAIModal>
</section>
);
};
export default UserSettingModel;

View File

@ -0,0 +1,174 @@
import { IModalManagerChildrenProps } from '@/components/modal-manager';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { Modal } from '@/components/ui/modal/modal';
import { LLMFactory } from '@/constants/llm';
import { useTranslate } from '@/hooks/common-hooks';
import { KeyboardEventHandler, useCallback, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { ApiKeyPostBody } from '../../../interface';
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
loading: boolean;
initialValue: string;
llmFactory: string;
editMode?: boolean;
onOk: (postBody: ApiKeyPostBody) => void;
showModal?(): void;
}
type FieldType = {
api_key?: string;
base_url?: string;
group_id?: string;
};
const modelsWithBaseUrl = [
LLMFactory.OpenAI,
LLMFactory.AzureOpenAI,
LLMFactory.TongYiQianWen,
];
const ApiKeyModal = ({
visible,
hideModal,
llmFactory,
loading,
initialValue,
editMode = false,
onOk,
}: IProps) => {
const form = useForm<FieldType>();
const { t } = useTranslate('setting');
const handleOk = useCallback(async () => {
await form.handleSubmit((values) => onOk(values))();
}, [form, onOk]);
const handleKeyDown: KeyboardEventHandler<HTMLInputElement> = useCallback(
async (e) => {
if (e.key === 'Enter') {
await handleOk();
}
},
[handleOk],
);
useEffect(() => {
if (visible) {
form.setValue('api_key', initialValue);
}
}, [initialValue, form, visible]);
return (
<Modal
title={editMode ? t('editModel') : t('modify')}
open={visible}
onOpenChange={(open) => !open && hideModal()}
onOk={handleOk}
onCancel={hideModal}
confirmLoading={loading}
okText={t('save')}
cancelText={t('cancel')}
>
<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-primary">
{t('apiKey')}
<span className="ml-1 text-destructive">*</span>
</FormLabel>
<FormControl>
<Input
{...field}
onKeyDown={handleKeyDown}
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{modelsWithBaseUrl.some((x) => x === llmFactory) && (
<FormField
name="base_url"
render={({ field }) => (
<FormItem>
<FormLabel className="text-sm font-medium text-text-primary">
{t('baseUrl')}
</FormLabel>
<FormControl>
<Input
{...field}
placeholder={
llmFactory === LLMFactory.TongYiQianWen
? t('tongyiBaseUrlPlaceholder')
: 'https://api.openai.com/v1'
}
onKeyDown={handleKeyDown}
className="w-full"
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
{llmFactory?.toLowerCase() === 'Anthropic'.toLowerCase() && (
<FormField
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>
)}
/>
)}
{llmFactory?.toLowerCase() === 'Minimax'.toLowerCase() && (
<FormField
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>
)}
/>
)}
</div>
</Form>
</Modal>
);
};
export default ApiKeyModal;

View File

@ -3,7 +3,7 @@ import { IModalProps } from '@/interfaces/common';
import { IAddLlmRequestBody } from '@/interfaces/request/llm';
import { Flex, Form, Input, InputNumber, Modal, Select, Space } from 'antd';
import { useMemo } from 'react';
import { BedrockRegionList } from '../constant';
import { BedrockRegionList } from '../../constant';
type FieldType = IAddLlmRequestBody & {
bedrock_ak: string;

View File

@ -7,7 +7,7 @@ import {
} from '@/hooks/llm-hooks';
import { Form, Modal, Select } from 'antd';
import { useEffect } from 'react';
import { useFetchSystemModelSettingOnMount } from '../hooks';
import { useFetchSystemModelSettingOnMount } from '../../hooks';
interface IProps extends Omit<IModalManagerChildrenProps, 'showModal'> {
loading: boolean;

View File

@ -16,6 +16,7 @@ const {
set_tenant_info,
add_llm,
delete_llm,
enable_llm,
deleteFactory,
getSystemStatus,
getSystemVersion,
@ -79,6 +80,10 @@ const methods = {
url: delete_llm,
method: 'post',
},
enable_llm: {
url: enable_llm,
method: 'post',
},
getSystemStatus: {
url: getSystemStatus,
method: 'get',

View File

@ -31,6 +31,7 @@ export default {
set_api_key: `${api_host}/llm/set_api_key`,
add_llm: `${api_host}/llm/add_llm`,
delete_llm: `${api_host}/llm/delete_llm`,
enable_llm: `${api_host}/llm/enable_llm`,
deleteFactory: `${api_host}/llm/delete_factory`,
// plugin