Changed settings page to use model selector

This commit is contained in:
Lucas Oliveira 2025-10-02 14:09:46 -03:00
parent 8360388101
commit 4225bbeb0d

View file

@ -1,6 +1,6 @@
"use client"; "use client";
import { ArrowUpRight, Loader2, PlugZap, Plus, RefreshCw } from "lucide-react"; import { ArrowUpRight, Loader2, PlugZap, Plus } from "lucide-react";
import { useRouter, useSearchParams } from "next/navigation"; import { useRouter, useSearchParams } from "next/navigation";
import { Suspense, useCallback, useEffect, useState } from "react"; import { Suspense, useCallback, useEffect, useState } from "react";
import { useUpdateFlowSettingMutation } from "@/app/api/mutations/useUpdateFlowSettingMutation"; import { useUpdateFlowSettingMutation } from "@/app/api/mutations/useUpdateFlowSettingMutation";
@ -11,6 +11,8 @@ import {
} from "@/app/api/queries/useGetModelsQuery"; } from "@/app/api/queries/useGetModelsQuery";
import { useGetSettingsQuery } from "@/app/api/queries/useGetSettingsQuery"; import { useGetSettingsQuery } from "@/app/api/queries/useGetSettingsQuery";
import { ConfirmationDialog } from "@/components/confirmation-dialog"; import { ConfirmationDialog } from "@/components/confirmation-dialog";
import { LabelWrapper } from "@/components/label-wrapper";
import OpenAILogo from "@/components/logo/openai-logo";
import { ProtectedRoute } from "@/components/protected-route"; import { ProtectedRoute } from "@/components/protected-route";
import { Badge } from "@/components/ui/badge"; import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
@ -21,8 +23,6 @@ import {
CardHeader, CardHeader,
CardTitle, CardTitle,
} from "@/components/ui/card"; } from "@/components/ui/card";
import { Checkbox } from "@/components/ui/checkbox";
import { Switch } from "@/components/ui/switch";
import { Input } from "@/components/ui/input"; import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { import {
@ -31,18 +31,19 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from "@/components/ui/select"; } from "@/components/ui/select";
import { Switch } from "@/components/ui/switch";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import { useAuth } from "@/contexts/auth-context"; import { useAuth } from "@/contexts/auth-context";
import { useTask } from "@/contexts/task-context"; import { useTask } from "@/contexts/task-context";
import { useDebounce } from "@/lib/debounce";
import { import {
DEFAULT_AGENT_SETTINGS, DEFAULT_AGENT_SETTINGS,
DEFAULT_KNOWLEDGE_SETTINGS, DEFAULT_KNOWLEDGE_SETTINGS,
UI_CONSTANTS, UI_CONSTANTS,
} from "@/lib/constants"; } from "@/lib/constants";
import { useDebounce } from "@/lib/debounce";
import { ModelSelector } from "../onboarding/components/model-selector";
import { getFallbackModels, type ModelProvider } from "./helpers/model-helpers"; import { getFallbackModels, type ModelProvider } from "./helpers/model-helpers";
import { ModelSelectItems } from "./helpers/model-select-item"; import { ModelSelectItems } from "./helpers/model-select-item";
import { LabelWrapper } from "@/components/label-wrapper";
const { MAX_SYSTEM_PROMPT_CHARS } = UI_CONSTANTS; const { MAX_SYSTEM_PROMPT_CHARS } = UI_CONSTANTS;
@ -114,7 +115,8 @@ function KnowledgeSourcesPage() {
const [chunkOverlap, setChunkOverlap] = useState<number>(50); const [chunkOverlap, setChunkOverlap] = useState<number>(50);
const [tableStructure, setTableStructure] = useState<boolean>(false); const [tableStructure, setTableStructure] = useState<boolean>(false);
const [ocr, setOcr] = useState<boolean>(false); const [ocr, setOcr] = useState<boolean>(false);
const [pictureDescriptions, setPictureDescriptions] = useState<boolean>(false); const [pictureDescriptions, setPictureDescriptions] =
useState<boolean>(false);
// Fetch settings using React Query // Fetch settings using React Query
const { data: settings = {} } = useGetSettingsQuery({ const { data: settings = {} } = useGetSettingsQuery({
@ -131,7 +133,7 @@ function KnowledgeSourcesPage() {
{ {
enabled: enabled:
(isAuthenticated || isNoAuthMode) && currentProvider === "openai", (isAuthenticated || isNoAuthMode) && currentProvider === "openai",
} },
); );
const { data: ollamaModelsData } = useGetOllamaModelsQuery( const { data: ollamaModelsData } = useGetOllamaModelsQuery(
@ -139,14 +141,14 @@ function KnowledgeSourcesPage() {
{ {
enabled: enabled:
(isAuthenticated || isNoAuthMode) && currentProvider === "ollama", (isAuthenticated || isNoAuthMode) && currentProvider === "ollama",
} },
); );
const { data: ibmModelsData } = useGetIBMModelsQuery( const { data: ibmModelsData } = useGetIBMModelsQuery(
undefined, // No params for now, could be extended later undefined, // No params for now, could be extended later
{ {
enabled: (isAuthenticated || isNoAuthMode) && currentProvider === "ibm", enabled: (isAuthenticated || isNoAuthMode) && currentProvider === "ibm",
} },
); );
// Select the appropriate models data based on provider // Select the appropriate models data based on provider
@ -164,7 +166,7 @@ function KnowledgeSourcesPage() {
onSuccess: () => { onSuccess: () => {
console.log("Setting updated successfully"); console.log("Setting updated successfully");
}, },
onError: error => { onError: (error) => {
console.error("Failed to update setting:", error.message); console.error("Failed to update setting:", error.message);
}, },
}); });
@ -174,7 +176,7 @@ function KnowledgeSourcesPage() {
(variables: Parameters<typeof updateFlowSettingMutation.mutate>[0]) => { (variables: Parameters<typeof updateFlowSettingMutation.mutate>[0]) => {
updateFlowSettingMutation.mutate(variables); updateFlowSettingMutation.mutate(variables);
}, },
500 500,
); );
// Sync system prompt state with settings data // Sync system prompt state with settings data
@ -303,8 +305,8 @@ function KnowledgeSourcesPage() {
// Initialize connectors list with metadata from backend // Initialize connectors list with metadata from backend
const initialConnectors = connectorTypes const initialConnectors = connectorTypes
.filter(type => connectorsResult.connectors[type].available) // Only show available connectors .filter((type) => connectorsResult.connectors[type].available) // Only show available connectors
.map(type => ({ .map((type) => ({
id: type, id: type,
name: connectorsResult.connectors[type].name, name: connectorsResult.connectors[type].name,
description: connectorsResult.connectors[type].description, description: connectorsResult.connectors[type].description,
@ -323,20 +325,20 @@ function KnowledgeSourcesPage() {
const data = await response.json(); const data = await response.json();
const connections = data.connections || []; const connections = data.connections || [];
const activeConnection = connections.find( const activeConnection = connections.find(
(conn: Connection) => conn.is_active (conn: Connection) => conn.is_active,
); );
const isConnected = activeConnection !== undefined; const isConnected = activeConnection !== undefined;
setConnectors(prev => setConnectors((prev) =>
prev.map(c => prev.map((c) =>
c.type === connectorType c.type === connectorType
? { ? {
...c, ...c,
status: isConnected ? "connected" : "not_connected", status: isConnected ? "connected" : "not_connected",
connectionId: activeConnection?.connection_id, connectionId: activeConnection?.connection_id,
} }
: c : c,
) ),
); );
} }
} }
@ -347,7 +349,7 @@ function KnowledgeSourcesPage() {
const handleConnect = async (connector: Connector) => { const handleConnect = async (connector: Connector) => {
setIsConnecting(connector.id); setIsConnecting(connector.id);
setSyncResults(prev => ({ ...prev, [connector.id]: null })); setSyncResults((prev) => ({ ...prev, [connector.id]: null }));
try { try {
// Use the shared auth callback URL, same as connectors page // Use the shared auth callback URL, same as connectors page
@ -379,7 +381,7 @@ function KnowledgeSourcesPage() {
`response_type=code&` + `response_type=code&` +
`scope=${result.oauth_config.scopes.join(" ")}&` + `scope=${result.oauth_config.scopes.join(" ")}&` +
`redirect_uri=${encodeURIComponent( `redirect_uri=${encodeURIComponent(
result.oauth_config.redirect_uri result.oauth_config.redirect_uri,
)}&` + )}&` +
`access_type=offline&` + `access_type=offline&` +
`prompt=consent&` + `prompt=consent&` +
@ -508,9 +510,9 @@ function KnowledgeSourcesPage() {
// Watch for task completions and refresh stats // Watch for task completions and refresh stats
useEffect(() => { useEffect(() => {
// Find newly completed tasks by comparing with previous state // Find newly completed tasks by comparing with previous state
const newlyCompletedTasks = tasks.filter(task => { const newlyCompletedTasks = tasks.filter((task) => {
const wasCompleted = const wasCompleted =
prevTasks.find(prev => prev.task_id === task.task_id)?.status === prevTasks.find((prev) => prev.task_id === task.task_id)?.status ===
"completed"; "completed";
return task.status === "completed" && !wasCompleted; return task.status === "completed" && !wasCompleted;
}); });
@ -533,7 +535,7 @@ function KnowledgeSourcesPage() {
const handleEditInLangflow = ( const handleEditInLangflow = (
flowType: "chat" | "ingest", flowType: "chat" | "ingest",
closeDialog: () => void closeDialog: () => void,
) => { ) => {
// Select the appropriate flow ID and edit URL based on flow type // Select the appropriate flow ID and edit URL based on flow type
const targetFlowId = const targetFlowId =
@ -564,7 +566,7 @@ function KnowledgeSourcesPage() {
fetch(`/api/reset-flow/retrieval`, { fetch(`/api/reset-flow/retrieval`, {
method: "POST", method: "POST",
}) })
.then(response => { .then((response) => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
@ -577,7 +579,7 @@ function KnowledgeSourcesPage() {
handleModelChange(DEFAULT_AGENT_SETTINGS.llm_model); handleModelChange(DEFAULT_AGENT_SETTINGS.llm_model);
closeDialog(); // Close after successful completion closeDialog(); // Close after successful completion
}) })
.catch(error => { .catch((error) => {
console.error("Error restoring retrieval flow:", error); console.error("Error restoring retrieval flow:", error);
closeDialog(); // Close even on error (could show error toast instead) closeDialog(); // Close even on error (could show error toast instead)
}); });
@ -587,7 +589,7 @@ function KnowledgeSourcesPage() {
fetch(`/api/reset-flow/ingest`, { fetch(`/api/reset-flow/ingest`, {
method: "POST", method: "POST",
}) })
.then(response => { .then((response) => {
if (response.ok) { if (response.ok) {
return response.json(); return response.json();
} }
@ -602,7 +604,7 @@ function KnowledgeSourcesPage() {
setPictureDescriptions(false); setPictureDescriptions(false);
closeDialog(); // Close after successful completion closeDialog(); // Close after successful completion
}) })
.catch(error => { .catch((error) => {
console.error("Error restoring ingest flow:", error); console.error("Error restoring ingest flow:", error);
closeDialog(); // Close even on error (could show error toast instead) closeDialog(); // Close even on error (could show error toast instead)
}); });
@ -700,7 +702,7 @@ function KnowledgeSourcesPage() {
{/* Connectors Grid */} {/* Connectors Grid */}
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
{connectors.map(connector => ( {connectors.map((connector) => (
<Card key={connector.id} className="relative flex flex-col"> <Card key={connector.id} className="relative flex flex-col">
<CardHeader> <CardHeader>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
@ -834,7 +836,7 @@ function KnowledgeSourcesPage() {
} }
confirmText="Proceed" confirmText="Proceed"
confirmIcon={<ArrowUpRight />} confirmIcon={<ArrowUpRight />}
onConfirm={closeDialog => onConfirm={(closeDialog) =>
handleEditInLangflow("chat", closeDialog) handleEditInLangflow("chat", closeDialog)
} }
variant="warning" variant="warning"
@ -846,32 +848,18 @@ function KnowledgeSourcesPage() {
<div className="space-y-6"> <div className="space-y-6">
<div className="space-y-2"> <div className="space-y-2">
<LabelWrapper <LabelWrapper
helperText="Model used for chat"
id="model-select"
label="Language model" label="Language model"
required helperText="Model used for chat"
id="embedding-model"
required={true}
> >
<Select <ModelSelector
value={ options={modelsData?.language_models || []}
settings.agent?.llm_model || noOptionsPlaceholder={modelsData ? "No language models detected." : "Loading models..."}
modelsData?.language_models?.find(m => m.default)?.value || icon={<OpenAILogo className="w-4 h-4" />}
"gpt-4" value={modelsData ? settings.agent?.llm_model || "" : ""}
}
onValueChange={handleModelChange} onValueChange={handleModelChange}
>
<SelectTrigger id="model-select">
<SelectValue placeholder="Select a model" />
</SelectTrigger>
<SelectContent>
<ModelSelectItems
models={modelsData?.language_models}
fallbackModels={
getFallbackModels(currentProvider).language
}
provider={currentProvider}
/> />
</SelectContent>
</Select>
</LabelWrapper> </LabelWrapper>
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
@ -882,7 +870,7 @@ function KnowledgeSourcesPage() {
id="system-prompt" id="system-prompt"
placeholder="Enter your agent instructions here..." placeholder="Enter your agent instructions here..."
value={systemPrompt} value={systemPrompt}
onChange={e => setSystemPrompt(e.target.value)} onChange={(e) => setSystemPrompt(e.target.value)}
rows={6} rows={6}
className={`resize-none ${ className={`resize-none ${
systemPrompt.length > MAX_SYSTEM_PROMPT_CHARS systemPrompt.length > MAX_SYSTEM_PROMPT_CHARS
@ -993,7 +981,7 @@ function KnowledgeSourcesPage() {
confirmText="Proceed" confirmText="Proceed"
confirmIcon={<ArrowUpRight />} confirmIcon={<ArrowUpRight />}
variant="warning" variant="warning"
onConfirm={closeDialog => onConfirm={(closeDialog) =>
handleEditInLangflow("ingest", closeDialog) handleEditInLangflow("ingest", closeDialog)
} }
/> />
@ -1013,7 +1001,8 @@ function KnowledgeSourcesPage() {
disabled={true} disabled={true}
value={ value={
settings.knowledge?.embedding_model || settings.knowledge?.embedding_model ||
modelsData?.embedding_models?.find(m => m.default)?.value || modelsData?.embedding_models?.find((m) => m.default)
?.value ||
"text-embedding-ada-002" "text-embedding-ada-002"
} }
onValueChange={handleEmbeddingModelChange} onValueChange={handleEmbeddingModelChange}
@ -1051,7 +1040,7 @@ function KnowledgeSourcesPage() {
type="number" type="number"
min="1" min="1"
value={chunkSize} value={chunkSize}
onChange={e => handleChunkSizeChange(e.target.value)} onChange={(e) => handleChunkSizeChange(e.target.value)}
className="w-full pr-20" className="w-full pr-20"
/> />
<div className="absolute inset-y-0 right-0 flex items-center pr-8 pointer-events-none"> <div className="absolute inset-y-0 right-0 flex items-center pr-8 pointer-events-none">
@ -1074,7 +1063,7 @@ function KnowledgeSourcesPage() {
type="number" type="number"
min="0" min="0"
value={chunkOverlap} value={chunkOverlap}
onChange={e => handleChunkOverlapChange(e.target.value)} onChange={(e) => handleChunkOverlapChange(e.target.value)}
className="w-full pr-20" className="w-full pr-20"
/> />
<div className="absolute inset-y-0 right-0 flex items-center pr-8 pointer-events-none"> <div className="absolute inset-y-0 right-0 flex items-center pr-8 pointer-events-none">
@ -1113,7 +1102,8 @@ function KnowledgeSourcesPage() {
OCR OCR
</Label> </Label>
<div className="text-sm text-muted-foreground"> <div className="text-sm text-muted-foreground">
Extracts text from images/PDFs. Ingest is slower when enabled. Extracts text from images/PDFs. Ingest is slower when
enabled.
</div> </div>
</div> </div>
<Switch <Switch