User configurable templates (#6420)
Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -1014,6 +1014,140 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"/config/prompts": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"super::routes::prompts"
|
||||
],
|
||||
"operationId": "get_prompts",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "List of all available prompts",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PromptsListResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/config/prompts/{name}": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"super::routes::prompts"
|
||||
],
|
||||
"operationId": "get_prompt",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"description": "Prompt template name (e.g., system.md)",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Prompt content retrieved successfully",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/PromptContentResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Prompt not found"
|
||||
}
|
||||
}
|
||||
},
|
||||
"put": {
|
||||
"tags": [
|
||||
"super::routes::prompts"
|
||||
],
|
||||
"operationId": "save_prompt",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"description": "Prompt template name (e.g., system.md)",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SavePromptRequest"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Prompt saved successfully",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Prompt not found"
|
||||
},
|
||||
"500": {
|
||||
"description": "Failed to save prompt"
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"tags": [
|
||||
"super::routes::prompts"
|
||||
],
|
||||
"operationId": "reset_prompt",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "name",
|
||||
"in": "path",
|
||||
"description": "Prompt template name (e.g., system.md)",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "Prompt reset to default successfully",
|
||||
"content": {
|
||||
"text/plain": {
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"404": {
|
||||
"description": "Prompt not found"
|
||||
},
|
||||
"500": {
|
||||
"description": "Failed to reset prompt"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/config/providers": {
|
||||
"get": {
|
||||
"tags": [
|
||||
@@ -4691,6 +4825,43 @@
|
||||
"Tool"
|
||||
]
|
||||
},
|
||||
"PromptContentResponse": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"name",
|
||||
"content",
|
||||
"default_content",
|
||||
"is_customized"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
},
|
||||
"default_content": {
|
||||
"type": "string"
|
||||
},
|
||||
"is_customized": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"PromptsListResponse": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"prompts"
|
||||
],
|
||||
"properties": {
|
||||
"prompts": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Template"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProviderDetails": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -5352,6 +5523,17 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"SavePromptRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
"content"
|
||||
],
|
||||
"properties": {
|
||||
"content": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"SaveRecipeRequest": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
@@ -5970,6 +6152,34 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"Template": {
|
||||
"type": "object",
|
||||
"description": "Information about a template including its content and customization status",
|
||||
"required": [
|
||||
"name",
|
||||
"description",
|
||||
"default_content",
|
||||
"is_customized"
|
||||
],
|
||||
"properties": {
|
||||
"default_content": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"is_customized": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"user_content": {
|
||||
"type": "string",
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"TextContent": {
|
||||
"type": "object",
|
||||
"required": [
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -617,6 +617,17 @@ export type PricingResponse = {
|
||||
|
||||
export type PrincipalType = 'Extension' | 'Tool';
|
||||
|
||||
export type PromptContentResponse = {
|
||||
content: string;
|
||||
default_content: string;
|
||||
is_customized: boolean;
|
||||
name: string;
|
||||
};
|
||||
|
||||
export type PromptsListResponse = {
|
||||
prompts: Array<Template>;
|
||||
};
|
||||
|
||||
export type ProviderDetails = {
|
||||
is_configured: boolean;
|
||||
metadata: ProviderMetadata;
|
||||
@@ -857,6 +868,10 @@ export type RunNowResponse = {
|
||||
session_id: string;
|
||||
};
|
||||
|
||||
export type SavePromptRequest = {
|
||||
content: string;
|
||||
};
|
||||
|
||||
export type SaveRecipeRequest = {
|
||||
id?: string | null;
|
||||
recipe: Recipe;
|
||||
@@ -1041,6 +1056,17 @@ export type TelemetryEventRequest = {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Information about a template including its content and customization status
|
||||
*/
|
||||
export type Template = {
|
||||
default_content: string;
|
||||
description: string;
|
||||
is_customized: boolean;
|
||||
name: string;
|
||||
user_content?: string | null;
|
||||
};
|
||||
|
||||
export type TextContent = {
|
||||
_meta?: {
|
||||
[key: string]: unknown;
|
||||
@@ -2005,6 +2031,114 @@ export type GetPricingResponses = {
|
||||
|
||||
export type GetPricingResponse = GetPricingResponses[keyof GetPricingResponses];
|
||||
|
||||
export type GetPromptsData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
query?: never;
|
||||
url: '/config/prompts';
|
||||
};
|
||||
|
||||
export type GetPromptsResponses = {
|
||||
/**
|
||||
* List of all available prompts
|
||||
*/
|
||||
200: PromptsListResponse;
|
||||
};
|
||||
|
||||
export type GetPromptsResponse = GetPromptsResponses[keyof GetPromptsResponses];
|
||||
|
||||
export type ResetPromptData = {
|
||||
body?: never;
|
||||
path: {
|
||||
/**
|
||||
* Prompt template name (e.g., system.md)
|
||||
*/
|
||||
name: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/config/prompts/{name}';
|
||||
};
|
||||
|
||||
export type ResetPromptErrors = {
|
||||
/**
|
||||
* Prompt not found
|
||||
*/
|
||||
404: unknown;
|
||||
/**
|
||||
* Failed to reset prompt
|
||||
*/
|
||||
500: unknown;
|
||||
};
|
||||
|
||||
export type ResetPromptResponses = {
|
||||
/**
|
||||
* Prompt reset to default successfully
|
||||
*/
|
||||
200: string;
|
||||
};
|
||||
|
||||
export type ResetPromptResponse = ResetPromptResponses[keyof ResetPromptResponses];
|
||||
|
||||
export type GetPromptData = {
|
||||
body?: never;
|
||||
path: {
|
||||
/**
|
||||
* Prompt template name (e.g., system.md)
|
||||
*/
|
||||
name: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/config/prompts/{name}';
|
||||
};
|
||||
|
||||
export type GetPromptErrors = {
|
||||
/**
|
||||
* Prompt not found
|
||||
*/
|
||||
404: unknown;
|
||||
};
|
||||
|
||||
export type GetPromptResponses = {
|
||||
/**
|
||||
* Prompt content retrieved successfully
|
||||
*/
|
||||
200: PromptContentResponse;
|
||||
};
|
||||
|
||||
export type GetPromptResponse = GetPromptResponses[keyof GetPromptResponses];
|
||||
|
||||
export type SavePromptData = {
|
||||
body: SavePromptRequest;
|
||||
path: {
|
||||
/**
|
||||
* Prompt template name (e.g., system.md)
|
||||
*/
|
||||
name: string;
|
||||
};
|
||||
query?: never;
|
||||
url: '/config/prompts/{name}';
|
||||
};
|
||||
|
||||
export type SavePromptErrors = {
|
||||
/**
|
||||
* Prompt not found
|
||||
*/
|
||||
404: unknown;
|
||||
/**
|
||||
* Failed to save prompt
|
||||
*/
|
||||
500: unknown;
|
||||
};
|
||||
|
||||
export type SavePromptResponses = {
|
||||
/**
|
||||
* Prompt saved successfully
|
||||
*/
|
||||
200: string;
|
||||
};
|
||||
|
||||
export type SavePromptResponse = SavePromptResponses[keyof SavePromptResponses];
|
||||
|
||||
export type ProvidersData = {
|
||||
body?: never;
|
||||
path?: never;
|
||||
|
||||
@@ -302,9 +302,8 @@ function BaseChatContent({
|
||||
name: session?.name || 'No Session',
|
||||
};
|
||||
|
||||
// Update the global chat context when session name changes
|
||||
const lastSetNameRef = useRef<string>('');
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const currentSessionName = session?.name;
|
||||
if (currentSessionName && currentSessionName !== lastSetNameRef.current) {
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import {
|
||||
getPrompt,
|
||||
getPrompts,
|
||||
PromptContentResponse,
|
||||
Template,
|
||||
resetPrompt,
|
||||
savePrompt,
|
||||
} from '../../api';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
|
||||
import { Button } from '../ui/button';
|
||||
import { AlertTriangle, RotateCcw, ArrowLeft } from 'lucide-react';
|
||||
import { toast } from 'react-toastify';
|
||||
|
||||
export default function PromptsSettingsSection() {
|
||||
const [prompts, setPrompts] = useState<Template[]>([]);
|
||||
const [selectedPrompt, setSelectedPrompt] = useState<string | null>(null);
|
||||
const [promptData, setPromptData] = useState<PromptContentResponse | null>(null);
|
||||
const [content, setContent] = useState('');
|
||||
const [hasChanges, setHasChanges] = useState(false);
|
||||
|
||||
const fetchPrompts = async () => {
|
||||
try {
|
||||
const response = await getPrompts();
|
||||
if (response.data) {
|
||||
setPrompts(response.data.prompts);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch prompts:', error);
|
||||
toast.error('Failed to load prompts');
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchPrompts();
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (selectedPrompt) {
|
||||
const fetchPrompt = async () => {
|
||||
try {
|
||||
const response = await getPrompt({ path: { name: selectedPrompt } });
|
||||
if (response.data) {
|
||||
setPromptData(response.data);
|
||||
setContent(response.data.content);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch prompt:', error);
|
||||
toast.error('Failed to load prompt');
|
||||
}
|
||||
};
|
||||
fetchPrompt();
|
||||
}
|
||||
}, [selectedPrompt]);
|
||||
|
||||
useEffect(() => {
|
||||
if (promptData) {
|
||||
setHasChanges(content !== promptData.content);
|
||||
}
|
||||
}, [content, promptData]);
|
||||
|
||||
const handleResetAll = async () => {
|
||||
if (
|
||||
!window.confirm(
|
||||
'Are you sure you want to reset all prompts to their defaults? This cannot be undone.'
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const customizedPrompts = prompts.filter((p) => p.is_customized);
|
||||
for (const prompt of customizedPrompts) {
|
||||
await resetPrompt({ path: { name: prompt.name } });
|
||||
}
|
||||
toast.success('All prompts reset to defaults');
|
||||
fetchPrompts();
|
||||
} catch (error) {
|
||||
console.error('Failed to reset all prompts:', error);
|
||||
toast.error('Failed to reset prompts');
|
||||
}
|
||||
};
|
||||
|
||||
const handleSave = async () => {
|
||||
if (!selectedPrompt) return;
|
||||
try {
|
||||
await savePrompt({
|
||||
path: { name: selectedPrompt },
|
||||
body: { content },
|
||||
});
|
||||
toast.success('Prompt saved');
|
||||
setPromptData((prev) => (prev ? { ...prev, content, is_customized: true } : null));
|
||||
fetchPrompts();
|
||||
} catch (error) {
|
||||
console.error('Failed to save prompt:', error);
|
||||
toast.error('Failed to save prompt');
|
||||
}
|
||||
};
|
||||
|
||||
const handleReset = async () => {
|
||||
if (!selectedPrompt) return;
|
||||
if (
|
||||
!window.confirm(
|
||||
'Are you sure you want to reset this prompt to its default? This cannot be undone.'
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await resetPrompt({ path: { name: selectedPrompt } });
|
||||
if (promptData) {
|
||||
setContent(promptData.default_content);
|
||||
setPromptData({ ...promptData, content: promptData.default_content, is_customized: false });
|
||||
}
|
||||
fetchPrompts();
|
||||
toast.success('Prompt reset to default');
|
||||
} catch (error) {
|
||||
console.error('Failed to reset prompt:', error);
|
||||
toast.error('Failed to reset prompt');
|
||||
}
|
||||
};
|
||||
|
||||
const handleRestoreDefault = () => {
|
||||
if (promptData) {
|
||||
if (hasChanges) {
|
||||
if (!window.confirm('Replace current content with default? Your changes will be lost.')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
setContent(promptData.default_content);
|
||||
}
|
||||
};
|
||||
|
||||
const handleBack = () => {
|
||||
if (hasChanges) {
|
||||
if (!window.confirm('You have unsaved changes. Are you sure you want to go back?')) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
setSelectedPrompt(null);
|
||||
setPromptData(null);
|
||||
setContent('');
|
||||
};
|
||||
|
||||
const hasCustomizedPrompts = prompts.some((p) => p.is_customized);
|
||||
|
||||
if (selectedPrompt) {
|
||||
return (
|
||||
<div className="space-y-4 pr-4 pb-8 mt-1">
|
||||
<Card className="pb-2 rounded-lg">
|
||||
<CardHeader className="pb-4">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleBack}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<ArrowLeft className="h-4 w-4" />
|
||||
Back to List
|
||||
</Button>
|
||||
<div className="flex items-center gap-2">
|
||||
{promptData?.is_customized && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleReset}
|
||||
className="flex items-center gap-2"
|
||||
>
|
||||
<RotateCcw className="h-4 w-4" />
|
||||
Reset to Default
|
||||
</Button>
|
||||
)}
|
||||
<Button onClick={handleSave} disabled={!hasChanges} size="sm">
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<CardTitle>Edit: {selectedPrompt}</CardTitle>
|
||||
{promptData?.is_customized && (
|
||||
<span className="px-2 py-0.5 text-xs rounded-full bg-blue-500/20 text-blue-600 dark:text-blue-400">
|
||||
Customized
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 space-y-4 flex flex-col h-full">
|
||||
<div className="text-sm text-text-muted bg-background-subtle p-3 rounded-lg">
|
||||
<p>
|
||||
<strong>Tip:</strong> Template variables like{' '}
|
||||
<code className="bg-background-default px-1 rounded">{'{{ extensions }}'}</code> or{' '}
|
||||
<code className="bg-background-default px-1 rounded">
|
||||
{'{% for item in list %}'}
|
||||
</code>{' '}
|
||||
are replaced with actual values at runtime. Be careful not to remove required
|
||||
variables.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 flex-1 flex flex-col min-h-0">
|
||||
<div className="flex items-center justify-between">
|
||||
<label className="text-sm font-medium">Editing: {selectedPrompt}</label>
|
||||
{promptData?.is_customized && content !== promptData.default_content && (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
onClick={handleRestoreDefault}
|
||||
className="text-xs"
|
||||
>
|
||||
Restore Default
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<textarea
|
||||
value={content}
|
||||
className="w-full flex-1 min-h-[500px] border rounded-md p-3 text-sm font-mono resize-y bg-background-default text-textStandard border-borderStandard focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
onChange={(e) => setContent(e.target.value)}
|
||||
placeholder="Enter prompt content..."
|
||||
spellCheck={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{hasChanges && (
|
||||
<div className="text-sm text-yellow-600 dark:text-yellow-400">
|
||||
You have unsaved changes
|
||||
</div>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4 pr-4 pb-8 mt-1">
|
||||
<Card className="pb-2 rounded-lg border-yellow-500/50 bg-yellow-500/10">
|
||||
<CardHeader className="pb-2">
|
||||
<div className="flex items-start gap-3">
|
||||
<AlertTriangle className="h-5 w-5 text-yellow-500 flex-shrink-0 mt-1" />
|
||||
<div className="flex-1">
|
||||
<CardTitle className="text-yellow-600 dark:text-yellow-400">Prompt Editing</CardTitle>
|
||||
<p className="text-sm text-text-muted mt-2">
|
||||
Customize the prompts that define goose's behavior in different contexts. These
|
||||
prompts use Jinja2 templating syntax. Be careful when modifying template variables,
|
||||
as incorrect changes can break functionality. Please share any improvements with the
|
||||
community.
|
||||
</p>
|
||||
</div>
|
||||
{hasCustomizedPrompts && (
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={handleResetAll}
|
||||
className="flex items-center gap-2 border-yellow-500/50 hover:bg-yellow-500/20"
|
||||
>
|
||||
<RotateCcw className="h-4 w-4" />
|
||||
Reset All
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pt-4">
|
||||
<div className="space-y-2">
|
||||
{prompts.map((prompt) => (
|
||||
<div
|
||||
key={prompt.name}
|
||||
className="flex items-center justify-between p-3 rounded-lg border border-border-default hover:bg-background-subtle transition-colors"
|
||||
>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-center gap-2">
|
||||
<h4 className="font-medium text-text-default truncate">{prompt.name}</h4>
|
||||
{prompt.is_customized && (
|
||||
<span className="px-2 py-0.5 text-xs rounded-full bg-blue-500/20 text-blue-600 dark:text-blue-400">
|
||||
Customized
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<p className="text-sm text-text-muted mt-0.5 truncate">{prompt.description}</p>
|
||||
</div>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => setSelectedPrompt(prompt.name)}
|
||||
className="ml-4"
|
||||
>
|
||||
Edit
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -6,9 +6,10 @@ import SessionSharingSection from './sessions/SessionSharingSection';
|
||||
import ExternalBackendSection from './app/ExternalBackendSection';
|
||||
import AppSettingsSection from './app/AppSettingsSection';
|
||||
import ConfigSettings from './config/ConfigSettings';
|
||||
import PromptsSettingsSection from './PromptsSettingsSection';
|
||||
import { ExtensionConfig } from '../../api';
|
||||
import { MainPanelLayout } from '../Layout/MainPanelLayout';
|
||||
import { Bot, Share2, Monitor, MessageSquare } from 'lucide-react';
|
||||
import { Bot, Share2, Monitor, MessageSquare, FileText } from 'lucide-react';
|
||||
import { useState, useEffect, useRef } from 'react';
|
||||
import ChatSettingsSection from './chat/ChatSettingsSection';
|
||||
import { CONFIGURATION_ENABLED } from '../../updates';
|
||||
@@ -50,6 +51,7 @@ export default function SettingsView({
|
||||
tools: 'chat',
|
||||
app: 'app',
|
||||
chat: 'chat',
|
||||
prompts: 'prompts',
|
||||
};
|
||||
|
||||
const targetTab = sectionToTab[viewOptions.section];
|
||||
@@ -120,6 +122,14 @@ export default function SettingsView({
|
||||
<Share2 className="h-4 w-4" />
|
||||
Session
|
||||
</TabsTrigger>
|
||||
<TabsTrigger
|
||||
value="prompts"
|
||||
className="flex gap-2"
|
||||
data-testid="settings-prompts-tab"
|
||||
>
|
||||
<FileText className="h-4 w-4" />
|
||||
Prompts
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="app" className="flex gap-2" data-testid="settings-app-tab">
|
||||
<Monitor className="h-4 w-4" />
|
||||
App
|
||||
@@ -152,6 +162,13 @@ export default function SettingsView({
|
||||
</div>
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent
|
||||
value="prompts"
|
||||
className="mt-0 focus-visible:outline-none focus-visible:ring-0"
|
||||
>
|
||||
<PromptsSettingsSection />
|
||||
</TabsContent>
|
||||
|
||||
<TabsContent
|
||||
value="app"
|
||||
className="mt-0 focus-visible:outline-none focus-visible:ring-0"
|
||||
|
||||
@@ -214,20 +214,24 @@ export const SwitchModelModal = ({
|
||||
|
||||
results.forEach(({ provider: p, models, error }) => {
|
||||
const modelList = error
|
||||
? (p.metadata.known_models?.map(({ name }) => name) || [])
|
||||
: (models || []);
|
||||
? p.metadata.known_models?.map(({ name }) => name) || []
|
||||
: models || [];
|
||||
|
||||
if (error) {
|
||||
errors.push(error);
|
||||
}
|
||||
|
||||
const options: { value: string; label: string; provider: string; providerType: ProviderType }[] =
|
||||
modelList.map((m) => ({
|
||||
value: m,
|
||||
label: m,
|
||||
provider: p.name,
|
||||
providerType: p.provider_type,
|
||||
}));
|
||||
const options: {
|
||||
value: string;
|
||||
label: string;
|
||||
provider: string;
|
||||
providerType: ProviderType;
|
||||
}[] = modelList.map((m) => ({
|
||||
value: m,
|
||||
label: m,
|
||||
provider: p.name,
|
||||
providerType: p.provider_type,
|
||||
}));
|
||||
|
||||
if (p.metadata.allows_unlisted_models && p.provider_type !== 'Custom') {
|
||||
options.push({
|
||||
|
||||
@@ -215,10 +215,8 @@ export function useChatStream({
|
||||
// The backend regenerates the name after each of the first 3 user messages
|
||||
// to refine it as more context becomes available
|
||||
if (!error && sessionId) {
|
||||
const userMessageCount = messagesRef.current.filter(
|
||||
(m) => m.role === 'user'
|
||||
).length;
|
||||
|
||||
const userMessageCount = messagesRef.current.filter((m) => m.role === 'user').length;
|
||||
|
||||
// Only refresh for the first 3 user messages
|
||||
if (userMessageCount <= 3) {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user