wip: agui integration
This commit is contained in:
@@ -326,7 +326,19 @@ When answering questions:
|
||||
2. If asked about something outside your tools (weather, news, websites), explain that you can only access the user's local blog content.
|
||||
3. Be concise and helpful. Format post information clearly when displaying it.
|
||||
4. If a search returns no results, suggest alternative queries or filters.
|
||||
5. When asked to describe or analyze an image, use the view_image tool to see the actual image content.`;
|
||||
5. When asked to describe or analyze an image, use the view_image tool to see the actual image content.
|
||||
|
||||
Agentic UI Contract:
|
||||
- You may include structured UI payloads in your assistant response so the app can render interactive widgets.
|
||||
- You DO have the ability to return interactive AGUI payloads (including bar charts) as JSON, even though you cannot draw bitmap images.
|
||||
- When the user asks for a chart or guided workflow, prefer returning a valid AGUI payload over refusing.
|
||||
- Use JSON with specVersion: "1" and an elements array.
|
||||
- Prefer actionable widgets (cards, forms, tabs, inputs, metrics, tables, charts) when they reduce follow-up friction.
|
||||
- Keep textual guidance and UI semantically consistent.
|
||||
- Include only valid, supported action names. Supported actions include: openSettings, openPost, openMedia, openPanel, setActiveView, toggleSidebar, togglePanel, toggleAssistantSidebar.
|
||||
- Supported element types include: text, metric, list, table, action, chart, form, input, datePicker, card, image, tabs.
|
||||
- For tabs elements, include each tab with id, label, and nested elements.
|
||||
- Never invent unsupported specVersion values or unsupported element/action names.`;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -66,6 +66,9 @@ export interface ModelInfo {
|
||||
}
|
||||
|
||||
export interface SendMessageOptions {
|
||||
metadata?: {
|
||||
surface?: 'tab' | 'sidebar';
|
||||
};
|
||||
onDelta?: (delta: string) => void;
|
||||
onToolCall?: (toolCall: { name: string; args: unknown }) => void;
|
||||
onToolResult?: (result: { name: string; result: unknown }) => void;
|
||||
@@ -237,7 +240,7 @@ export class OpenCodeManager {
|
||||
userMessage: string,
|
||||
options: SendMessageOptions = {}
|
||||
): Promise<SendMessageResult> {
|
||||
const { onDelta, onToolCall, onToolResult } = options;
|
||||
const { metadata, onDelta, onToolCall, onToolResult } = options;
|
||||
|
||||
try {
|
||||
const readyCheck = await this.checkReady();
|
||||
@@ -272,11 +275,15 @@ export class OpenCodeManager {
|
||||
|
||||
// Build message history from DB (excluding system messages)
|
||||
const dbMessages = conversation.messages.filter(m => m.role !== 'system');
|
||||
const surfaceHint = metadata?.surface
|
||||
? `\n\n[Client UI surface: ${metadata.surface}. Render response UI for this surface while keeping content functionally equivalent.]`
|
||||
: '';
|
||||
const userMessageForModel = `${userMessage}${surfaceHint}`;
|
||||
// Add the new user message
|
||||
dbMessages.push({
|
||||
conversationId,
|
||||
role: 'user',
|
||||
content: userMessage,
|
||||
content: userMessageForModel,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
|
||||
|
||||
@@ -256,12 +256,13 @@ export function registerChatHandlers(): void {
|
||||
// ============ Chat Messaging ============
|
||||
|
||||
// Send a message
|
||||
ipcMain.handle('chat:sendMessage', async (_, conversationId: string, message: string) => {
|
||||
ipcMain.handle('chat:sendMessage', async (_, conversationId: string, message: string, metadata?: { surface?: 'tab' | 'sidebar' }) => {
|
||||
try {
|
||||
const manager = await getOpenCodeManager();
|
||||
const mainWindow = mainWindowGetter?.();
|
||||
|
||||
const result = await manager.sendMessage(conversationId, message, {
|
||||
metadata,
|
||||
onDelta: (delta) => {
|
||||
if (mainWindow) {
|
||||
mainWindow.webContents.send('chat-stream-delta', { conversationId, delta });
|
||||
@@ -286,6 +287,22 @@ export function registerChatHandlers(): void {
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('chat:addSystemEvent', async (_, conversationId: string, content: string) => {
|
||||
try {
|
||||
const engine = getChatEngine();
|
||||
await engine.addMessage({
|
||||
conversationId,
|
||||
role: 'system',
|
||||
content,
|
||||
createdAt: new Date(),
|
||||
});
|
||||
return { success: true };
|
||||
} catch (error) {
|
||||
console.error('[Chat IPC] Error adding system event:', error);
|
||||
return { success: false, error: (error as Error).message };
|
||||
}
|
||||
});
|
||||
|
||||
// Abort a running message
|
||||
ipcMain.handle('chat:abortMessage', async (_, conversationId: string) => {
|
||||
try {
|
||||
|
||||
@@ -84,7 +84,11 @@ function runWebContentsMenuAction(sender: any, action: AppMenuAction): boolean {
|
||||
sender.selectAll?.();
|
||||
return true;
|
||||
case 'toggleDevTools':
|
||||
sender.toggleDevTools?.();
|
||||
if (sender.isDevToolsOpened?.()) {
|
||||
sender.closeDevTools?.();
|
||||
} else {
|
||||
sender.openDevTools?.({ mode: 'detach' });
|
||||
}
|
||||
return true;
|
||||
case 'reload':
|
||||
sender.reload?.();
|
||||
|
||||
@@ -49,6 +49,20 @@ interface Rectangle {
|
||||
// Check if dev server is likely running (only in development)
|
||||
const isDev = process.env.NODE_ENV === 'development';
|
||||
|
||||
function toggleDetachedDevTools(targetWindow: BrowserWindow | null): void {
|
||||
const webContents = targetWindow?.webContents;
|
||||
if (!webContents) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (webContents.isDevToolsOpened()) {
|
||||
webContents.closeDevTools();
|
||||
return;
|
||||
}
|
||||
|
||||
webContents.openDevTools({ mode: 'detach' });
|
||||
}
|
||||
|
||||
function getWindowStatePath(): string | null {
|
||||
if (typeof app.getPath !== 'function') {
|
||||
return null;
|
||||
@@ -246,7 +260,7 @@ function createWindow(): void {
|
||||
// F12 or Ctrl+Shift+I to toggle DevTools
|
||||
if (input.key === 'F12' ||
|
||||
(input.control && input.shift && input.key.toLowerCase() === 'i')) {
|
||||
mainWindow?.webContents.toggleDevTools();
|
||||
toggleDetachedDevTools(mainWindow);
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
@@ -255,13 +269,13 @@ function createWindow(): void {
|
||||
const rendererPath = path.join(__dirname, '../renderer/index.html');
|
||||
if (isDev) {
|
||||
mainWindow.loadURL('http://localhost:5173');
|
||||
mainWindow.webContents.openDevTools();
|
||||
mainWindow.webContents.openDevTools({ mode: 'detach' });
|
||||
} else if (fs.existsSync(rendererPath)) {
|
||||
mainWindow.loadFile(rendererPath);
|
||||
} else {
|
||||
// Fallback to dev server if built files don't exist
|
||||
mainWindow.loadURL('http://localhost:5173');
|
||||
mainWindow.webContents.openDevTools();
|
||||
mainWindow.webContents.openDevTools({ mode: 'detach' });
|
||||
}
|
||||
|
||||
// Forward events to renderer
|
||||
@@ -571,6 +585,11 @@ function createApplicationMenu(): Menu {
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === 'toggleDevTools') {
|
||||
toggleDetachedDevTools(mainWindow);
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === 'viewOnGitHub') {
|
||||
void shell.openExternal('https://github.com/rfc1437/bDS');
|
||||
return;
|
||||
|
||||
@@ -299,7 +299,8 @@ export const electronAPI: ElectronAPI = {
|
||||
deleteConversation: (id: string) => ipcRenderer.invoke('chat:deleteConversation', id),
|
||||
|
||||
// Messaging
|
||||
sendMessage: (conversationId: string, message: string) => ipcRenderer.invoke('chat:sendMessage', conversationId, message),
|
||||
sendMessage: (conversationId: string, message: string, metadata?: { surface?: 'tab' | 'sidebar' }) => ipcRenderer.invoke('chat:sendMessage', conversationId, message, metadata),
|
||||
addSystemEvent: (conversationId: string, content: string) => ipcRenderer.invoke('chat:addSystemEvent', conversationId, content),
|
||||
abortMessage: (conversationId: string) => ipcRenderer.invoke('chat:abortMessage', conversationId),
|
||||
getHistory: (conversationId: string) => ipcRenderer.invoke('chat:getHistory', conversationId),
|
||||
clearMessages: (conversationId: string) => ipcRenderer.invoke('chat:clearMessages', conversationId),
|
||||
|
||||
@@ -431,6 +431,10 @@ export interface ChatTitleUpdate {
|
||||
title: string;
|
||||
}
|
||||
|
||||
export interface ChatSendMetadata {
|
||||
surface?: 'tab' | 'sidebar';
|
||||
}
|
||||
|
||||
export interface SiteValidationReport {
|
||||
sitemapPath: string;
|
||||
sitemapChanged: boolean;
|
||||
@@ -726,7 +730,8 @@ export interface ElectronAPI {
|
||||
deleteConversation: (id: string) => Promise<boolean>;
|
||||
|
||||
// Messaging
|
||||
sendMessage: (conversationId: string, message: string) => Promise<{ success: boolean; message?: string; error?: string }>;
|
||||
sendMessage: (conversationId: string, message: string, metadata?: ChatSendMetadata) => Promise<{ success: boolean; message?: string; error?: string }>;
|
||||
addSystemEvent: (conversationId: string, content: string) => Promise<{ success: boolean; error?: string }>;
|
||||
abortMessage: (conversationId: string) => Promise<void>;
|
||||
getHistory: (conversationId: string) => Promise<ChatMessage[]>;
|
||||
clearMessages: (conversationId: string) => Promise<void>;
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"menu.item.viewMedia": "Medien",
|
||||
"menu.item.toggleSidebar": "Seitenleiste umschalten",
|
||||
"menu.item.togglePanel": "Panel umschalten",
|
||||
"menu.item.toggleAssistantSidebar": "Assistenz-Seitenleiste umschalten",
|
||||
"menu.item.toggleDevTools": "Entwicklerwerkzeuge umschalten",
|
||||
"menu.item.reload": "Neu laden",
|
||||
"menu.item.forceReload": "Erzwungen neu laden",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"menu.item.viewMedia": "Media",
|
||||
"menu.item.toggleSidebar": "Toggle Sidebar",
|
||||
"menu.item.togglePanel": "Toggle Panel",
|
||||
"menu.item.toggleAssistantSidebar": "Toggle Assistant Sidebar",
|
||||
"menu.item.toggleDevTools": "Toggle Developer Tools",
|
||||
"menu.item.reload": "Reload",
|
||||
"menu.item.forceReload": "Force Reload",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"menu.item.viewMedia": "Medios",
|
||||
"menu.item.toggleSidebar": "Alternar barra lateral",
|
||||
"menu.item.togglePanel": "Alternar panel",
|
||||
"menu.item.toggleAssistantSidebar": "Alternar barra del asistente",
|
||||
"menu.item.toggleDevTools": "Alternar herramientas de desarrollo",
|
||||
"menu.item.reload": "Recargar",
|
||||
"menu.item.forceReload": "Forzar recarga",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"menu.item.viewMedia": "Médias",
|
||||
"menu.item.toggleSidebar": "Basculer la barre latérale",
|
||||
"menu.item.togglePanel": "Basculer le panneau",
|
||||
"menu.item.toggleAssistantSidebar": "Basculer le panneau Assistant",
|
||||
"menu.item.toggleDevTools": "Basculer les outils de développement",
|
||||
"menu.item.reload": "Recharger",
|
||||
"menu.item.forceReload": "Forcer le rechargement",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
"menu.item.viewMedia": "Contenuti media",
|
||||
"menu.item.toggleSidebar": "Attiva/disattiva barra laterale",
|
||||
"menu.item.togglePanel": "Attiva/disattiva pannello",
|
||||
"menu.item.toggleAssistantSidebar": "Attiva/disattiva barra assistente",
|
||||
"menu.item.toggleDevTools": "Attiva/disattiva strumenti sviluppatore",
|
||||
"menu.item.reload": "Ricarica",
|
||||
"menu.item.forceReload": "Forza ricaricamento",
|
||||
|
||||
@@ -19,6 +19,7 @@ export type AppMenuAction =
|
||||
| 'viewMedia'
|
||||
| 'toggleSidebar'
|
||||
| 'togglePanel'
|
||||
| 'toggleAssistantSidebar'
|
||||
| 'toggleDevTools'
|
||||
| 'reload'
|
||||
| 'forceReload'
|
||||
@@ -103,6 +104,7 @@ export const APP_MENU_GROUPS: AppMenuGroupDefinition[] = [
|
||||
{ label: 'menu.item.viewMedia', action: 'viewMedia', accelerator: 'CmdOrCtrl+2' },
|
||||
{ label: 'menu.item.toggleSidebar', action: 'toggleSidebar', accelerator: 'CmdOrCtrl+B' },
|
||||
{ label: 'menu.item.togglePanel', action: 'togglePanel', accelerator: 'CmdOrCtrl+J' },
|
||||
{ label: 'menu.item.toggleAssistantSidebar', action: 'toggleAssistantSidebar', accelerator: 'CmdOrCtrl+\\' },
|
||||
{ label: 'menu.item.toggleDevTools', action: 'toggleDevTools', accelerator: 'CmdOrCtrl+Shift+I' },
|
||||
{ label: '', action: 'view-separator-1', separator: true },
|
||||
{ label: 'menu.item.reload', action: 'reload' },
|
||||
@@ -156,6 +158,7 @@ export const APP_MENU_ACTION_EVENT_MAP: Partial<Record<AppMenuAction, string>> =
|
||||
viewMedia: 'menu:viewMedia',
|
||||
toggleSidebar: 'menu:toggleSidebar',
|
||||
togglePanel: 'menu:togglePanel',
|
||||
toggleAssistantSidebar: 'menu:toggleAssistantSidebar',
|
||||
toggleDevTools: 'menu:toggleDevTools',
|
||||
previewPost: 'menu:previewPost',
|
||||
publishSelected: 'menu:publishSelected',
|
||||
|
||||
Reference in New Issue
Block a user