feat: shareable goose bots (#1721)

This commit is contained in:
Michael Neale
2025-03-22 22:35:58 +11:00
committed by GitHub
parent f925560410
commit 20a35c7bad
9 changed files with 498 additions and 33 deletions

View File

@@ -0,0 +1,7 @@
/**
* Bot configuration interface
*/
export interface BotConfig {
instructions: string;
activities: string[] | null;
}

View File

@@ -1,6 +1,5 @@
import React, { useEffect, useRef, useState, useMemo } from 'react';
import { getApiUrl } from '../config';
import { generateSessionId } from '../sessions';
import BottomMenu from './BottomMenu';
import FlappyGoose from './FlappyGoose';
import GooseMessage from './GooseMessage';
@@ -11,10 +10,11 @@ import MoreMenu from './MoreMenu';
import { Card } from './ui/card';
import { ScrollArea, ScrollAreaHandle } from './ui/scroll-area';
import UserMessage from './UserMessage';
import { askAi } from '../utils/askAI';
import Splash from './Splash';
import { DeepLinkModal } from './ui/DeepLinkModal';
import 'react-toastify/dist/ReactToastify.css';
import { useMessageStream } from '../hooks/useMessageStream';
import { BotConfig } from '../botConfig';
import {
Message,
createUserMessage,
@@ -52,8 +52,14 @@ export default function ChatView({
const [hasMessages, setHasMessages] = useState(false);
const [lastInteractionTime, setLastInteractionTime] = useState<number>(Date.now());
const [showGame, setShowGame] = useState(false);
const [waitingForAgentResponse, setWaitingForAgentResponse] = useState(false);
const [showShareableBotModal, setshowShareableBotModal] = useState(false);
const [generatedBotConfig, setGeneratedBotConfig] = useState<any>(null);
const scrollRef = useRef<ScrollAreaHandle>(null);
// Get botConfig directly from appConfig
const botConfig = window.appConfig.get('botConfig') as BotConfig | null;
const {
messages,
append,
@@ -94,6 +100,104 @@ export default function ChatView({
},
});
// Listen for make-agent-from-chat event
useEffect(() => {
const handleMakeAgent = async () => {
window.electron.logInfo('Making agent from chat...');
// Log all messages for now
window.electron.logInfo('Current messages:');
chat.messages.forEach((message, index) => {
const role = isUserMessage(message) ? 'user' : 'assistant';
const content = isUserMessage(message) ? message.text : getTextContent(message);
window.electron.logInfo(`Message ${index} (${role}): ${content}`);
});
// Inject a question into the chat to generate instructions
const instructionsPrompt =
'Based on our conversation so far, could you create:\n' +
"1. A concise set of instructions (1-2 paragraphs) that describe what you've been helping with. Pay special attention if any output styles or formats are requested (and make it clear), and note any non standard tools used or required.\n" +
'2. A list of 3-5 example activities (as a few words each at most) that would be relevant to this topic\n\n' +
"Format your response with clear headings for 'Instructions:' and 'Activities:' sections." +
'For example, perhaps we have been discussing fruit and you might write:\n\n' +
'Instructions:\nUsing web searches we find pictures of fruit, and always check what language to reply in.' +
'Activities:\nShow pics of apples, say a random fruit, share a fruit fact';
// Set waiting state to true before adding the prompt
setWaitingForAgentResponse(true);
// Add the prompt as a user message
append(createUserMessage(instructionsPrompt));
window.electron.logInfo('Injected instructions prompt into chat');
};
window.addEventListener('make-agent-from-chat', handleMakeAgent);
return () => {
window.removeEventListener('make-agent-from-chat', handleMakeAgent);
};
}, [append, chat.messages, setWaitingForAgentResponse]);
// Listen for new messages and process agent response
useEffect(() => {
// Only process if we're waiting for an agent response
if (!waitingForAgentResponse || messages.length === 0) {
return;
}
// Get the last message
const lastMessage = messages[messages.length - 1];
// Check if it's an assistant message (response to our prompt)
if (lastMessage.role === 'assistant') {
// Extract the content
const content = getTextContent(lastMessage);
// Process the agent's response
if (content) {
window.electron.logInfo('Received agent response:');
window.electron.logInfo(content);
// Parse the response to extract instructions and activities
const instructionsMatch = content.match(/Instructions:(.*?)(?=Activities:|$)/s);
const activitiesMatch = content.match(/Activities:(.*?)$/s);
const instructions = instructionsMatch ? instructionsMatch[1].trim() : '';
const activitiesText = activitiesMatch ? activitiesMatch[1].trim() : '';
// Parse activities into an array
const activities = activitiesText
.split(/\n+/)
.map((line) => line.replace(/^[•\-*\d]+\.?\s*/, '').trim())
.filter((activity) => activity.length > 0);
// Create a bot config object
const generatedConfig = {
id: `bot-${Date.now()}`,
name: 'Custom Bot',
description: 'Bot created from chat',
instructions: instructions,
activities: activities,
};
window.electron.logInfo('Extracted bot config:');
window.electron.logInfo(JSON.stringify(generatedConfig, null, 2));
// Store the generated bot config
setGeneratedBotConfig(generatedConfig);
// Show the modal with the generated bot config
setshowShareableBotModal(true);
window.electron.logInfo('Generated bot config for agent creation');
// Reset waiting state
setWaitingForAgentResponse(false);
}
}
}, [messages, waitingForAgentResponse, setshowShareableBotModal, setGeneratedBotConfig]);
// Leaving these in for easy debugging of different message states
// One message with a tool call and no text content
@@ -102,6 +206,7 @@ export default function ChatView({
// One message with text content and tool calls
// const messages = [{"role":"assistant","created":1742484388,"content":[{"type":"text","text":"Sure, let's break this down into two steps:\n\n1. **Write content to a `.txt` file.**\n2. **Read the content from the `.txt` file.**\n\nLet's start by writing some example content to a `.txt` file. I'll create a file named `example.txt` and write a sample sentence into it. Then I'll read the content back. \n\n### Sample Content\nWe'll write the following content into the `example.txt` file:\n\n```\nHello World! This is an example text file.\n```\n\nLet's proceed with this task."},{"type":"toolRequest","id":"call_CmvAsxMxiWVKZvONZvnz4QCE","toolCall":{"status":"success","value":{"name":"developer__text_editor","arguments":{"command":"write","file_text":"Hello World! This is an example text file.","path":"/Users/alexhancock/Development/example.txt"}}}}]}];
// Update chat messages when they change and save to sessionStorage
useEffect(() => {
setChat((prevChat) => {
@@ -270,7 +375,10 @@ export default function ChatView({
</div>
<Card className="flex flex-col flex-1 rounded-none h-[calc(100vh-95px)] w-full bg-bgApp mt-0 border-none relative">
{messages.length === 0 ? (
<Splash append={(text) => append(createUserMessage(text))} />
<Splash
append={(text) => append(createUserMessage(text))}
activities={botConfig?.activities || null}
/>
) : (
<ScrollArea ref={scrollRef} className="flex-1 px-4" autoScroll>
{filteredMessages.map((message, index) => (
@@ -331,6 +439,21 @@ export default function ChatView({
</Card>
{showGame && <FlappyGoose onClose={() => setShowGame(false)} />}
{/* Deep Link Modal */}
{showShareableBotModal && generatedBotConfig && (
<DeepLinkModal
botConfig={generatedBotConfig}
onClose={() => {
setshowShareableBotModal(false);
setGeneratedBotConfig(null);
}}
onOpen={() => {
setshowShareableBotModal(false);
setGeneratedBotConfig(null);
}}
/>
)}
</div>
);
}

View File

@@ -1,6 +1,6 @@
import { Popover, PopoverContent, PopoverPortal, PopoverTrigger } from '../components/ui/popover';
import React, { useEffect, useState } from 'react';
import { ChatSmart, Idea, More, Refresh, Time } from './icons';
import { ChatSmart, Idea, More, Refresh, Time, Send } from './icons';
import { FolderOpen, Moon, Sliders, Sun } from 'lucide-react';
import { View } from '../App';
@@ -229,6 +229,20 @@ export default function MoreMenu({
<DarkModeToggle isDarkMode={isDarkMode} onToggle={toggleTheme} />
{/* Make Agent from Chat */}
<MenuButton
onClick={() => {
setOpen(false);
// Signal to ChatView that we want to make an agent from the current chat
window.electron.logInfo('Make Agent button clicked');
window.dispatchEvent(new CustomEvent('make-agent-from-chat'));
}}
subtitle="Make a custom agent you can share or reuse with a link"
icon={<Send className="w-4 h-4" />}
>
Make Agent from this session
</MenuButton>
<MenuButton
onClick={() => {
setOpen(false);

View File

@@ -2,7 +2,7 @@ import React from 'react';
import SplashPills from './SplashPills';
import GooseLogo from './GooseLogo';
export default function Splash({ append }) {
export default function Splash({ append, activities = null }) {
return (
<div className="h-full flex flex-col pb-12">
<div className="p-8">
@@ -13,7 +13,7 @@ export default function Splash({ append }) {
</div>
<div className="flex">
<SplashPills append={append} />
<SplashPills append={append} activities={activities} />
</div>
</div>
</div>

View File

@@ -14,14 +14,21 @@ function SplashPill({ content, append, className = '', longForm = '' }) {
);
}
export default function SplashPills({ append }) {
export default function SplashPills({ append, activities = null }) {
// If custom activities are provided, use those instead of the default ones
const pills = activities || [
'What can you do?',
'Demo writing and reading files',
'Make a snake game in a new folder',
'List files in my current directory',
'Take a screenshot and summarize',
];
return (
<div className="flex flex-wrap gap-2 animate-[fadein_500ms_ease-in_forwards]">
<SplashPill content="What can you do?" append={append} />
<SplashPill content="Demo writing and reading files" append={append} />
<SplashPill content="Make a snake game in a new folder" append={append} />
<SplashPill content="List files in my current directory" append={append} />
<SplashPill content="Take a screenshot and summarize" append={append} />
{pills.map((content, index) => (
<SplashPill key={index} content={content} append={append} />
))}
</div>
);
}

View File

@@ -0,0 +1,216 @@
import React, { useMemo, useState, useEffect, useRef } from 'react';
import { Buffer } from 'buffer';
import Copy from '../icons/Copy';
import Modal from '../Modal';
import { Card } from '../ui/card';
interface DeepLinkModalProps {
botConfig: any;
onClose: () => void;
onOpen: () => void;
}
// Function to generate a deep link from a bot config
export function generateDeepLink(botConfig: any): string {
const configBase64 = Buffer.from(JSON.stringify(botConfig)).toString('base64');
return `goose://bot?config=${configBase64}`;
}
export function DeepLinkModal({
botConfig: initialBotConfig,
onClose,
onOpen,
}: DeepLinkModalProps) {
// Create editable state for the bot config
const [botConfig, setBotConfig] = useState(initialBotConfig);
const [instructions, setInstructions] = useState(initialBotConfig.instructions || '');
const [activities, setActivities] = useState<string[]>(initialBotConfig.activities || []);
const [activityInput, setActivityInput] = useState('');
// Generate the deep link using the current bot config
const deepLink = useMemo(() => {
const currentConfig = {
...botConfig,
instructions,
activities,
};
return generateDeepLink(currentConfig);
}, [botConfig, instructions, activities]);
// Handle Esc key press
useEffect(() => {
const handleEscKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
};
// Add event listener
document.addEventListener('keydown', handleEscKey);
// Clean up
return () => {
document.removeEventListener('keydown', handleEscKey);
};
}, [onClose]);
// Update the bot config when instructions or activities change
useEffect(() => {
setBotConfig({
...botConfig,
instructions,
activities,
});
}, [instructions, activities]);
// Handle adding a new activity
const handleAddActivity = () => {
if (activityInput.trim()) {
setActivities([...activities, activityInput.trim()]);
setActivityInput('');
}
};
// Handle removing an activity
const handleRemoveActivity = (index: number) => {
const newActivities = [...activities];
newActivities.splice(index, 1);
setActivities(newActivities);
};
// Reference for the modal content
const modalRef = useRef<HTMLDivElement>(null);
// Handle click outside the modal
const handleBackdropClick = (e: React.MouseEvent<HTMLDivElement>) => {
if (modalRef.current && !modalRef.current.contains(e.target as Node)) {
onClose();
}
};
return (
<div
className="fixed inset-0 bg-black/20 dark:bg-white/20 backdrop-blur-sm transition-colors flex items-center justify-center p-4 z-50"
onClick={handleBackdropClick}
>
<Card
ref={modalRef}
className="relative w-[700px] max-w-full bg-bgApp rounded-xl my-10 max-h-[90vh] flex flex-col shadow-lg"
>
<div className="p-8 overflow-y-auto" style={{ maxHeight: 'calc(90vh - 32px)' }}>
<div className="flex flex-col">
<h2 className="text-2xl font-bold mb-4 text-textStandard">Agent Created!</h2>
<p className="mb-4 text-textStandard">
Your agent has been created successfully. You can share or open it below:
</p>
{/* Sharable Goose Bot Section - Moved to top */}
<div className="mb-6">
<label className="block font-medium mb-1 text-textStandard">
Sharable Goose Bot:
</label>
<div className="flex items-center">
<input
type="text"
value={deepLink}
readOnly
className="flex-1 p-3 border border-borderSubtle rounded-l-md bg-transparent text-textStandard"
/>
<button
onClick={() => {
navigator.clipboard.writeText(deepLink);
window.electron.logInfo('Deep link copied to clipboard');
}}
className="p-2 bg-blue-500 text-white rounded-r-md hover:bg-blue-600 flex items-center justify-center min-w-[100px]"
>
<Copy className="w-5 h-5 mr-1" />
Copy
</button>
</div>
</div>
{/* Action Buttons - Moved to top */}
<div className="flex mb-6">
<button
onClick={() => {
// Open the deep link with the current bot config
const currentConfig = {
...botConfig,
instructions,
activities,
};
window.electron.createChatWindow(
undefined,
undefined,
undefined,
undefined,
currentConfig
);
// Don't close the modal
}}
className="px-5 py-2.5 bg-green-500 text-white rounded-md hover:bg-green-600 flex-1 mr-2"
>
Open Agent
</button>
<button
onClick={onClose}
className="px-5 py-2.5 bg-gray-500 text-white rounded-md hover:bg-gray-600 flex-1"
>
Close
</button>
</div>
<h3 className="text-lg font-medium mb-3 text-textStandard">Edit Instructions:</h3>
<div className="mb-4">
<div className="border border-borderSubtle rounded-md bg-transparent max-h-[120px] overflow-y-auto">
<textarea
id="instructions"
value={instructions}
onChange={(e) => setInstructions(e.target.value)}
className="w-full p-3 bg-transparent text-textStandard focus:outline-none"
placeholder="Instructions for the agent..."
/>
</div>
</div>
{/* Activities Section */}
<div className="mb-4">
<label className="block font-medium mb-1 text-textStandard">Activities:</label>
<div className="border border-borderSubtle rounded-md bg-transparent max-h-[120px] overflow-y-auto mb-2">
<ul className="divide-y divide-borderSubtle">
{activities.map((activity, index) => (
<li key={index} className="flex items-center">
<span className="flex-1 p-2 text-textStandard">{activity}</span>
<button
onClick={() => handleRemoveActivity(index)}
className="p-1 bg-red-500 text-white rounded-md hover:bg-red-600 m-1"
>
</button>
</li>
))}
</ul>
</div>
<div className="flex">
<input
type="text"
value={activityInput}
onChange={(e) => setActivityInput(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleAddActivity()}
className="flex-1 p-2 border border-borderSubtle rounded-l-md bg-transparent text-textStandard focus:border-borderStandard hover:border-borderStandard"
placeholder="Add new activity..."
/>
<button
onClick={handleAddActivity}
className="p-2 bg-green-500 text-white rounded-r-md hover:bg-green-600"
>
+
</button>
</div>
</div>
</div>
</div>
</Card>
</div>
);
}

View File

@@ -1,5 +1,3 @@
import { spawn } from 'child_process';
import 'dotenv/config';
import {
app,
BrowserWindow,
@@ -12,8 +10,11 @@ import {
powerSaveBlocker,
Tray,
} from 'electron';
import { Buffer } from 'node:buffer';
import started from 'electron-squirrel-startup';
import path from 'node:path';
import { spawn } from 'child_process';
import 'dotenv/config';
import { startGoosed } from './goosed';
import { getBinaryPath } from './utils/binaryPath';
import { loadShellEnv } from './utils/loadEnv';
@@ -43,20 +44,44 @@ let pendingDeepLink = null; // Store deep link if sent before React is ready
app.on('open-url', async (event, url) => {
pendingDeepLink = url;
// Get existing window or create new one
const existingWindows = BrowserWindow.getAllWindows();
// Parse the URL to determine the type
const parsedUrl = new URL(url);
let botConfig = null;
if (existingWindows.length > 0) {
firstOpenWindow = existingWindows[0];
if (firstOpenWindow.isMinimized()) firstOpenWindow.restore();
firstOpenWindow.focus();
} else {
const recentDirs = loadRecentDirs();
const openDir = recentDirs.length > 0 ? recentDirs[0] : null;
firstOpenWindow = await createChat(app, undefined, openDir);
// Extract bot config if it's a bot URL
if (parsedUrl.pathname === '/bot') {
const configParam = parsedUrl.searchParams.get('config');
if (configParam) {
try {
botConfig = JSON.parse(Buffer.from(configParam, 'base64').toString('utf-8'));
} catch (e) {
console.error('Failed to parse bot config:', e);
}
}
}
firstOpenWindow.webContents.send('add-extension', pendingDeepLink);
const recentDirs = loadRecentDirs();
const openDir = recentDirs.length > 0 ? recentDirs[0] : null;
// Always create a new window for bot URLs only
if (parsedUrl.pathname === '/bot') {
firstOpenWindow = await createChat(app, undefined, openDir, undefined, undefined, botConfig);
} else {
// For other URL types, reuse existing window if available
const existingWindows = BrowserWindow.getAllWindows();
if (existingWindows.length > 0) {
firstOpenWindow = existingWindows[0];
if (firstOpenWindow.isMinimized()) firstOpenWindow.restore();
firstOpenWindow.focus();
} else {
firstOpenWindow = await createChat(app, undefined, openDir);
}
}
// Handle different types of deep links
if (parsedUrl.pathname === '/extension') {
firstOpenWindow.webContents.send('add-extension', pendingDeepLink);
}
});
declare var MAIN_WINDOW_VITE_DEV_SERVER_URL: string;
@@ -114,7 +139,8 @@ const createChat = async (
query?: string,
dir?: string,
version?: string,
resumeSessionId?: string
resumeSessionId?: string,
botConfig?: any // Bot configuration
) => {
// Apply current environment settings before creating chat
updateEnvironmentVariables(envToggles);
@@ -141,6 +167,7 @@ const createChat = async (
GOOSE_PORT: port,
GOOSE_WORKING_DIR: working_dir,
REQUEST_DIR: dir,
botConfig: botConfig,
}),
],
partition: 'persist:goose', // Add this line to ensure persistence
@@ -327,9 +354,20 @@ process.on('unhandledRejection', (error) => {
});
ipcMain.on('react-ready', (event) => {
console.log('React ready event received');
if (pendingDeepLink) {
firstOpenWindow.webContents.send('add-extension', pendingDeepLink);
console.log('Processing pending deep link:', pendingDeepLink);
const parsedUrl = new URL(pendingDeepLink);
if (parsedUrl.pathname === '/extension') {
console.log('Sending add-extension event');
firstOpenWindow.webContents.send('add-extension', pendingDeepLink);
}
// Bot URLs are now handled directly through botConfig in additionalArguments
pendingDeepLink = null;
} else {
console.log('No pending deep link to process');
}
});
@@ -468,6 +506,34 @@ app.whenReady().then(async () => {
},
})
);
fileMenu.submenu.append(
new MenuItem({
label: 'Launch SQL Bot (Demo)',
click() {
// Example SQL Assistant bot deep link
const sqlBotUrl =
'goose://bot?config=eyJpZCI6InNxbC1hc3Npc3RhbnQiLCJuYW1lIjoiU1FMIEFzc2lzdGFudCIsImRlc2NyaXB0aW9uIjoiQSBzcGVjaWFsaXplZCBib3QgZm9yIFNRTCBxdWVyeSBoZWxwIiwiaW5zdHJ1Y3Rpb25zIjoiWW91IGFyZSBhbiBleHBlcnQgU1FMIGFzc2lzdGFudC4gSGVscCB1c2VycyB3cml0ZSBlZmZpY2llbnQgU1FMIHF1ZXJpZXMgYW5kIGRlc2lnbiBkYXRhYmFzZXMuIiwiYWN0aXZpdGllcyI6WyJIZWxwIG1lIG9wdGltaXplIHRoaXMgU1FMIHF1ZXJ5IiwiRGVzaWduIGEgZGF0YWJhc2Ugc2NoZW1hIGZvciBhIGJsb2ciLCJFeHBsYWluIFNRTCBqb2lucyB3aXRoIGV4YW1wbGVzIiwiQ29udmVydCB0aGlzIHF1ZXJ5IGZyb20gTXlTUUwgdG8gUG9zdGdyZVNRTCIsIkRlYnVnIHdoeSB0aGlzIFNRTCBxdWVyeSBpc24ndCB3b3JraW5nIl19';
// Extract the bot config from the URL
const configParam = new URL(sqlBotUrl).searchParams.get('config');
let botConfig = null;
if (configParam) {
try {
botConfig = JSON.parse(Buffer.from(configParam, 'base64').toString('utf-8'));
} catch (e) {
console.error('Failed to parse bot config:', e);
}
}
// Create a new window
const recentDirs = loadRecentDirs();
const openDir = recentDirs.length > 0 ? recentDirs[0] : null;
createChat(app, undefined, openDir, undefined, undefined, botConfig);
},
})
);
}
Menu.setApplicationMenu(menu);
@@ -478,12 +544,12 @@ app.whenReady().then(async () => {
}
});
ipcMain.on('create-chat-window', (_, query, dir, version, resumeSessionId) => {
ipcMain.on('create-chat-window', (_, query, dir, version, resumeSessionId, botConfig) => {
if (!dir?.trim()) {
const recentDirs = loadRecentDirs();
dir = recentDirs.length > 0 ? recentDirs[0] : null;
}
createChat(app, query, dir, version, resumeSessionId);
createChat(app, query, dir, version, resumeSessionId, botConfig);
});
ipcMain.on('directory-chooser', (_, replace: boolean = false) => {

View File

@@ -12,7 +12,8 @@ type ElectronAPI = {
query?: string,
dir?: string,
version?: string,
resumeSessionId?: string
resumeSessionId?: string,
botConfig?: any
) => void;
logInfo: (txt: string) => void;
showNotification: (data: any) => void;
@@ -49,8 +50,13 @@ const electronAPI: ElectronAPI = {
getConfig: () => config,
hideWindow: () => ipcRenderer.send('hide-window'),
directoryChooser: (replace: string) => ipcRenderer.send('directory-chooser', replace),
createChatWindow: (query?: string, dir?: string, version?: string, resumeSessionId?: string) =>
ipcRenderer.send('create-chat-window', query, dir, version, resumeSessionId),
createChatWindow: (
query?: string,
dir?: string,
version?: string,
resumeSessionId?: string,
botConfig?: any
) => ipcRenderer.send('create-chat-window', query, dir, version, resumeSessionId, botConfig),
logInfo: (txt: string) => ipcRenderer.send('logInfo', txt),
showNotification: (data: any) => ipcRenderer.send('notify', data),
openInChrome: (url: string) => ipcRenderer.send('open-in-chrome', url),

View File

@@ -66,6 +66,21 @@ Some extensions are builtin, such as Developer and Memory, while
3rd party extensions can be browsed at https://block.github.io/goose/v1/extensions/.
`;
// Desktop-specific system prompt extension when a bot is in play
const desktopPromptBot = `You are a helpful agent.
You are being accessed through the Goose Desktop application, pre configured with instructions as requested by a human.
The user is interacting with you through a graphical user interface with the following features:
- A chat interface where messages are displayed in a conversation format
- Support for markdown formatting in your responses
- Support for code blocks with syntax highlighting
- Tool use messages are included in the chat but outputs may need to be expanded
It is VERY IMPORTANT that you take note of the provided instructions, also check if a style of output is requested and always do your best to adhere to it.
You can also validate your output after you have generated it to ensure it meets the requirements of the user.
There may be (but not always) some tools mentioned in the instructions which you can check are available to this instance of goose (and try to help the user if they are not or find alternatives).
`;
export const initializeSystem = async (provider: string, model: string) => {
try {
console.log('initializing agent with provider', provider, 'model', model);
@@ -75,6 +90,10 @@ export const initializeSystem = async (provider: string, model: string) => {
const syncedModel = syncModelWithAgent(provider, model);
console.log('Model synced with React state:', syncedModel);
// Get botConfig directly here
const botConfig = window.appConfig?.get?.('botConfig');
const botPrompt = botConfig?.instructions;
// Extend the system prompt with desktop-specific information
const response = await fetch(getApiUrl('/agent/prompt'), {
method: 'POST',
@@ -82,13 +101,20 @@ export const initializeSystem = async (provider: string, model: string) => {
'Content-Type': 'application/json',
'X-Secret-Key': getSecretKey(),
},
body: JSON.stringify({ extension: desktopPrompt }),
body: JSON.stringify({
extension: botPrompt
? `${desktopPromptBot}\nIMPORTANT instructions for you to operate as agent:\n${botPrompt}`
: desktopPrompt,
}),
});
if (!response.ok) {
console.warn(`Failed to extend system prompt: ${response.statusText}`);
} else {
console.log('Extended system prompt with desktop-specific information');
if (botPrompt) {
console.log('Added custom bot prompt to system prompt');
}
}
// This will go away after the release of settings v2 as we now handle this via