mirror of
https://github.com/infiniflow/ragflow.git
synced 2026-11-10 21:32:43 +08:00
Feat: Display both "naive" and "agentic" configurations on the chat settings page. (#21285)
This commit is contained in:
1 parent
b84cadea97
commit
fbb37f509f
20 files changed
+757
-87
No files matched your search
@@ -0,0 +1,80 @@
|
||||
// Stub for @/routes: the real module calls createBrowserRouter at module
|
||||
// scope, which constructs a fetch Request — a global jsdom does not provide —
|
||||
// so any import chain reaching it (e.g. llm-setting-items → llm hooks →
|
||||
// navigate-hooks) crashes the test worker before a test runs. Tests only ever
|
||||
// consume the Routes path constants; keep this object in sync with the enum
|
||||
// in src/routes.tsx (values copied 1:1, template literals resolved).
|
||||
const Routes = {
|
||||
Root: '/',
|
||||
Login: '/login-next',
|
||||
Logout: '/logout',
|
||||
Home: '/home',
|
||||
Datasets: '/datasets',
|
||||
DatasetBase: '/dataset',
|
||||
Files: '/files',
|
||||
Dataset: '/dataset/files',
|
||||
Agent: '/agent',
|
||||
AgentTemplates: '/agent-templates',
|
||||
Agents: '/agents',
|
||||
Pipeline: '/pipeline',
|
||||
Explore: '/explore',
|
||||
AgentExplore: '/agent/:id/explore',
|
||||
Memories: '/memories',
|
||||
Memory: '/memory',
|
||||
MemoryMessage: '/memory-message',
|
||||
MemorySetting: '/memory-setting',
|
||||
AgentList: '/agent-list',
|
||||
Searches: '/searches',
|
||||
Search: '/search',
|
||||
SearchShare: '/search/share',
|
||||
Chats: '/chats',
|
||||
Chat: '/chat',
|
||||
Skills: '/files/skills',
|
||||
ProfileSetting: '/profile-setting',
|
||||
Profile: '/profile',
|
||||
Api: '/api',
|
||||
Mcp: '/mcp',
|
||||
Team: '/team',
|
||||
Plan: '/plan',
|
||||
Model: '/model',
|
||||
Prompt: '/prompt',
|
||||
CompilationTemplatesEditNext: '/compilation-templates/edit-next',
|
||||
DataSource: '/data-source',
|
||||
DataSourceDetailPage: '/data-source-detail-page',
|
||||
ChatChannel: '/chat-channel',
|
||||
ProfileMcp: '/profile-setting/mcp',
|
||||
ProfileTeam: '/profile-setting/team',
|
||||
ProfilePlan: '/profile-setting/plan',
|
||||
ProfileModel: '/profile-setting/model',
|
||||
ProfilePrompt: '/profile-setting/prompt',
|
||||
ProfileProfile: '/profile-setting/profile',
|
||||
DatasetTesting: '/retrieval',
|
||||
Chunk: '/chunk',
|
||||
ChunkResult: '/chunk/chunk',
|
||||
Parsed: '/parsed',
|
||||
ParsedResult: '/chunk/parsed',
|
||||
Result: '/result',
|
||||
ResultView: '/chunk/result',
|
||||
KnowledgeGraph: '/knowledge-graph',
|
||||
Compilation: '/compilation',
|
||||
AgentLogPage: '/agent-log-page',
|
||||
AgentShare: '/agent/share',
|
||||
ChatShare: '/chats/share',
|
||||
ChatWidget: '/chats/widget',
|
||||
UserSetting: '/user-setting',
|
||||
DataSetOverview: '/logs',
|
||||
DataSetSetting: '/configuration',
|
||||
DataflowResult: '/dataflow-result',
|
||||
Admin: '/admin',
|
||||
AdminServices: '/admin/services',
|
||||
AdminUserManagement: '/admin/users',
|
||||
AdminSandboxSettings: '/admin/sandbox-settings',
|
||||
AdminWhitelist: '/admin/whitelist',
|
||||
AdminRoles: '/admin/roles',
|
||||
AdminMonitoring: '/admin/monitoring',
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
__esModule: true,
|
||||
Routes,
|
||||
};
|
||||
@@ -15,6 +15,9 @@ const config: Config = {
|
||||
// Drags the app shell (routes/react-router) into jsdom; see __mocks__
|
||||
'^@/components/layout-recognize-form-field$':
|
||||
'<rootDir>/__mocks__/layout-recognize-form-field.js',
|
||||
// The app shell itself: createBrowserRouter runs at module scope and
|
||||
// needs the fetch Request global, which jsdom does not provide.
|
||||
'^@/routes$': '<rootDir>/__mocks__/routes.js',
|
||||
'^@/(.*)$': '<rootDir>/src/$1',
|
||||
'^human-id$': '<rootDir>/__mocks__/human-id.js',
|
||||
},
|
||||
|
||||
@@ -40,7 +40,11 @@ import * as React from 'react';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { toast } from 'sonner';
|
||||
import storage from '@/utils/authorization-util';
|
||||
import {
|
||||
setThinkingLevel,
|
||||
ThinkingMode,
|
||||
useThinkingLevel,
|
||||
} from '@/hooks/use-thinking-level';
|
||||
import { SelectWithSearch } from '../originui/select-with-search';
|
||||
import { AudioButton } from '../ui/audio-button';
|
||||
|
||||
@@ -97,9 +101,7 @@ export function NextMessageInput({
|
||||
null,
|
||||
);
|
||||
|
||||
const [enableThinking, setEnableThinking] = useState(() =>
|
||||
storage.getThinkingLevel(),
|
||||
);
|
||||
const enableThinking = useThinkingLevel();
|
||||
const [enableInternet, setEnableInternet] = useState(false);
|
||||
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
@@ -165,8 +167,11 @@ export function NextMessageInput({
|
||||
];
|
||||
|
||||
const handleThinkingChange = useCallback((value: string) => {
|
||||
setEnableThinking(value);
|
||||
storage.setThinkingLevel(value);
|
||||
setThinkingLevel(
|
||||
value === ThinkingMode.Agentic
|
||||
? ThinkingMode.Agentic
|
||||
: ThinkingMode.Naive,
|
||||
);
|
||||
}, []);
|
||||
|
||||
const handleInternetToggle = useCallback(() => {
|
||||
|
||||
@@ -20,6 +20,7 @@ import message from '@/components/ui/message';
|
||||
import { ChatSearchParams } from '@/constants/chat';
|
||||
import { ListDeletionKey } from '@/constants/list-deletion';
|
||||
import {
|
||||
IAgenticRagSettings,
|
||||
IClientConversation,
|
||||
IConversation,
|
||||
IDialog,
|
||||
@@ -69,6 +70,12 @@ export const enum ChatApiAction {
|
||||
CreateSharedConversation = 'createSharedConversation',
|
||||
}
|
||||
|
||||
export const AgenticRagKeys = {
|
||||
all: ['agenticRag'] as const,
|
||||
config: (chatId?: string) =>
|
||||
[...AgenticRagKeys.all, 'config', chatId] as const,
|
||||
};
|
||||
|
||||
export const useGetChatSearchParams = () => {
|
||||
const [currentQueryParameters] = useSearchParams();
|
||||
|
||||
@@ -224,6 +231,7 @@ export const useUpdateChat = () => {
|
||||
queryKey: [ChatApiAction.FetchChatList],
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [ChatApiAction.FetchChat] });
|
||||
queryClient.invalidateQueries({ queryKey: AgenticRagKeys.all });
|
||||
message.success(t('message.modified'));
|
||||
}
|
||||
return data?.code;
|
||||
@@ -260,6 +268,7 @@ export const usePatchChat = () => {
|
||||
queryKey: [ChatApiAction.FetchChatList],
|
||||
});
|
||||
queryClient.invalidateQueries({ queryKey: [ChatApiAction.FetchChat] });
|
||||
queryClient.invalidateQueries({ queryKey: AgenticRagKeys.all });
|
||||
message.success(t('message.modified'));
|
||||
}
|
||||
return data?.code;
|
||||
@@ -291,6 +300,21 @@ export const useFetchChat = () => {
|
||||
return { data, loading, refetch };
|
||||
};
|
||||
|
||||
export const useFetchAgenticRagConfig = (chatId?: string) => {
|
||||
const { data, isFetching: loading } = useQuery<IAgenticRagSettings>({
|
||||
queryKey: AgenticRagKeys.config(chatId),
|
||||
enabled: !!chatId,
|
||||
gcTime: 0,
|
||||
refetchOnWindowFocus: false,
|
||||
queryFn: async () => {
|
||||
const { data } = await chatService.getAgenticRagConfig(chatId);
|
||||
return data?.data;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading };
|
||||
};
|
||||
|
||||
//#region Session
|
||||
|
||||
export const useFetchSessionList = () => {
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright 2026 The InfiniFlow Authors. All Rights Reserved.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import storage from '@/utils/authorization-util';
|
||||
import { useSyncExternalStore } from 'react';
|
||||
|
||||
// Values mirror the strings persisted under the `thinkingLevel` localStorage
|
||||
// key; they double as the reasoning level sent with each message ('0' naive,
|
||||
// '5' agentic).
|
||||
export const ThinkingMode = { Naive: '0', Agentic: '5' } as const;
|
||||
export type ThinkingMode = (typeof ThinkingMode)[keyof typeof ThinkingMode];
|
||||
|
||||
const listeners = new Set<() => void>();
|
||||
|
||||
function subscribe(listener: () => void) {
|
||||
listeners.add(listener);
|
||||
return () => {
|
||||
listeners.delete(listener);
|
||||
};
|
||||
}
|
||||
|
||||
// Persists the level, then notifies same-tab subscribers (e.g. the chat
|
||||
// settings panel) so a Type change in the message input re-renders them.
|
||||
export function setThinkingLevel(mode: ThinkingMode) {
|
||||
storage.setThinkingLevel(mode);
|
||||
listeners.forEach((notify) => notify());
|
||||
}
|
||||
|
||||
export function useThinkingLevel(): ThinkingMode {
|
||||
// getThinkingLevel already sanitizes unknown persisted values to Naive.
|
||||
return useSyncExternalStore(
|
||||
subscribe,
|
||||
storage.getThinkingLevel,
|
||||
) as ThinkingMode;
|
||||
}
|
||||
@@ -8,6 +8,30 @@ export interface IDocumentDownloadInfo {
|
||||
size?: number;
|
||||
}
|
||||
|
||||
export interface AgenticRagOverride {
|
||||
content?: string;
|
||||
tools?: string[];
|
||||
}
|
||||
|
||||
export interface AgenticRagTemplate {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
temperature?: number | null;
|
||||
tools: string[];
|
||||
content: string;
|
||||
}
|
||||
|
||||
// Response of GET /api/v1/chats/:chat_id/agentic-rag
|
||||
export interface IAgenticRagSettings {
|
||||
base_template: string;
|
||||
defaults: AgenticRagTemplate;
|
||||
override: AgenticRagOverride | null;
|
||||
effective: AgenticRagTemplate;
|
||||
available_tools: Array<{ name: string; description: string }>;
|
||||
using_defaults: boolean;
|
||||
}
|
||||
|
||||
export interface PromptConfig {
|
||||
empty_response: string;
|
||||
parameters: Parameter[];
|
||||
@@ -20,6 +44,7 @@ export interface PromptConfig {
|
||||
use_kg: boolean;
|
||||
reasoning?: boolean;
|
||||
cross_languages?: Array<string>;
|
||||
agentic_rag?: AgenticRagOverride | null;
|
||||
brave_api_key?: string;
|
||||
exa_api_key?: string;
|
||||
firecrawl_api_key?: string;
|
||||
|
||||
@@ -1183,6 +1183,13 @@ This auto-tagging feature enhances retrieval by adding another layer of domain-s
|
||||
thinkingLevelNone: 'Naive',
|
||||
thinkingLevelAgentic: 'Agentic',
|
||||
thinkingLevelAgenticDescription: 'Self-directed corpus exploration',
|
||||
naiveSettings: 'Naive settings',
|
||||
agenticSettings: 'Agentic settings',
|
||||
agenticTemplate: 'Agentic template',
|
||||
agenticTemplateTip:
|
||||
'The system template the agentic RAG agent runs with. Editing it overrides the shipped default for this chat; reset to restore it.',
|
||||
agenticTemplateTooLong: 'Template must be at most 32768 characters',
|
||||
resetToDefault: 'Reset to default',
|
||||
thinkingTip:
|
||||
'Only controls thinking mode for official Qwen, Kimi, and GLM model providers. System default disables Qwen thinking to avoid long-running tasks.',
|
||||
quote: 'Show citations',
|
||||
|
||||
@@ -1076,6 +1076,13 @@ NER:使用 spaCy NER 和基于规则的关键词提取来抽取 Entities 和 R
|
||||
thinkingLevelNone: 'Naive',
|
||||
thinkingLevelAgentic: 'Agentic',
|
||||
thinkingLevelAgenticDescription: '自主探索语料库',
|
||||
naiveSettings: 'Naive 设置',
|
||||
agenticSettings: 'Agentic 设置',
|
||||
agenticTemplate: 'Agentic 模板',
|
||||
agenticTemplateTip:
|
||||
'Agentic RAG 运行时使用的系统模板。编辑后为当前助手覆盖默认模板,可一键重置恢复。',
|
||||
agenticTemplateTooLong: '模板长度不能超过 32768 个字符',
|
||||
resetToDefault: '重置为默认',
|
||||
thinkingTip:
|
||||
'仅控制官方模型提供商中的 Qwen、Kimi 和 GLM 模型思考模式。系统默认会关闭 Qwen 思考,以避免任务长时间运行。',
|
||||
quote: '显示引文',
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
import { resolveAgenticRagSubmitValue } from './agentic-rag-submit';
|
||||
|
||||
describe('resolveAgenticRagSubmitValue', () => {
|
||||
const DefaultContent = 'shipped default template';
|
||||
|
||||
it('returns null when there is no value', () => {
|
||||
expect(resolveAgenticRagSubmitValue(undefined, DefaultContent)).toBeNull();
|
||||
expect(resolveAgenticRagSubmitValue(null, DefaultContent)).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null for blank content, which the backend rejects', () => {
|
||||
expect(
|
||||
resolveAgenticRagSubmitValue({ content: ' ' }, DefaultContent),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('returns null when content matches the server default', () => {
|
||||
expect(
|
||||
resolveAgenticRagSubmitValue({ content: DefaultContent }, DefaultContent),
|
||||
).toBeNull();
|
||||
});
|
||||
|
||||
it('preserves a tools override when content matches the server default', () => {
|
||||
expect(
|
||||
resolveAgenticRagSubmitValue(
|
||||
{ content: DefaultContent, tools: ['grep_chunks', 'list_chunks'] },
|
||||
DefaultContent,
|
||||
),
|
||||
).toEqual({ tools: ['grep_chunks', 'list_chunks'] });
|
||||
});
|
||||
|
||||
it('keeps the content when the default is unknown (query never ran)', () => {
|
||||
expect(
|
||||
resolveAgenticRagSubmitValue({ content: DefaultContent }, undefined),
|
||||
).toEqual({ content: DefaultContent });
|
||||
});
|
||||
|
||||
it('returns custom content', () => {
|
||||
expect(
|
||||
resolveAgenticRagSubmitValue({ content: 'custom' }, DefaultContent),
|
||||
).toEqual({ content: 'custom' });
|
||||
});
|
||||
|
||||
it('preserves a tools override on the reset path', () => {
|
||||
expect(
|
||||
resolveAgenticRagSubmitValue(
|
||||
{ content: '', tools: ['grep_chunks', 'list_chunks'] },
|
||||
DefaultContent,
|
||||
),
|
||||
).toEqual({ tools: ['grep_chunks', 'list_chunks'] });
|
||||
});
|
||||
|
||||
it('preserves a tools override together with custom content', () => {
|
||||
expect(
|
||||
resolveAgenticRagSubmitValue(
|
||||
{ content: 'custom', tools: ['list_chunks'] },
|
||||
DefaultContent,
|
||||
),
|
||||
).toEqual({ content: 'custom', tools: ['list_chunks'] });
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
import type { AgenticRagOverride } from '@/interfaces/database/chat';
|
||||
|
||||
/**
|
||||
* Maps the form's agentic_rag value to the PUT payload. The backend treats
|
||||
* `agentic_rag: null` as "reset to the shipped template", so blank content and
|
||||
* content identical to the server default both collapse to null — this keeps
|
||||
* chats on the shipped default following server-side template updates instead
|
||||
* of freezing a redundant copy. A stored tools override is preserved on every
|
||||
* path: the full-replace PUT must not silently drop it.
|
||||
*/
|
||||
export function resolveAgenticRagSubmitValue(
|
||||
value: AgenticRagOverride | null | undefined,
|
||||
defaultContent: string | undefined,
|
||||
): AgenticRagOverride | null {
|
||||
const content = value?.content;
|
||||
const tools = value?.tools?.length ? value.tools : undefined;
|
||||
|
||||
if (!content?.trim()) {
|
||||
return tools ? { tools } : null;
|
||||
}
|
||||
if (defaultContent !== undefined && content === defaultContent) {
|
||||
return tools ? { tools } : null;
|
||||
}
|
||||
return tools ? { content, tools } : { content };
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Form } from '@/components/ui/form';
|
||||
import { TooltipProvider } from '@/components/ui/tooltip';
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { AgenticTemplateFormField } from './agentic-template-form-field';
|
||||
|
||||
const DefaultTemplate = 'default template';
|
||||
const FieldName = 'prompt_config.agentic_rag.content';
|
||||
const Config = {
|
||||
defaults: { content: DefaultTemplate },
|
||||
override: null,
|
||||
};
|
||||
|
||||
jest.mock('react-i18next', () => ({
|
||||
useTranslation: () => ({ t: (key: string) => key }),
|
||||
}));
|
||||
|
||||
function Harness({
|
||||
defaultValues,
|
||||
}: {
|
||||
defaultValues: Record<string, unknown>;
|
||||
}) {
|
||||
const form = useForm({ defaultValues, mode: 'onChange' });
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Form {...form}>
|
||||
<AgenticTemplateFormField name={FieldName} config={Config} />
|
||||
</Form>
|
||||
</TooltipProvider>
|
||||
);
|
||||
}
|
||||
|
||||
describe('AgenticTemplateFormField', () => {
|
||||
it('fills the shipped template only when the field has no value', async () => {
|
||||
render(<Harness defaultValues={{ prompt_config: {} }} />);
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.getByRole('textbox')).toHaveValue(DefaultTemplate),
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps an explicitly blank value instead of autofilling it', () => {
|
||||
render(
|
||||
<Harness
|
||||
defaultValues={{
|
||||
prompt_config: { agentic_rag: { content: '' } },
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
expect(screen.getByRole('textbox')).toHaveValue('');
|
||||
});
|
||||
|
||||
it('resets custom content to the shipped template', () => {
|
||||
render(
|
||||
<Harness
|
||||
defaultValues={{
|
||||
prompt_config: { agentic_rag: { content: 'custom template' } },
|
||||
}}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(
|
||||
screen.getByRole('button', { name: 'chat.resetToDefault' }),
|
||||
);
|
||||
|
||||
expect(screen.getByRole('textbox')).toHaveValue(DefaultTemplate);
|
||||
});
|
||||
|
||||
it('keeps the field empty after the autofilled template is cleared', async () => {
|
||||
render(<Harness defaultValues={{ prompt_config: {} }} />);
|
||||
const textarea = screen.getByRole('textbox');
|
||||
|
||||
await waitFor(() => expect(textarea).toHaveValue(DefaultTemplate));
|
||||
fireEvent.change(textarea, { target: { value: '' } });
|
||||
|
||||
await waitFor(() => expect(textarea).toHaveValue(''));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,84 @@
|
||||
'use client';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { getDirAttribute } from '@/utils/text-direction';
|
||||
import { useEffect } from 'react';
|
||||
import { useFormContext, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface AgenticTemplateFormFieldProps {
|
||||
name: string;
|
||||
config?: {
|
||||
defaults: { content: string };
|
||||
override?: { content?: string } | null;
|
||||
};
|
||||
}
|
||||
|
||||
export function AgenticTemplateFormField({
|
||||
name,
|
||||
config,
|
||||
}: AgenticTemplateFormFieldProps) {
|
||||
const { t } = useTranslation();
|
||||
const { control, setValue } = useFormContext();
|
||||
|
||||
const value = useWatch({ control, name });
|
||||
const defaultContent = config?.defaults.content;
|
||||
|
||||
// The template arrives after the form's own reset; fill it only while the
|
||||
// field has no value. An empty string is an explicit edit, and the submit
|
||||
// path maps it back to the shipped template.
|
||||
useEffect(() => {
|
||||
if (!config || value != null) return;
|
||||
setValue(name, config.override?.content ?? config.defaults.content);
|
||||
}, [config, value, name, setValue]);
|
||||
|
||||
const handleReset = () => {
|
||||
if (defaultContent === undefined) return;
|
||||
setValue(name, defaultContent, { shouldDirty: true, shouldValidate: true });
|
||||
};
|
||||
|
||||
return (
|
||||
<FormField
|
||||
control={control}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center justify-between">
|
||||
<FormLabel tooltip={t('chat.agenticTemplateTip')}>
|
||||
{t('chat.agenticTemplate')}
|
||||
</FormLabel>
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleReset}
|
||||
disabled={
|
||||
defaultContent === undefined || value === defaultContent
|
||||
}
|
||||
>
|
||||
{t('chat.resetToDefault')}
|
||||
</Button>
|
||||
</div>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
{...field}
|
||||
value={field.value ?? ''}
|
||||
rows={10}
|
||||
className="overflow-y-auto"
|
||||
dir={getDirAttribute(value || '')}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -17,25 +17,39 @@ import {
|
||||
} from '@/components/ui/form';
|
||||
import { Textarea } from '@/components/ui/textarea';
|
||||
import { WebSearchFormField } from '@/components/web-search-form-field';
|
||||
import { useFetchAgenticRagConfig } from '@/hooks/use-chat-request';
|
||||
import { prefixName } from '@/utils/form';
|
||||
import { getDirAttribute } from '@/utils/text-direction';
|
||||
import { useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useParams } from 'react-router';
|
||||
import { AgenticTemplateFormField } from './agentic-template-form-field';
|
||||
import { DynamicVariableForm } from './dynamic-variable';
|
||||
|
||||
interface ChatPromptEngineProps {
|
||||
prefix?: string;
|
||||
collapseOpen?: boolean;
|
||||
onCollapseOpenChange?: (open: boolean) => void;
|
||||
advancedSettingOpen?: boolean;
|
||||
onAdvancedSettingOpenChange?: (open: boolean) => void;
|
||||
naiveSettingOpen?: boolean;
|
||||
onNaiveSettingOpenChange?: (open: boolean) => void;
|
||||
agenticSettingOpen?: boolean;
|
||||
onAgenticSettingOpenChange?: (open: boolean) => void;
|
||||
}
|
||||
|
||||
export function ChatPromptEngine({
|
||||
prefix = '',
|
||||
collapseOpen,
|
||||
onCollapseOpenChange,
|
||||
advancedSettingOpen,
|
||||
onAdvancedSettingOpenChange,
|
||||
naiveSettingOpen,
|
||||
onNaiveSettingOpenChange,
|
||||
agenticSettingOpen,
|
||||
onAgenticSettingOpenChange,
|
||||
}: ChatPromptEngineProps) {
|
||||
const { t } = useTranslation();
|
||||
const form = useFormContext();
|
||||
const { id } = useParams();
|
||||
// Keep the default config available even when the advanced panel is collapsed.
|
||||
const { data: agenticConfig } = useFetchAgenticRagConfig(id);
|
||||
const systemPromptValue = form.watch(
|
||||
prefixName(prefix, 'prompt_config.system'),
|
||||
);
|
||||
@@ -43,42 +57,19 @@ export function ChatPromptEngine({
|
||||
const emptyResponseValue = form.watch(
|
||||
prefixName(prefix, 'prompt_config.empty_response'),
|
||||
);
|
||||
|
||||
return (
|
||||
<Collapse
|
||||
title={t('flow.advancedSettings')}
|
||||
open={collapseOpen}
|
||||
onOpenChange={onCollapseOpenChange}
|
||||
open={advancedSettingOpen}
|
||||
onOpenChange={onAdvancedSettingOpenChange}
|
||||
>
|
||||
<div className="space-y-8">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={prefixName(prefix, 'prompt_config.empty_response')}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel tooltip={t('chat.emptyResponseTip')}>
|
||||
{t('chat.emptyResponse')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
{...field}
|
||||
placeholder={t('chat.emptyResponsePlaceholder')}
|
||||
dir={getDirAttribute(emptyResponseValue || '')}
|
||||
></Textarea>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<SwitchFormField
|
||||
name={prefixName(prefix, 'prompt_config.quote')}
|
||||
label={t('chat.quote')}
|
||||
tooltip={t('chat.quoteTip')}
|
||||
></SwitchFormField>
|
||||
<SwitchFormField
|
||||
name={prefixName(prefix, 'prompt_config.keyword')}
|
||||
label={t('chat.keyword')}
|
||||
tooltip={t('chat.keywordTip')}
|
||||
></SwitchFormField>
|
||||
<SwitchFormField
|
||||
name={prefixName(prefix, 'prompt_config.tts')}
|
||||
label={t('chat.tts')}
|
||||
@@ -86,50 +77,94 @@ export function ChatPromptEngine({
|
||||
></SwitchFormField>
|
||||
<WebSearchFormField prefix={prefix} />
|
||||
<MetadataFilter></MetadataFilter>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={prefixName(prefix, 'prompt_config.system')}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('chat.system')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
{...field}
|
||||
rows={8}
|
||||
placeholder={t('chat.systemPlaceholder')}
|
||||
className="overflow-y-auto"
|
||||
dir={getDirAttribute(systemPromptValue || '')}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<SimilaritySliderFormField
|
||||
isTooltipShown
|
||||
similarityName={prefixName(prefix, 'similarity_threshold')}
|
||||
similarityWeightName={prefixName(
|
||||
prefix,
|
||||
'keywords_similarity_weight',
|
||||
)}
|
||||
></SimilaritySliderFormField>
|
||||
<RerankCandidatesCountFormField
|
||||
name={prefixName(prefix, 'rerank_candidates_count')}
|
||||
></RerankCandidatesCountFormField>
|
||||
<TopNFormField name={prefixName(prefix, 'top_n')}></TopNFormField>
|
||||
|
||||
<RerankFormFields prefix={prefix}></RerankFormFields>
|
||||
<CrossLanguageFormField
|
||||
name={prefixName(prefix, 'prompt_config.cross_languages')}
|
||||
></CrossLanguageFormField>
|
||||
<SwitchFormField
|
||||
name={prefixName(prefix, 'prompt_config.refine_multiturn')}
|
||||
label={t('chat.multiTurn')}
|
||||
tooltip={t('chat.multiTurnTip')}
|
||||
></SwitchFormField>
|
||||
<DynamicVariableForm
|
||||
name={prefixName(prefix, 'prompt_config.parameters')}
|
||||
></DynamicVariableForm>
|
||||
<Collapse
|
||||
title={t('chat.naiveSettings')}
|
||||
open={naiveSettingOpen}
|
||||
onOpenChange={onNaiveSettingOpenChange}
|
||||
>
|
||||
<div className="space-y-8">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={prefixName(prefix, 'prompt_config.empty_response')}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel tooltip={t('chat.emptyResponseTip')}>
|
||||
{t('chat.emptyResponse')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
{...field}
|
||||
placeholder={t('chat.emptyResponsePlaceholder')}
|
||||
dir={getDirAttribute(emptyResponseValue || '')}
|
||||
></Textarea>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<SwitchFormField
|
||||
name={prefixName(prefix, 'prompt_config.keyword')}
|
||||
label={t('chat.keyword')}
|
||||
tooltip={t('chat.keywordTip')}
|
||||
></SwitchFormField>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={prefixName(prefix, 'prompt_config.system')}
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('chat.system')}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
{...field}
|
||||
rows={8}
|
||||
placeholder={t('chat.systemPlaceholder')}
|
||||
className="overflow-y-auto"
|
||||
dir={getDirAttribute(systemPromptValue || '')}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<SimilaritySliderFormField
|
||||
isTooltipShown
|
||||
similarityName={prefixName(prefix, 'similarity_threshold')}
|
||||
similarityWeightName={prefixName(
|
||||
prefix,
|
||||
'keywords_similarity_weight',
|
||||
)}
|
||||
></SimilaritySliderFormField>
|
||||
<RerankCandidatesCountFormField
|
||||
name={prefixName(prefix, 'rerank_candidates_count')}
|
||||
></RerankCandidatesCountFormField>
|
||||
<TopNFormField name={prefixName(prefix, 'top_n')}></TopNFormField>
|
||||
|
||||
<RerankFormFields prefix={prefix}></RerankFormFields>
|
||||
<CrossLanguageFormField
|
||||
name={prefixName(prefix, 'prompt_config.cross_languages')}
|
||||
></CrossLanguageFormField>
|
||||
<SwitchFormField
|
||||
name={prefixName(prefix, 'prompt_config.refine_multiturn')}
|
||||
label={t('chat.multiTurn')}
|
||||
tooltip={t('chat.multiTurnTip')}
|
||||
></SwitchFormField>
|
||||
<DynamicVariableForm
|
||||
name={prefixName(prefix, 'prompt_config.parameters')}
|
||||
></DynamicVariableForm>
|
||||
</div>
|
||||
</Collapse>
|
||||
|
||||
<Collapse
|
||||
title={t('chat.agenticSettings')}
|
||||
open={agenticSettingOpen}
|
||||
onOpenChange={onAgenticSettingOpenChange}
|
||||
>
|
||||
<AgenticTemplateFormField
|
||||
name={prefixName(prefix, 'prompt_config.agentic_rag.content')}
|
||||
config={agenticConfig}
|
||||
/>
|
||||
</Collapse>
|
||||
</div>
|
||||
</Collapse>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import { WebSearchProvider } from '@/constants/chat';
|
||||
import { buildChatSettingFormSchema } from './use-chat-setting-schema';
|
||||
|
||||
const t = (key: string) => key;
|
||||
|
||||
const BaseValues = {
|
||||
name: 'assistant',
|
||||
icon: '',
|
||||
dataset_ids: [] as string[],
|
||||
prompt_config: {
|
||||
quote: true,
|
||||
keyword: false,
|
||||
tts: false,
|
||||
refine_multiturn: true,
|
||||
system: 'Answer using {knowledge}',
|
||||
parameters: [] as Array<{ key: string; optional: boolean }>,
|
||||
},
|
||||
rerank_candidates_count: 64,
|
||||
similarity_threshold: 0.2,
|
||||
keywords_similarity_weight: 0.8,
|
||||
llm_setting: {},
|
||||
meta_data_filter: { method: 'disabled', manual: [] },
|
||||
};
|
||||
|
||||
function parseSettings(
|
||||
promptConfig: Record<string, unknown> = {},
|
||||
overrides: Record<string, unknown> = {},
|
||||
) {
|
||||
const schema = buildChatSettingFormSchema(t);
|
||||
return schema.safeParse({
|
||||
...BaseValues,
|
||||
...overrides,
|
||||
prompt_config: { ...BaseValues.prompt_config, ...promptConfig },
|
||||
});
|
||||
}
|
||||
|
||||
describe('buildChatSettingFormSchema', () => {
|
||||
it('rejects a blank system prompt', () => {
|
||||
const result = parseSettings({ system: '' });
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error?.issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
path: ['prompt_config', 'system'],
|
||||
message: 'systemMessage',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('flags a system prompt missing {knowledge} when datasets are selected', () => {
|
||||
const result = parseSettings(
|
||||
{ system: 'answer the question' },
|
||||
{ dataset_ids: ['kb1'] },
|
||||
);
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error?.issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
path: ['prompt_config', 'system'],
|
||||
message: 'knowledgePlaceholderMessage',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects blank variable keys', () => {
|
||||
const parameters = [{ key: '', optional: true }];
|
||||
const result = parseSettings({ parameters });
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error?.issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
path: ['prompt_config', 'parameters', 0, 'key'],
|
||||
message: 'variableKeyMessage',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('requires the web search API key', () => {
|
||||
const result = parseSettings({
|
||||
web_search_provider: WebSearchProvider.Tavily,
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error?.issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
path: ['prompt_config', 'tavily_api_key'],
|
||||
message: 'webSearchApiKeyRequired',
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it('keeps agentic_rag through parsing instead of stripping it', () => {
|
||||
const agenticRag = {
|
||||
content: 'custom template',
|
||||
tools: ['grep_chunks', 'list_chunks'],
|
||||
};
|
||||
const result = parseSettings({ agentic_rag: agenticRag });
|
||||
expect(result.success).toBe(true);
|
||||
expect(result.data?.prompt_config.agentic_rag).toEqual(agenticRag);
|
||||
});
|
||||
|
||||
it('rejects agentic templates longer than 32768 characters', () => {
|
||||
const result = parseSettings({
|
||||
agentic_rag: { content: 'x'.repeat(32769) },
|
||||
});
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.error?.issues).toContainEqual(
|
||||
expect.objectContaining({
|
||||
path: ['prompt_config', 'agentic_rag', 'content'],
|
||||
message: 'agenticTemplateTooLong',
|
||||
}),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -3,18 +3,24 @@ import { Form } from '@/components/ui/form';
|
||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||
import { DatasetMetadata } from '@/constants/chat';
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useFetchChat, useUpdateChat } from '@/hooks/use-chat-request';
|
||||
import {
|
||||
AgenticRagKeys,
|
||||
useFetchChat,
|
||||
useUpdateChat,
|
||||
} from '@/hooks/use-chat-request';
|
||||
import { useFindLlmByUuid } from '@/hooks/use-llm-request';
|
||||
import {
|
||||
useRevalidateStaleDatasetIds,
|
||||
useStaleDatasetFormSchema,
|
||||
} from '@/hooks/use-stale-dataset-validation';
|
||||
import { IAgenticRagSettings } from '@/interfaces/database/chat';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
removeUselessFieldsFromValues,
|
||||
setLLMSettingEnabledValues,
|
||||
} from '@/utils/form';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useQueryClient } from '@tanstack/react-query';
|
||||
import { isEmpty, omit } from 'lodash';
|
||||
import { LucidePanelRightClose, LucideSettings } from 'lucide-react';
|
||||
import { useEffect } from 'react';
|
||||
@@ -24,6 +30,7 @@ import { useParams } from 'react-router';
|
||||
import { z } from 'zod';
|
||||
import ChatBasicSetting from './chat-basic-settings';
|
||||
import { ChatPromptEngine } from './chat-prompt-engine';
|
||||
import { resolveAgenticRagSubmitValue } from './agentic-rag-submit';
|
||||
import { SavingButton } from './saving-button';
|
||||
import { useChatSettingSchema } from './use-chat-setting-schema';
|
||||
import { useRevealSubmitErrors } from './use-reveal-submit-errors';
|
||||
@@ -41,6 +48,7 @@ export function ChatSettings({ hasSingleChatBox }: ChatSettingsProps) {
|
||||
);
|
||||
const { updateChat, loading } = useUpdateChat();
|
||||
const findLlmByUuid = useFindLlmByUuid();
|
||||
const queryClient = useQueryClient();
|
||||
const { id } = useParams();
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -54,6 +62,10 @@ export function ChatSettings({ hasSingleChatBox }: ChatSettingsProps) {
|
||||
onModelSettingOpenChange,
|
||||
advancedSettingOpen,
|
||||
onAdvancedSettingOpenChange,
|
||||
naiveSettingOpen,
|
||||
onNaiveSettingOpenChange,
|
||||
agenticSettingOpen,
|
||||
onAgenticSettingOpenChange,
|
||||
} = useRevealSubmitErrors();
|
||||
|
||||
type FormSchemaType = z.infer<typeof formSchema>;
|
||||
@@ -107,6 +119,19 @@ export function ChatSettings({ hasSingleChatBox }: ChatSettingsProps) {
|
||||
};
|
||||
}
|
||||
|
||||
// Keep the override in sync with the shipped default on every save while
|
||||
// preserving a custom content or complete tools override.
|
||||
const agenticRag = resolveAgenticRagSubmitValue(
|
||||
values.prompt_config?.agentic_rag,
|
||||
queryClient.getQueryData<IAgenticRagSettings>(AgenticRagKeys.config(id))
|
||||
?.defaults?.content,
|
||||
);
|
||||
|
||||
nextValues.prompt_config = {
|
||||
...nextValues.prompt_config,
|
||||
agentic_rag: agenticRag,
|
||||
};
|
||||
|
||||
updateChat({
|
||||
chatId: id!,
|
||||
params: {
|
||||
@@ -207,8 +232,12 @@ export function ChatSettings({ hasSingleChatBox }: ChatSettingsProps) {
|
||||
onCollapseOpenChange={onModelSettingOpenChange}
|
||||
></ChatBasicSetting>
|
||||
<ChatPromptEngine
|
||||
collapseOpen={advancedSettingOpen}
|
||||
onCollapseOpenChange={onAdvancedSettingOpenChange}
|
||||
advancedSettingOpen={advancedSettingOpen}
|
||||
onAdvancedSettingOpenChange={onAdvancedSettingOpenChange}
|
||||
naiveSettingOpen={naiveSettingOpen}
|
||||
onNaiveSettingOpenChange={onNaiveSettingOpenChange}
|
||||
agenticSettingOpen={agenticSettingOpen}
|
||||
onAgenticSettingOpenChange={onAgenticSettingOpenChange}
|
||||
></ChatPromptEngine>
|
||||
</section>
|
||||
</ScrollArea>
|
||||
|
||||
@@ -12,13 +12,15 @@ import {
|
||||
import { topnSchema } from '@/components/top-n-item';
|
||||
import { WebSearchProvider } from '@/constants/chat';
|
||||
import { useTranslate } from '@/hooks/common-hooks';
|
||||
import { useMemo } from 'react';
|
||||
import { z, ZodIssueCode } from 'zod';
|
||||
import { missingWebSearchApiKeyField } from '../web-search-api-key';
|
||||
import { chatPromptKbIssues } from './validate-chat-prompt';
|
||||
|
||||
export function useChatSettingSchema() {
|
||||
const { t } = useTranslate('chat');
|
||||
|
||||
// Pure builder so the schema can be unit-tested without rendering a form.
|
||||
// All settings remain in one schema because both naive and agentic fields are
|
||||
// available in the settings form at the same time.
|
||||
export function buildChatSettingFormSchema(t: (key: string) => string) {
|
||||
const promptConfigSchema = z.object({
|
||||
quote: z.boolean(),
|
||||
keyword: z.boolean(),
|
||||
@@ -62,6 +64,19 @@ export function useChatSettingSchema() {
|
||||
.or(z.literal('')),
|
||||
reasoning: z.boolean().optional(),
|
||||
cross_languages: z.array(z.string()).optional(),
|
||||
// The per-chat agentic template override lives here; without this
|
||||
// declaration zod strips the key at parse time and the full-replace PUT
|
||||
// would silently delete a saved override on every settings save.
|
||||
agentic_rag: z
|
||||
.object({
|
||||
content: z
|
||||
.string()
|
||||
.max(32768, { message: t('agenticTemplateTooLong') })
|
||||
.optional(),
|
||||
tools: z.array(z.string()).optional(),
|
||||
})
|
||||
.nullable()
|
||||
.optional(),
|
||||
});
|
||||
|
||||
const formSchema = z
|
||||
@@ -108,3 +123,12 @@ export function useChatSettingSchema() {
|
||||
|
||||
return formSchema;
|
||||
}
|
||||
|
||||
export function useChatSettingSchema() {
|
||||
const { t } = useTranslate('chat');
|
||||
|
||||
return useMemo(
|
||||
() => buildChatSettingFormSchema((key: string) => t(key)),
|
||||
[t],
|
||||
);
|
||||
}
|
||||
@@ -17,12 +17,16 @@ function Harness() {
|
||||
handleInvalidSubmit,
|
||||
modelSettingOpen,
|
||||
advancedSettingOpen,
|
||||
naiveSettingOpen,
|
||||
agenticSettingOpen,
|
||||
} = useRevealSubmitErrors();
|
||||
|
||||
return (
|
||||
<form ref={formContainerRef}>
|
||||
<span data-testid="model-open">{String(modelSettingOpen)}</span>
|
||||
<span data-testid="advanced-open">{String(advancedSettingOpen)}</span>
|
||||
<span data-testid="naive-open">{String(naiveSettingOpen)}</span>
|
||||
<span data-testid="agentic-open">{String(agenticSettingOpen)}</span>
|
||||
<button type="button" onClick={handleInvalidSubmit}>
|
||||
save
|
||||
</button>
|
||||
@@ -32,20 +36,24 @@ function Harness() {
|
||||
}
|
||||
|
||||
describe('useRevealSubmitErrors', () => {
|
||||
it('keeps both sections collapsed initially', () => {
|
||||
it('keeps all sections collapsed initially', () => {
|
||||
render(<Harness />);
|
||||
|
||||
expect(screen.getByTestId('model-open')).toHaveTextContent('false');
|
||||
expect(screen.getByTestId('advanced-open')).toHaveTextContent('false');
|
||||
expect(screen.getByTestId('naive-open')).toHaveTextContent('false');
|
||||
expect(screen.getByTestId('agentic-open')).toHaveTextContent('false');
|
||||
});
|
||||
|
||||
it('expands both sections and scrolls to the first error on invalid submit', () => {
|
||||
it('expands all sections and scrolls to the first error on invalid submit', () => {
|
||||
render(<Harness />);
|
||||
|
||||
fireEvent.click(screen.getByText('save'));
|
||||
|
||||
expect(screen.getByTestId('model-open')).toHaveTextContent('true');
|
||||
expect(screen.getByTestId('advanced-open')).toHaveTextContent('true');
|
||||
expect(screen.getByTestId('naive-open')).toHaveTextContent('true');
|
||||
expect(screen.getByTestId('agentic-open')).toHaveTextContent('true');
|
||||
expect(scrollIntoViewMock).toHaveBeenCalledTimes(1);
|
||||
expect(scrollIntoViewMock).toHaveBeenCalledWith({
|
||||
behavior: 'smooth',
|
||||
|
||||
@@ -14,12 +14,16 @@ const ErrorIndicatorSelector =
|
||||
export function useRevealSubmitErrors() {
|
||||
const [modelSettingOpen, setModelSettingOpen] = useState(false);
|
||||
const [advancedSettingOpen, setAdvancedSettingOpen] = useState(false);
|
||||
const [naiveSettingOpen, setNaiveSettingOpen] = useState(false);
|
||||
const [agenticSettingOpen, setAgenticSettingOpen] = useState(false);
|
||||
const [invalidSubmitSignal, setInvalidSubmitSignal] = useState(0);
|
||||
const formContainerRef = useRef<HTMLFormElement>(null);
|
||||
|
||||
const handleInvalidSubmit = useCallback(() => {
|
||||
setModelSettingOpen(true);
|
||||
setAdvancedSettingOpen(true);
|
||||
setNaiveSettingOpen(true);
|
||||
setAgenticSettingOpen(true);
|
||||
setInvalidSubmitSignal((signal) => signal + 1);
|
||||
}, []);
|
||||
|
||||
@@ -44,5 +48,9 @@ export function useRevealSubmitErrors() {
|
||||
onModelSettingOpenChange: setModelSettingOpen,
|
||||
advancedSettingOpen,
|
||||
onAdvancedSettingOpenChange: setAdvancedSettingOpen,
|
||||
naiveSettingOpen,
|
||||
onNaiveSettingOpenChange: setNaiveSettingOpen,
|
||||
agenticSettingOpen,
|
||||
onAgenticSettingOpenChange: setAgenticSettingOpen,
|
||||
};
|
||||
}
|
||||
@@ -21,6 +21,7 @@ const {
|
||||
createChat,
|
||||
listChats,
|
||||
getChat,
|
||||
getAgenticRagConfig,
|
||||
updateChat,
|
||||
patchChat,
|
||||
deleteChat,
|
||||
@@ -52,6 +53,10 @@ const methods = {
|
||||
url: getChat,
|
||||
method: 'get',
|
||||
},
|
||||
getAgenticRagConfig: {
|
||||
url: getAgenticRagConfig,
|
||||
method: 'get',
|
||||
},
|
||||
updateChat: {
|
||||
url: updateChat,
|
||||
method: 'put',
|
||||
|
||||
@@ -296,6 +296,8 @@ export default {
|
||||
createChat: `${restAPIv1}/chats`,
|
||||
listChats: `${restAPIv1}/chats`,
|
||||
getChat: (chatId: string) => `${restAPIv1}/chats/${chatId}`,
|
||||
getAgenticRagConfig: (chatId: string) =>
|
||||
`${restAPIv1}/chats/${chatId}/agentic-rag`,
|
||||
updateChat: (chatId: string) => `${restAPIv1}/chats/${chatId}`,
|
||||
patchChat: (chatId: string) => `${restAPIv1}/chats/${chatId}`,
|
||||
deleteChat: (chatId: string) => `${restAPIv1}/chats/${chatId}`,
|
||||
|
||||
Reference in new issue
Block a user