diff --git a/frontend-modern/src/components/AI/AIChat.tsx b/frontend-modern/src/components/AI/AIChat.tsx index d85b3f7e5..77f76a9ec 100644 --- a/frontend-modern/src/components/AI/AIChat.tsx +++ b/frontend-modern/src/components/AI/AIChat.tsx @@ -215,26 +215,51 @@ export const AIChat: Component = (props) => { const [selectedModel, setSelectedModel] = createSignal(''); // Empty = use default const [showModelSelector, setShowModelSelector] = createSignal(false); + // Autonomous mode toggle + const [autonomousMode, setAutonomousMode] = createSignal(false); + const [isTogglingAutonomous, setIsTogglingAutonomous] = createSignal(false); + let messagesEndRef: HTMLDivElement | undefined; let inputRef: HTMLTextAreaElement | undefined; let abortControllerRef: AbortController | null = null; - // Fetch available models on mount using the dynamic API + // Fetch available models and settings on mount onMount(async () => { try { - const result = await AIAPI.getModels(); - if (result.models && result.models.length > 0) { - setAvailableModels(result.models.map(m => ({ + const [modelsResult, settingsResult] = await Promise.all([ + AIAPI.getModels(), + AIAPI.getSettings(), + ]); + if (modelsResult.models && modelsResult.models.length > 0) { + setAvailableModels(modelsResult.models.map(m => ({ id: m.id, name: m.name || m.id, description: m.description, }))); } + if (settingsResult) { + setAutonomousMode(settingsResult.autonomous_mode || false); + } } catch (_e) { // Silently fail - models will just not be selectable } }); + // Toggle autonomous mode + const toggleAutonomousMode = async () => { + const newValue = !autonomousMode(); + setIsTogglingAutonomous(true); + try { + await AIAPI.updateSettings({ autonomous_mode: newValue }); + setAutonomousMode(newValue); + notificationStore.success(newValue ? 'Autonomous mode enabled' : 'Autonomous mode disabled'); + } catch (e) { + notificationStore.error('Failed to toggle autonomous mode'); + } finally { + setIsTogglingAutonomous(false); + } + }; + // Wrapper to sync messages to global store const setMessages = (updater: Message[] | ((prev: Message[]) => Message[])) => { setMessagesLocal((prev) => { @@ -448,9 +473,22 @@ export const AIChat: Component = (props) => { // Emit event for metadata refresh if AI set a resource URL if (data.name === 'set_resource_url' && data.success) { - logger.info('[AIChat] Emitting metadata-changed event for set_resource_url'); + let payload; + try { + // Optimistically parse the input to pass relevant data for immediate update + const parsedInput = JSON.parse(data.input); + // Handle both new 'resource_id' field and legacy 'id'/'guest_id' fields + const id = parsedInput.resource_id || parsedInput.guest_id || parsedInput.id; + if (id && typeof parsedInput.url !== 'undefined') { + payload = { guestId: id, url: parsedInput.url }; + } + } catch (e) { + logger.warn('[AIChat] Failed to parse set_resource_url input for optimistic update', e); + } + + logger.info('[AIChat] Emitting metadata-changed event for set_resource_url', { payload }); window.dispatchEvent(new CustomEvent('pulse:metadata-changed', { - detail: { source: 'ai', tool: data.name } + detail: { source: 'ai', tool: data.name, payload } })); } @@ -719,10 +757,10 @@ export const AIChat: Component = (props) => { await AIAPI.executeStream( { prompt: continuationPrompt, - target_type: targetType(), - target_id: targetId(), - context: contextData(), - history: historyForContinuation, + target_type: targetType() || undefined, + target_id: targetId() || undefined, + context: contextData() || undefined, + history: historyForContinuation.length > 0 ? historyForContinuation : undefined, }, (event: AIStreamEvent) => { logger.debug('[AIChat] Continuation event received', { type: event.type }); @@ -924,6 +962,25 @@ export const AIChat: Component = (props) => { + {/* Autonomous Mode toggle */} + + +
+ setForm('geminiApiKey', e.currentTarget.value)} + placeholder={settings()?.gemini_configured ? '••••••••••• (configured)' : 'AIza...'} + class={controlClass()} + disabled={saving()} + /> +
+

+ Get API key → +

+ +
+ + +
+
+
+ +

+ {providerTestResult()?.message} +

+
+
+ + + {/* Ollama */}
+