diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx index 51a09f525f..28b2848eb8 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/index.tsx @@ -1,16 +1,23 @@ -import React, { type FC, useCallback, useState } from 'react' -import { type SchemaRoot, Type } from '../../../types' +import React, { type FC, useCallback, useEffect, useState } from 'react' +import type { SchemaRoot } from '../../../types' import { PortalToFollowElem, PortalToFollowElemContent, PortalToFollowElemTrigger, } from '@/app/components/base/portal-to-follow-elem' import useTheme from '@/hooks/use-theme' +import type { CompletionParams, Model } from '@/types/app' +import { ModelModeType } from '@/types/app' import { Theme } from '@/types/app' import { SchemaGeneratorDark, SchemaGeneratorLight } from './assets' import cn from '@/utils/classnames' +import type { ModelInfo } from './prompt-editor' import PromptEditor from './prompt-editor' import GeneratedResult from './generated-result' +import { useGenerateStructuredOutputRules } from '@/service/use-common' +import Toast from '@/app/components/base/toast' +import { type FormValue, ModelTypeEnum } from '@/app/components/header/account-setting/model-provider-page/declarations' +import { useModelListAndDefaultModelAndCurrentProviderAndModel } from '@/app/components/header/account-setting/model-provider-page/hooks' type JsonSchemaGeneratorProps = { onApply: (schema: SchemaRoot) => void @@ -29,9 +36,28 @@ export const JsonSchemaGenerator: FC = ({ const [open, setOpen] = useState(false) const { theme } = useTheme() const [view, setView] = useState(GeneratorView.promptEditor) + const [model, setModel] = useState({ + name: '', + provider: '', + mode: ModelModeType.completion, + completion_params: {} as CompletionParams, + }) const [instruction, setInstruction] = useState('') const [schema, setSchema] = useState(null) const SchemaGenerator = theme === Theme.light ? SchemaGeneratorLight : SchemaGeneratorDark + const { + defaultModel, + } = useModelListAndDefaultModelAndCurrentProviderAndModel(ModelTypeEnum.textGeneration) + + useEffect(() => { + if (defaultModel) { + setModel(prev => ({ + ...prev, + name: defaultModel.model, + provider: defaultModel.provider.provider, + })) + } + }, [defaultModel]) const handleTrigger = useCallback((e: React.MouseEvent) => { e.stopPropagation() @@ -42,34 +68,41 @@ export const JsonSchemaGenerator: FC = ({ setOpen(false) }, []) - const generateSchema = useCallback(async () => { - // todo: fetch schema, delete mock data - await new Promise((resolve) => { - setTimeout(() => { - setSchema({ - type: Type.object, - properties: { - string_field_1: { - type: Type.string, - description: '可为空可为空可为空可为空可为空可为空可为空可为空可为空可为空', - }, - string_field_2: { - type: Type.string, - description: '可为空可为空可为空可为空可为空可为空可为空可为空可为空可为空', - }, - }, - required: [ - 'string_field_1', - ], - additionalProperties: false, - }) - resolve() - }, 1000) - }) + const handleModelChange = useCallback((model: ModelInfo) => { + setModel(prev => ({ + ...prev, + provider: model.provider, + name: model.modelId, + mode: model.mode as ModelModeType, + })) }, []) + const handleCompletionParamsChange = useCallback((newParams: FormValue) => { + setModel(prev => ({ + ...prev, + completion_params: newParams as CompletionParams, + }), + ) + }, []) + + const { mutateAsync: generateStructuredOutputRules } = useGenerateStructuredOutputRules() + + const generateSchema = useCallback(async () => { + const { output, error } = await generateStructuredOutputRules({ instruction, model_config: model! }) + if (error) { + Toast.notify({ + type: 'error', + message: error, + }) + return + } + return output + }, [instruction, model, generateStructuredOutputRules]) + const handleGenerate = useCallback(async () => { - await generateSchema() + const output = await generateSchema() + if (output === undefined) return + setSchema(JSON.parse(output)) setView(GeneratorView.result) }, [generateSchema]) @@ -78,7 +111,9 @@ export const JsonSchemaGenerator: FC = ({ } const handleRegenerate = useCallback(async () => { - await generateSchema() + const output = await generateSchema() + if (output === undefined) return + setSchema(JSON.parse(output)) }, [generateSchema]) const handleApply = () => { @@ -111,9 +146,12 @@ export const JsonSchemaGenerator: FC = ({ {view === GeneratorView.promptEditor && ( )} {view === GeneratorView.result && ( diff --git a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx index 3cb7a611b1..382e1c20d0 100644 --- a/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx +++ b/web/app/components/workflow/nodes/llm/components/json-schema-config-modal/json-schema-generator/prompt-editor.tsx @@ -1,34 +1,45 @@ -import React from 'react' +import React, { useCallback } from 'react' import type { FC } from 'react' import { RiCloseLine, RiSparklingFill } from '@remixicon/react' import { useTranslation } from 'react-i18next' -import ModelSelector from '@/app/components/header/account-setting/model-provider-page/model-selector' -import { useTextGenerationCurrentProviderAndModelAndModelList } from '@/app/components/header/account-setting/model-provider-page/hooks' import Textarea from '@/app/components/base/textarea' import Tooltip from '@/app/components/base/tooltip' import Button from '@/app/components/base/button' +import type { FormValue } from '@/app/components/header/account-setting/model-provider-page/declarations' +import ModelParameterModal from '@/app/components/header/account-setting/model-provider-page/model-parameter-modal' +import type { Model } from '@/types/app' + +export type ModelInfo = { + modelId: string + provider: string + mode?: string + features?: string[] +} type PromptEditorProps = { instruction: string + model: Model onInstructionChange: (instruction: string) => void + onCompletionParamsChange: (newParams: FormValue) => void + onModelChange: (model: ModelInfo) => void onClose: () => void onGenerate: () => void } const PromptEditor: FC = ({ instruction, + model, onInstructionChange, + onCompletionParamsChange, onClose, onGenerate, + onModelChange, }) => { const { t } = useTranslation() - const { - activeTextGenerationModelList, - } = useTextGenerationCurrentProviderAndModelAndModelList() - - const handleChangeModel = () => { - } + const handleInstructionChange = useCallback((e: React.ChangeEvent) => { + onInstructionChange(e.target.value) + }, [onInstructionChange]) return (
@@ -49,9 +60,17 @@ const PromptEditor: FC = ({
{t('common.modelProvider.model')}
-
@@ -64,7 +83,7 @@ const PromptEditor: FC = ({ className='h-[364px] px-2 py-1 resize-none' value={instruction} placeholder={t('workflow.nodes.llm.jsonSchema.promptPlaceholder')} - onChange={e => onInstructionChange(e.target.value)} + onChange={handleInstructionChange} />
diff --git a/web/models/common.ts b/web/models/common.ts index 4c25e92a85..d40a1489bd 100644 --- a/web/models/common.ts +++ b/web/models/common.ts @@ -1,23 +1,24 @@ import type { I18nText } from '@/i18n/language' +import type { Model } from '@/types/app' -export interface CommonResponse { +export type CommonResponse = { result: 'success' | 'fail' } -export interface OauthResponse { +export type OauthResponse = { redirect_url: string } -export interface SetupStatusResponse { +export type SetupStatusResponse = { step: 'finished' | 'not_started' setup_at?: Date } -export interface InitValidateStatusResponse { +export type InitValidateStatusResponse = { status: 'finished' | 'not_started' } -export interface UserProfileResponse { +export type UserProfileResponse = { id: string name: string email: string @@ -33,13 +34,13 @@ export interface UserProfileResponse { created_at?: string } -export interface UserProfileOriginResponse { +export type UserProfileOriginResponse = { json: () => Promise bodyUsed: boolean headers: any } -export interface LangGeniusVersionResponse { +export type LangGeniusVersionResponse = { current_version: string latest_version: string version: string @@ -49,7 +50,7 @@ export interface LangGeniusVersionResponse { current_env: string } -export interface TenantInfoResponse { +export type TenantInfoResponse = { name: string created_at: string providers: Array<{ @@ -80,14 +81,14 @@ export enum ProviderName { Tongyi = 'tongyi', ChatGLM = 'chatglm', } -export interface ProviderAzureToken { +export type ProviderAzureToken = { openai_api_base?: string openai_api_key?: string } -export interface ProviderAnthropicToken { +export type ProviderAnthropicToken = { anthropic_api_key?: string } -export interface ProviderTokenType { +export type ProviderTokenType = { [ProviderName.OPENAI]: string [ProviderName.AZURE_OPENAI]: ProviderAzureToken [ProviderName.ANTHROPIC]: ProviderAnthropicToken @@ -110,14 +111,14 @@ export type ProviderHosted = Provider & { quota_used: number } -export interface AccountIntegrate { +export type AccountIntegrate = { provider: 'google' | 'github' created_at: number is_bound: boolean link: string } -export interface IWorkspace { +export type IWorkspace = { id: string name: string plan: string @@ -137,7 +138,7 @@ export type ICurrentWorkspace = Omit & { } } -export interface DataSourceNotionPage { +export type DataSourceNotionPage = { page_icon: null | { type: string | null url: string | null @@ -156,7 +157,7 @@ export type NotionPage = DataSourceNotionPage & { export type DataSourceNotionPageMap = Record -export interface DataSourceNotionWorkspace { +export type DataSourceNotionWorkspace = { workspace_name: string workspace_id: string workspace_icon: string | null @@ -166,7 +167,7 @@ export interface DataSourceNotionWorkspace { export type DataSourceNotionWorkspaceMap = Record -export interface DataSourceNotion { +export type DataSourceNotion = { id: string provider: string is_bound: boolean @@ -181,12 +182,12 @@ export enum DataSourceProvider { jinaReader = 'jinareader', } -export interface FirecrawlConfig { +export type FirecrawlConfig = { api_key: string base_url: string } -export interface DataSourceItem { +export type DataSourceItem = { id: string category: DataSourceCategory provider: DataSourceProvider @@ -195,15 +196,15 @@ export interface DataSourceItem { updated_at: number } -export interface DataSources { +export type DataSources = { sources: DataSourceItem[] } -export interface GithubRepo { +export type GithubRepo = { stargazers_count: number } -export interface PluginProvider { +export type PluginProvider = { tool_name: string is_enabled: boolean credentials: { @@ -211,7 +212,7 @@ export interface PluginProvider { } | null } -export interface FileUploadConfigResponse { +export type FileUploadConfigResponse = { batch_count_limit: number image_file_size_limit?: number | string // default is 10MB file_size_limit: number // default is 15MB @@ -234,14 +235,14 @@ export type InvitationResponse = CommonResponse & { invitation_results: InvitationResult[] } -export interface ApiBasedExtension { +export type ApiBasedExtension = { id?: string name?: string api_endpoint?: string api_key?: string } -export interface CodeBasedExtensionForm { +export type CodeBasedExtensionForm = { type: string label: I18nText variable: string @@ -252,17 +253,17 @@ export interface CodeBasedExtensionForm { max_length?: number } -export interface CodeBasedExtensionItem { +export type CodeBasedExtensionItem = { name: string label: any form_schema: CodeBasedExtensionForm[] } -export interface CodeBasedExtension { +export type CodeBasedExtension = { module: string data: CodeBasedExtensionItem[] } -export interface ExternalDataTool { +export type ExternalDataTool = { type?: string label?: string icon?: string @@ -274,7 +275,7 @@ export interface ExternalDataTool { } & Partial> } -export interface ModerateResponse { +export type ModerateResponse = { flagged: boolean text: string } @@ -286,3 +287,13 @@ export type ModerationService = ( text: string } ) => Promise + +export type StructuredOutputRulesRequestBody = { + instruction: string + model_config: Model +} + +export type StructuredOutputRulesResponse = { + output: string + error?: string +} diff --git a/web/service/use-common.ts b/web/service/use-common.ts index 98ab535948..d49f3803ac 100644 --- a/web/service/use-common.ts +++ b/web/service/use-common.ts @@ -1,8 +1,10 @@ -import { get } from './base' +import { get, post } from './base' import type { FileUploadConfigResponse, + StructuredOutputRulesRequestBody, + StructuredOutputRulesResponse, } from '@/models/common' -import { useQuery } from '@tanstack/react-query' +import { useMutation, useQuery } from '@tanstack/react-query' const NAME_SPACE = 'common' @@ -12,3 +14,15 @@ export const useFileUploadConfig = () => { queryFn: () => get('/files/upload'), }) } + +export const useGenerateStructuredOutputRules = () => { + return useMutation({ + mutationKey: [NAME_SPACE, 'generate-structured-output-rules'], + mutationFn: (body: StructuredOutputRulesRequestBody) => { + return post( + '/rule-structured-output-generate', + { body }, + ) + }, + }) +}