mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-02-02 00:25:06 +08:00
Compare commits
6 Commits
ff2365b146
...
e9debfd74d
| Author | SHA1 | Date | |
|---|---|---|---|
| e9debfd74d | |||
| d8a7fb6f2b | |||
| c8a82da722 | |||
| 09dd786674 | |||
| 0ecccd27eb | |||
| 5a830ea68b |
@ -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:
|
||||
|
||||
@ -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}")
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
|
||||
@ -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 {
|
||||
@ -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 {
|
||||
@ -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;
|
||||
@ -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 {
|
||||
@ -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 {
|
||||
@ -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;
|
||||
@ -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
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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 {
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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,
|
||||
@ -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();
|
||||
@ -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;
|
||||
@ -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,
|
||||
@ -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';
|
||||
|
||||
@ -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 };
|
||||
@ -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 };
|
||||
@ -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,
|
||||
};
|
||||
@ -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 };
|
||||
@ -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 };
|
||||
@ -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,
|
||||
};
|
||||
@ -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 };
|
||||
@ -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 };
|
||||
@ -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 };
|
||||
@ -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 };
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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;
|
||||
@ -1,4 +1,4 @@
|
||||
import { asObjectSchema, type JSONSchema } from '../types/jsonSchema.ts';
|
||||
import { asObjectSchema, type JSONSchema } from '../types/json-schema';
|
||||
|
||||
/**
|
||||
* Merges two JSON schemas.
|
||||
|
||||
@ -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));
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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({
|
||||
@ -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();
|
||||
|
||||
@ -104,6 +104,7 @@ export interface ITenantInfo {
|
||||
tenant_id: string;
|
||||
chat_id: string;
|
||||
speech2text_id: string;
|
||||
rerank_id?: string;
|
||||
tts_id: string;
|
||||
}
|
||||
|
||||
|
||||
@ -37,5 +37,6 @@ export interface IMyLlmValue {
|
||||
export interface Llm {
|
||||
name: string;
|
||||
type: string;
|
||||
status: '0' | '1';
|
||||
used_token: number;
|
||||
}
|
||||
|
||||
@ -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',
|
||||
|
||||
@ -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: '升级',
|
||||
|
||||
@ -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;
|
||||
}),
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@ -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"
|
||||
|
||||
@ -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;
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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;
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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();
|
||||
|
||||
@ -3,8 +3,6 @@
|
||||
.factoryOperationWrapper {
|
||||
text-align: right;
|
||||
}
|
||||
.modelItem {
|
||||
}
|
||||
.llmList {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
584
web/src/pages/user-setting/setting-model/index1.tsx
Normal file
584
web/src/pages/user-setting/setting-model/index1.tsx
Normal 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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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;
|
||||
@ -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',
|
||||
|
||||
@ -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
|
||||
|
||||
Reference in New Issue
Block a user