chore: remove debug console.log statements, stale comments, and dead code (#8142)
Co-authored-by: Douwe Osinga <douwe@squareup.com>
This commit is contained in:
@@ -413,7 +413,6 @@ export function AppInner() {
|
||||
const { addExtension } = useConfig();
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Sending reactReady signal to Electron');
|
||||
try {
|
||||
window.electron.reactReady();
|
||||
} catch (error) {
|
||||
@@ -458,7 +457,6 @@ export function AppInner() {
|
||||
}, [navigate]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Setting up keyboard shortcuts');
|
||||
const handleKeyDown = (event: KeyboardEvent) => {
|
||||
const isMac = window.electron.platform === 'darwin';
|
||||
if ((isMac ? event.metaKey : event.ctrlKey) && event.key === 'n') {
|
||||
@@ -537,9 +535,6 @@ export function AppInner() {
|
||||
const handleSetView = (_event: IpcRendererEvent, ...args: unknown[]) => {
|
||||
const newView = args[0] as View;
|
||||
const section = args[1] as string | undefined;
|
||||
console.log(
|
||||
`Received view change request to: ${newView}${section ? `, section: ${section}` : ''}`
|
||||
);
|
||||
|
||||
if (section && newView === 'settings') {
|
||||
navigate(`/settings?section=${section}`);
|
||||
@@ -554,7 +549,6 @@ export function AppInner() {
|
||||
|
||||
useEffect(() => {
|
||||
const handleNewChat = (_event: IpcRendererEvent, ..._args: unknown[]) => {
|
||||
console.log('Received new-chat event from keyboard shortcut');
|
||||
window.dispatchEvent(new CustomEvent(AppEvents.TRIGGER_NEW_CHAT));
|
||||
};
|
||||
|
||||
@@ -581,16 +575,9 @@ export function AppInner() {
|
||||
useEffect(() => {
|
||||
const handleSetInitialMessage = async (_event: IpcRendererEvent, ...args: unknown[]) => {
|
||||
const initialMessage = args[0] as string;
|
||||
console.log(
|
||||
'[App] Received set-initial-message event:',
|
||||
initialMessage,
|
||||
'isProcessing:',
|
||||
isProcessingRef.current
|
||||
);
|
||||
|
||||
if (initialMessage && !isProcessingRef.current) {
|
||||
isProcessingRef.current = true;
|
||||
console.log('[App] Processing initial message from launcher:', initialMessage);
|
||||
navigate('/pair', {
|
||||
state: {
|
||||
initialMessage: { msg: initialMessage, images: [] },
|
||||
@@ -599,8 +586,6 @@ export function AppInner() {
|
||||
setTimeout(() => {
|
||||
isProcessingRef.current = false;
|
||||
}, 1000);
|
||||
} else if (initialMessage) {
|
||||
console.log('[App] Ignoring duplicate initial message (already processing)');
|
||||
}
|
||||
};
|
||||
window.electron.on('set-initial-message', handleSetInitialMessage);
|
||||
|
||||
@@ -92,7 +92,7 @@ export default function AnnouncementModal() {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('No announcements found or failed to load:', error);
|
||||
console.warn('No announcements found or failed to load:', error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -412,7 +412,6 @@ export default function ChatInput({
|
||||
provider = configModelAndProvider.provider;
|
||||
}
|
||||
if (!model || !provider) {
|
||||
console.log('No model or provider found');
|
||||
setIsTokenLimitLoaded(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ export const ConfigProvider: React.FC<ConfigProviderProps> = ({ children }) => {
|
||||
}
|
||||
|
||||
if (result.error && !result.data) {
|
||||
console.log(result.error);
|
||||
console.error(result.error);
|
||||
return extensionsList;
|
||||
}
|
||||
|
||||
|
||||
@@ -161,13 +161,11 @@ export function ExtensionInstallModal({ addExtension, setView }: ExtensionInstal
|
||||
|
||||
const handleExtensionRequest = useCallback(async (link: string): Promise<void> => {
|
||||
if (processingLinkRef.current === link) {
|
||||
console.log(`Skipping duplicate extension request (already processing): ${link}`);
|
||||
return;
|
||||
}
|
||||
processingLinkRef.current = link;
|
||||
|
||||
try {
|
||||
console.log(`Processing extension request: ${link}`);
|
||||
|
||||
const command = extractCommand(link);
|
||||
const remoteUrl = extractRemoteUrl(link);
|
||||
@@ -175,7 +173,6 @@ export function ExtensionInstallModal({ addExtension, setView }: ExtensionInstal
|
||||
const extensionsList = await getExtensionsRef.current(true);
|
||||
|
||||
if (extensionsList?.find((ext) => ext.name === extName)) {
|
||||
console.log(`Extension Already Installed: ${extName}`);
|
||||
|
||||
toastService.success({
|
||||
title: `Extension '${extName}' Already Installed`,
|
||||
@@ -183,7 +180,6 @@ export function ExtensionInstallModal({ addExtension, setView }: ExtensionInstal
|
||||
});
|
||||
return;
|
||||
}
|
||||
console.log('Extension not found, continuing to show modal');
|
||||
|
||||
const extensionInfo: ExtensionInfo = {
|
||||
name: extName,
|
||||
@@ -235,14 +231,12 @@ export function ExtensionInstallModal({ addExtension, setView }: ExtensionInstal
|
||||
setModalState((prev) => ({ ...prev, isPending: true }));
|
||||
|
||||
try {
|
||||
console.log(`Confirming installation of extension from: ${pendingLink}`);
|
||||
|
||||
if (addExtension) {
|
||||
await addExtensionFromDeepLink(
|
||||
pendingLink,
|
||||
addExtension,
|
||||
(view: string, options?: ViewOptions) => {
|
||||
console.log('Extension installation completed, navigating to:', view, options);
|
||||
setView(view as View, options);
|
||||
}
|
||||
);
|
||||
@@ -260,7 +254,6 @@ export function ExtensionInstallModal({ addExtension, setView }: ExtensionInstal
|
||||
}, [pendingLink, dismissModal, addExtension, setView]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log('Setting up extension install modal handler');
|
||||
|
||||
const handleAddExtension = async (_event: IpcRendererEvent, ...args: unknown[]) => {
|
||||
const link = args[0] as string;
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { AlertTriangle, StopCircle, PauseCircle, RotateCcw, Zap, AlertCircle } from 'lucide-react';
|
||||
import { Button } from './ui/button';
|
||||
import { InterruptionMatch } from '../utils/interruptionDetector';
|
||||
|
||||
interface InterruptionHandlerProps {
|
||||
match: InterruptionMatch | null;
|
||||
onConfirmInterruption: () => void;
|
||||
onCancelInterruption: () => void;
|
||||
onRedirect?: (newMessage: string) => void;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export const InterruptionHandler: React.FC<InterruptionHandlerProps> = ({
|
||||
match,
|
||||
onConfirmInterruption,
|
||||
onCancelInterruption,
|
||||
onRedirect,
|
||||
className = '',
|
||||
}) => {
|
||||
const [redirectMessage, setRedirectMessage] = useState('');
|
||||
const [showRedirectInput, setShowRedirectInput] = useState(false);
|
||||
const [isVisible, setIsVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (match) {
|
||||
setIsVisible(true);
|
||||
if (match.keyword.action === 'redirect') {
|
||||
setShowRedirectInput(true);
|
||||
} else {
|
||||
setShowRedirectInput(false);
|
||||
setRedirectMessage('');
|
||||
}
|
||||
} else {
|
||||
setIsVisible(false);
|
||||
}
|
||||
}, [match]);
|
||||
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const getIcon = () => {
|
||||
switch (match.keyword.action) {
|
||||
case 'stop':
|
||||
return <StopCircle className="w-6 h-6 text-red-500" />;
|
||||
case 'pause':
|
||||
return <PauseCircle className="w-6 h-6 text-amber-500" />;
|
||||
case 'redirect':
|
||||
return <RotateCcw className="w-6 h-6 text-blue-500" />;
|
||||
default:
|
||||
return <AlertTriangle className="w-6 h-6 text-orange-500" />;
|
||||
}
|
||||
};
|
||||
|
||||
const getActionColor = () => {
|
||||
switch (match.keyword.action) {
|
||||
case 'stop':
|
||||
return {
|
||||
bg: 'bg-red-50 dark:bg-red-950/20',
|
||||
border: 'border-red-200 dark:border-red-800/50',
|
||||
text: 'text-red-800 dark:text-red-200',
|
||||
accent: 'text-red-600 dark:text-red-400',
|
||||
};
|
||||
case 'pause':
|
||||
return {
|
||||
bg: 'bg-amber-50 dark:bg-amber-950/20',
|
||||
border: 'border-amber-200 dark:border-amber-800/50',
|
||||
text: 'text-amber-800 dark:text-amber-200',
|
||||
accent: 'text-amber-600 dark:text-amber-400',
|
||||
};
|
||||
case 'redirect':
|
||||
return {
|
||||
bg: 'bg-blue-50 dark:bg-blue-950/20',
|
||||
border: 'border-blue-200 dark:border-blue-800/50',
|
||||
text: 'text-blue-800 dark:text-blue-200',
|
||||
accent: 'text-blue-600 dark:text-blue-400',
|
||||
};
|
||||
default:
|
||||
return {
|
||||
bg: 'bg-orange-50 dark:bg-orange-950/20',
|
||||
border: 'border-orange-200 dark:border-orange-800/50',
|
||||
text: 'text-orange-800 dark:text-orange-200',
|
||||
accent: 'text-orange-600 dark:text-orange-400',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const colors = getActionColor();
|
||||
|
||||
const handleConfirm = () => {
|
||||
if (showRedirectInput && onRedirect && redirectMessage.trim()) {
|
||||
onRedirect(redirectMessage.trim());
|
||||
} else {
|
||||
onConfirmInterruption();
|
||||
}
|
||||
};
|
||||
|
||||
const getActionTitle = () => {
|
||||
switch (match.keyword.action) {
|
||||
case 'stop':
|
||||
return 'Stop Processing';
|
||||
case 'pause':
|
||||
return 'Pause Processing';
|
||||
case 'redirect':
|
||||
return 'Redirect Processing';
|
||||
default:
|
||||
return 'Interrupt Processing';
|
||||
}
|
||||
};
|
||||
|
||||
const getActionDescription = () => {
|
||||
switch (match.keyword.action) {
|
||||
case 'stop':
|
||||
return 'This will immediately stop the current processing and clear any queued messages.';
|
||||
case 'pause':
|
||||
return 'This will pause the current processing. Queued messages will be preserved.';
|
||||
case 'redirect':
|
||||
return 'This will stop current processing and redirect to a new task.';
|
||||
default:
|
||||
return 'This will interrupt the current processing.';
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`fixed inset-0 bg-black/50 backdrop-blur-sm flex items-center justify-center z-50 p-4 ${className}`}
|
||||
>
|
||||
<div
|
||||
className={`w-full max-w-md mx-auto transition-all duration-300 ease-out ${
|
||||
isVisible ? 'scale-100 opacity-100' : 'scale-95 opacity-0'
|
||||
}`}
|
||||
>
|
||||
{/* Main card */}
|
||||
<div
|
||||
className={`rounded-xl border shadow-2xl backdrop-blur-xl ${colors.bg} ${colors.border}`}
|
||||
>
|
||||
{/* Header */}
|
||||
<div className="p-6 border-b border-current/10">
|
||||
<div className="flex items-start gap-4">
|
||||
<div className="flex-shrink-0 p-2 rounded-full bg-white/50 dark:bg-black/20">
|
||||
{getIcon()}
|
||||
</div>
|
||||
<div className="flex-1">
|
||||
<h3 className={`text-lg font-semibold ${colors.text}`}>{getActionTitle()}</h3>
|
||||
<p className={`text-sm mt-1 ${colors.accent}`}>Detected: "{match.matchedText}"</p>
|
||||
</div>
|
||||
<div
|
||||
className={`text-xs px-2 py-1 rounded-full bg-white/30 dark:bg-black/20 ${colors.text}`}
|
||||
>
|
||||
{Math.round(match.confidence * 100)}% confident
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6">
|
||||
<div className="flex items-start gap-3 mb-4">
|
||||
<AlertCircle className={`w-4 h-4 mt-0.5 flex-shrink-0 ${colors.accent}`} />
|
||||
<p className={`text-sm leading-relaxed ${colors.text}`}>{getActionDescription()}</p>
|
||||
</div>
|
||||
|
||||
{/* Redirect input */}
|
||||
{showRedirectInput && (
|
||||
<div className="mb-4 space-y-2">
|
||||
<label className={`text-sm font-medium ${colors.text}`}>
|
||||
New task or instruction:
|
||||
</label>
|
||||
<textarea
|
||||
value={redirectMessage}
|
||||
onChange={(e) => setRedirectMessage(e.target.value)}
|
||||
placeholder="Enter your new instruction..."
|
||||
className={`w-full px-3 py-2 border rounded-lg resize-none focus:outline-none focus:ring-2 focus:ring-current/20 bg-white/50 dark:bg-black/20 ${colors.border} ${colors.text}`}
|
||||
rows={3}
|
||||
autoFocus
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Confidence indicator */}
|
||||
<div className="mb-4">
|
||||
<div className="flex items-center justify-between text-xs mb-1">
|
||||
<span className={colors.accent}>Detection Confidence</span>
|
||||
<span className={colors.text}>{Math.round(match.confidence * 100)}%</span>
|
||||
</div>
|
||||
<div className="w-full bg-white/30 dark:bg-black/20 rounded-full h-2">
|
||||
<div
|
||||
className={`h-2 rounded-full transition-all duration-500 ${
|
||||
match.confidence > 0.8
|
||||
? 'bg-green-500'
|
||||
: match.confidence > 0.6
|
||||
? 'bg-amber-500'
|
||||
: 'bg-red-500'
|
||||
}`}
|
||||
style={{ width: `${match.confidence * 100}%` }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Actions */}
|
||||
<div className="p-6 pt-0 flex gap-3">
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={onCancelInterruption}
|
||||
className={`flex-1 hover:bg-white/20 dark:hover:bg-black/20 ${colors.text}`}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleConfirm}
|
||||
disabled={showRedirectInput && !redirectMessage.trim()}
|
||||
className={`flex-1 bg-white/80 hover:bg-white dark:bg-white/10 dark:hover:bg-white/20 ${colors.text} font-medium shadow-md hover:shadow-lg transition-all duration-200`}
|
||||
>
|
||||
<Zap className="w-4 h-4 mr-2" />
|
||||
{showRedirectInput
|
||||
? 'Redirect'
|
||||
: match.keyword.action === 'stop'
|
||||
? 'Stop'
|
||||
: 'Confirm'}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Backdrop hint */}
|
||||
<div className="text-center mt-4">
|
||||
<p className="text-xs text-white/60">
|
||||
Click outside or press Cancel to continue current processing
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default InterruptionHandler;
|
||||
@@ -583,12 +583,7 @@ export default function McpAppRenderer({
|
||||
);
|
||||
|
||||
const handleLoggingMessage = useCallback(
|
||||
({ level, logger, data }: { level?: string; logger?: string; data?: unknown }) => {
|
||||
console.log(
|
||||
`[MCP App Notification]${logger ? ` [${logger}]` : ''} ${level || 'info'}:`,
|
||||
data
|
||||
);
|
||||
},
|
||||
(_notification: { level?: string; logger?: string; data?: unknown }) => {},
|
||||
[]
|
||||
);
|
||||
|
||||
|
||||
@@ -126,7 +126,6 @@ export const ModelAndProviderProvider: React.FC<ModelAndProviderProviderProps> =
|
||||
throw new Error('Failed to read GOOSE_MODEL or GOOSE_PROVIDER from config');
|
||||
}
|
||||
if (!model || !provider) {
|
||||
console.log('[getCurrentModelAndProvider] Checking app environment as fallback');
|
||||
return getFallbackModelAndProvider();
|
||||
}
|
||||
return { model: model, provider: provider };
|
||||
|
||||
@@ -1,113 +0,0 @@
|
||||
import React, { useEffect, useRef } from 'react';
|
||||
|
||||
interface WaveformVisualizerProps {
|
||||
audioContext: AudioContext | null;
|
||||
analyser: AnalyserNode | null;
|
||||
isRecording: boolean;
|
||||
}
|
||||
|
||||
export const WaveformVisualizer: React.FC<WaveformVisualizerProps> = ({
|
||||
analyser,
|
||||
isRecording,
|
||||
}) => {
|
||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||
const animationRef = useRef<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!canvasRef.current || !analyser || !isRecording) return;
|
||||
|
||||
const canvas = canvasRef.current;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) return;
|
||||
|
||||
// Set canvas size
|
||||
const dpr = window.devicePixelRatio || 1;
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
canvas.width = rect.width * dpr;
|
||||
canvas.height = rect.height * dpr;
|
||||
ctx.scale(dpr, dpr);
|
||||
|
||||
// Configure analyser
|
||||
analyser.fftSize = 256;
|
||||
const bufferLength = analyser.frequencyBinCount;
|
||||
const dataArray = new Uint8Array(bufferLength);
|
||||
|
||||
// Visual settings
|
||||
const barWidth = 3;
|
||||
const barSpacing = 2;
|
||||
const barCount = Math.floor(rect.width / (barWidth + barSpacing));
|
||||
const barMaxHeight = rect.height * 0.8;
|
||||
const barMinHeight = 2;
|
||||
|
||||
// Smoothing for bars
|
||||
const smoothedHeights = new Array(barCount).fill(0);
|
||||
const targetHeights = new Array(barCount).fill(0);
|
||||
|
||||
const draw = () => {
|
||||
if (!isRecording) return;
|
||||
|
||||
animationRef.current = requestAnimationFrame(draw);
|
||||
|
||||
// Get frequency data
|
||||
analyser.getByteFrequencyData(dataArray);
|
||||
|
||||
// Clear canvas
|
||||
ctx.clearRect(0, 0, rect.width, rect.height);
|
||||
|
||||
// Calculate target heights based on frequency data
|
||||
for (let i = 0; i < barCount; i++) {
|
||||
const dataIndex = Math.floor((i / barCount) * bufferLength * 0.5); // Use lower frequencies
|
||||
const value = dataArray[dataIndex] / 255;
|
||||
|
||||
// Apply some randomness and minimum height for visual interest
|
||||
const randomFactor = 0.85 + Math.random() * 0.3;
|
||||
targetHeights[i] = Math.max(barMinHeight, value * barMaxHeight * randomFactor);
|
||||
}
|
||||
|
||||
// Smooth the bar heights
|
||||
for (let i = 0; i < barCount; i++) {
|
||||
const diff = targetHeights[i] - smoothedHeights[i];
|
||||
smoothedHeights[i] += diff * 0.3; // Smoothing factor
|
||||
}
|
||||
|
||||
// Draw bars
|
||||
for (let i = 0; i < barCount; i++) {
|
||||
const x = i * (barWidth + barSpacing) + barSpacing;
|
||||
const barHeight = smoothedHeights[i];
|
||||
const y = (rect.height - barHeight) / 2;
|
||||
|
||||
// Create gradient for each bar
|
||||
const gradient = ctx.createLinearGradient(0, y, 0, y + barHeight);
|
||||
|
||||
// Dynamic color based on height
|
||||
const intensity = barHeight / barMaxHeight;
|
||||
const hue = 200 + intensity * 20; // Blue to cyan
|
||||
const saturation = 50 + intensity * 50;
|
||||
const lightness = 50 + intensity * 20;
|
||||
|
||||
gradient.addColorStop(0, `hsla(${hue}, ${saturation}%, ${lightness}%, 0.3)`);
|
||||
gradient.addColorStop(0.5, `hsla(${hue}, ${saturation}%, ${lightness}%, 0.8)`);
|
||||
gradient.addColorStop(1, `hsla(${hue}, ${saturation}%, ${lightness}%, 0.3)`);
|
||||
|
||||
ctx.fillStyle = gradient;
|
||||
ctx.fillRect(x, y, barWidth, barHeight);
|
||||
}
|
||||
};
|
||||
|
||||
draw();
|
||||
|
||||
return () => {
|
||||
if (animationRef.current) {
|
||||
cancelAnimationFrame(animationRef.current);
|
||||
}
|
||||
};
|
||||
}, [analyser, isRecording]);
|
||||
|
||||
return (
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="absolute inset-0 w-full h-full pointer-events-none"
|
||||
style={{ opacity: 0.9 }}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,98 +0,0 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { ChevronDown } from 'lucide-react';
|
||||
import { Button } from '../ui/button';
|
||||
|
||||
interface RecipeExpandableInfoProps {
|
||||
infoLabel: string;
|
||||
infoValue: string;
|
||||
required?: boolean;
|
||||
onClickEdit: () => void;
|
||||
}
|
||||
|
||||
export default function RecipeExpandableInfo({
|
||||
infoValue,
|
||||
infoLabel,
|
||||
required = false,
|
||||
onClickEdit,
|
||||
}: RecipeExpandableInfoProps) {
|
||||
const [isValueExpanded, setValueExpanded] = useState(false);
|
||||
const [isClamped, setIsClamped] = useState(false);
|
||||
// eslint-disable-next-line no-undef
|
||||
const contentRef = useRef<HTMLParagraphElement>(null);
|
||||
const measureRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const el = measureRef.current;
|
||||
if (el) {
|
||||
const lineHeight = parseFloat(window.getComputedStyle(el).lineHeight || '0');
|
||||
const maxHeight = lineHeight * 3;
|
||||
const actualHeight = el.scrollHeight;
|
||||
setIsClamped(actualHeight > maxHeight);
|
||||
}
|
||||
}, [infoValue]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex justify-between items-center mb-2">
|
||||
<label className="block text-md text-text-primary font-bold">
|
||||
{infoLabel} {required && <span className="text-red-500">*</span>}
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="relative rounded-lg bg-background-primary text-text-primary">
|
||||
{infoValue && (
|
||||
<>
|
||||
<div
|
||||
ref={measureRef}
|
||||
className="invisible absolute whitespace-pre-wrap w-full pointer-events-none"
|
||||
style={{ position: 'absolute', top: '-9999px' }}
|
||||
>
|
||||
{infoValue}
|
||||
</div>
|
||||
|
||||
<p
|
||||
ref={contentRef}
|
||||
className={`whitespace-pre-wrap transition-all duration-300 ${
|
||||
!isValueExpanded ? 'line-clamp-3' : ''
|
||||
}`}
|
||||
>
|
||||
{infoValue}
|
||||
</p>
|
||||
</>
|
||||
)}
|
||||
<div className="mt-4 flex items-center justify-between">
|
||||
<Button
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
setValueExpanded(true);
|
||||
onClickEdit();
|
||||
}}
|
||||
className="w-36 px-3 py-3 text-sm text-text-inverse rounded-xl hover:bg-background-inverse transition-colors"
|
||||
>
|
||||
{infoValue ? 'Edit' : 'Add'} {infoLabel.toLowerCase()}
|
||||
</Button>
|
||||
|
||||
{infoValue && isClamped && (
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
shape="round"
|
||||
onClick={() => setValueExpanded(!isValueExpanded)}
|
||||
aria-label={isValueExpanded ? 'Collapse content' : 'Expand content'}
|
||||
title={isValueExpanded ? 'Collapse' : 'Expand'}
|
||||
className="bg-background-secondary hover:bg-background-primary text-text-secondary hover:text-text-primary transition-colors"
|
||||
>
|
||||
<ChevronDown
|
||||
className={`w-6 h-6 transition-transform duration-300 ${
|
||||
isValueExpanded ? 'rotate-180' : ''
|
||||
}`}
|
||||
strokeWidth={2.5}
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import { Card } from '../ui/card';
|
||||
import { Button } from '../ui/button';
|
||||
|
||||
interface RecipeInfoModalProps {
|
||||
infoLabel?: string;
|
||||
originalValue?: string;
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
onSaveValue?: (val: string) => void;
|
||||
}
|
||||
export default function RecipeInfoModal({
|
||||
infoLabel = '',
|
||||
isOpen,
|
||||
onClose,
|
||||
originalValue = '',
|
||||
onSaveValue = () => {},
|
||||
}: RecipeInfoModalProps) {
|
||||
const [value, setValue] = useState(originalValue);
|
||||
const textareaRef = useRef<HTMLTextAreaElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (isOpen) {
|
||||
setValue(originalValue);
|
||||
textareaRef.current?.focus();
|
||||
}
|
||||
}, [isOpen, originalValue]);
|
||||
|
||||
const onSave = (event: React.FormEvent) => {
|
||||
onSaveValue(value);
|
||||
event.preventDefault();
|
||||
onClose();
|
||||
};
|
||||
if (!isOpen) return null;
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/20 dark:bg-white/20 backdrop-blur-sm transition-colors animate-[fadein_200ms_ease-in_forwards] z-[1000]">
|
||||
<Card className="fixed top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 flex flex-col min-w-[80%] min-h-[80%] bg-background-primary rounded-xl overflow-hidden shadow-lg px-8 pt-[24px] pb-0">
|
||||
<div className="flex mb-6">
|
||||
<h2 className="text-xl font-semibold text-text-primary">Edit {infoLabel}</h2>
|
||||
</div>
|
||||
<div className="flex flex-col flex-grow overflow-y-auto space-y-8">
|
||||
<textarea
|
||||
ref={textareaRef}
|
||||
className="w-full flex-grow resize-none min-h-[300px] max-h-[calc(100vh-300px)] border border-border-primary rounded-lg p-3 text-text-primary bg-background-primary focus:outline-none focus:ring-1 focus:ring-border-secondary focus:border-border-secondary"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
placeholder={`Enter ${infoLabel.toLowerCase()}...`}
|
||||
/>
|
||||
</div>
|
||||
<Button
|
||||
onClick={onSave}
|
||||
className="w-full h-[60px] rounded-none border-b border-border-primary bg-transparent hover:bg-background-secondary text-text-primary font-medium text-md"
|
||||
>
|
||||
Save Changes
|
||||
</Button>
|
||||
<Button
|
||||
onClick={onClose}
|
||||
variant="ghost"
|
||||
className="w-full h-[60px] rounded-none hover:bg-background-secondary text-text-secondary hover:text-text-primary text-md font-regular"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Card } from '../ui/card';
|
||||
import { Button } from '../ui/button';
|
||||
import { Input } from '../ui/input';
|
||||
import { Recipe, generateDeepLink } from '../../recipe';
|
||||
import Copy from '../icons/Copy';
|
||||
import { Check } from 'lucide-react';
|
||||
|
||||
interface ScheduleFromRecipeModalProps {
|
||||
isOpen: boolean;
|
||||
onClose: () => void;
|
||||
recipe: Recipe;
|
||||
onCreateSchedule: (deepLink: string) => void;
|
||||
}
|
||||
|
||||
export const ScheduleFromRecipeModal: React.FC<ScheduleFromRecipeModalProps> = ({
|
||||
isOpen,
|
||||
onClose,
|
||||
recipe,
|
||||
onCreateSchedule,
|
||||
}) => {
|
||||
const [copied, setCopied] = useState(false);
|
||||
const [deepLink, setDeepLink] = useState('');
|
||||
|
||||
useEffect(() => {
|
||||
let isCancelled = false;
|
||||
|
||||
const generateLink = async () => {
|
||||
if (isOpen && recipe) {
|
||||
try {
|
||||
const link = await generateDeepLink(recipe);
|
||||
if (!isCancelled) {
|
||||
setDeepLink(link);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to generate deeplink:', error);
|
||||
if (!isCancelled) {
|
||||
setDeepLink('Error generating deeplink');
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
generateLink();
|
||||
|
||||
return () => {
|
||||
isCancelled = true;
|
||||
};
|
||||
}, [isOpen, recipe]);
|
||||
|
||||
const handleCopy = () => {
|
||||
navigator.clipboard
|
||||
.writeText(deepLink)
|
||||
.then(() => {
|
||||
setCopied(true);
|
||||
setTimeout(() => setCopied(false), 2000);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error('Failed to copy the text:', err);
|
||||
});
|
||||
};
|
||||
|
||||
const handleCreateSchedule = () => {
|
||||
onCreateSchedule(deepLink);
|
||||
onClose();
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
setCopied(false);
|
||||
onClose();
|
||||
};
|
||||
|
||||
if (!isOpen) return null;
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 bg-black/50 z-40 flex items-center justify-center p-4">
|
||||
<Card className="w-full max-w-md bg-background-primary shadow-xl rounded-lg z-50 flex flex-col">
|
||||
<div className="px-6 pt-6 pb-4">
|
||||
<h2 className="text-xl font-semibold text-gray-900 dark:text-white">
|
||||
Create Schedule from Recipe
|
||||
</h2>
|
||||
<p className="text-sm text-gray-600 dark:text-gray-400 mt-2">
|
||||
Create a scheduled task using this recipe configuration.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="px-6 py-4 space-y-4">
|
||||
<div>
|
||||
<h3 className="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Recipe Details:
|
||||
</h3>
|
||||
<div className="bg-gray-50 dark:bg-gray-800 p-3 rounded-md">
|
||||
<p className="text-sm font-medium text-gray-900 dark:text-white">{recipe.title}</p>
|
||||
<p className="text-xs text-gray-600 dark:text-gray-400 mt-1">{recipe.description}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
Recipe Deep Link:
|
||||
</label>
|
||||
<div className="flex items-center">
|
||||
<Input type="text" value={deepLink} readOnly className="flex-1 text-xs font-mono" />
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleCopy}
|
||||
className="ml-2 px-3 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600 flex items-center"
|
||||
>
|
||||
{copied ? <Check className="w-4 h-4" /> : <Copy className="w-4 h-4" />}
|
||||
</Button>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||
This link contains your recipe configuration and can be used to create a schedule.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="px-6 pb-6 flex gap-2">
|
||||
<Button
|
||||
type="button"
|
||||
variant="outline"
|
||||
onClick={handleClose}
|
||||
className="flex-1 rounded-xl hover:bg-background-secondary text-text-secondary"
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
onClick={handleCreateSchedule}
|
||||
className="flex-1 text-sm text-text-inverse rounded-xl hover:bg-background-inverse transition-colors"
|
||||
>
|
||||
Create Schedule
|
||||
</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -42,7 +42,6 @@ export default function UpdateSection() {
|
||||
// Check if there's already an update state from the auto-check
|
||||
window.electron.getUpdateState().then((state) => {
|
||||
if (state) {
|
||||
console.log('Found existing update state:', state);
|
||||
setUpdateInfo((prev) => ({
|
||||
...prev,
|
||||
isUpdateAvailable: state.updateAvailable,
|
||||
@@ -58,7 +57,6 @@ export default function UpdateSection() {
|
||||
|
||||
// Listen for updater events
|
||||
window.electron.onUpdaterEvent((event) => {
|
||||
console.log('Updater event:', event);
|
||||
|
||||
switch (event.event) {
|
||||
case 'checking-for-update':
|
||||
|
||||
@@ -174,14 +174,10 @@ export async function addExtensionFromDeepLink(
|
||||
config.type === 'streamable_http' && config.headers && Object.keys(config.headers).length > 0;
|
||||
|
||||
if (hasEnvVars || hasHeaders) {
|
||||
console.log(
|
||||
'Environment variables or headers required, redirecting to extensions with env variables modal showing'
|
||||
);
|
||||
setView('extensions', { deepLinkConfig: config, showEnvVars: true });
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('No env vars required, activating extension directly');
|
||||
// Note: deeplink activation doesn't have access to sessionId
|
||||
// The extension will be added to config but not activated in the current session
|
||||
// It will be activated when the next session starts
|
||||
|
||||
@@ -311,8 +311,6 @@ export default function ExtensionModal({
|
||||
} catch (error) {
|
||||
console.error('Error during submission:', error);
|
||||
}
|
||||
} else {
|
||||
console.log('Form validation failed');
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ export default function ExtensionItem({
|
||||
// Success case is handled by the useEffect below when extension.enabled changes
|
||||
} catch {
|
||||
// If there was an error, revert the visual state
|
||||
console.log('Toggle failed, reverting visual state');
|
||||
setVisuallyEnabled(!newState);
|
||||
} finally {
|
||||
setIsToggling(false);
|
||||
|
||||
@@ -110,7 +110,6 @@ export default function PermissionModal({ extensionName, onClose }: PermissionMo
|
||||
if (response.error) {
|
||||
console.error('Failed to save permissions:', response.error);
|
||||
} else {
|
||||
console.log('Permissions updated successfully');
|
||||
onClose();
|
||||
}
|
||||
} catch (err) {
|
||||
|
||||
@@ -21,7 +21,6 @@ interface ProviderNameAndStatusProps {
|
||||
}
|
||||
|
||||
const ProviderNameAndStatus = memo(({ name, isConfigured }: ProviderNameAndStatusProps) => {
|
||||
// Remove the console.log completely
|
||||
return (
|
||||
<div className="flex items-center justify-between w-full">
|
||||
<CardTitle name={name} />
|
||||
|
||||
@@ -8,7 +8,6 @@ import { trackSettingToggled } from '../../../utils/analytics';
|
||||
|
||||
export default function SessionSharingSection() {
|
||||
const envBaseUrlShare = window.appConfig.get('GOOSE_BASE_URL_SHARE');
|
||||
console.log('envBaseUrlShare', envBaseUrlShare);
|
||||
|
||||
// If env is set, force sharing enabled and set the baseUrl accordingly.
|
||||
const [sessionSharingConfig, setSessionSharingConfig] = useState({
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
* CustomRadio - A reusable radio button component with dark mode support
|
||||
* @param {Object} props - Component props
|
||||
* @param {string} props.id - Unique identifier for the radio input
|
||||
* @param {string} props.name - Name attribute for the radio input
|
||||
* @param {string} props.value - Value of the radio input
|
||||
* @param {boolean} props.checked - Whether the radio is checked
|
||||
* @param {function} props.onChange - Function to call when radio selection changes
|
||||
* @param {boolean} [props.disabled] - Whether the radio is disabled
|
||||
* @param {React.ReactNode} [props.label] - Primary label content
|
||||
* @param {React.ReactNode} [props.secondaryLabel] - Secondary/subtitle label content
|
||||
* @param {React.ReactNode} [props.rightContent] - Optional content to display on the right side
|
||||
* @param {string} [props.className] - Additional CSS classes for the main container
|
||||
* @returns {JSX.Element}
|
||||
*/
|
||||
const CustomRadio = ({
|
||||
id,
|
||||
name,
|
||||
value,
|
||||
checked,
|
||||
onChange,
|
||||
disabled = false,
|
||||
label = null,
|
||||
secondaryLabel = null,
|
||||
rightContent = null,
|
||||
className = '',
|
||||
}: {
|
||||
id: string;
|
||||
name: string;
|
||||
value: string;
|
||||
checked: boolean;
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
disabled?: boolean;
|
||||
label?: React.ReactNode;
|
||||
secondaryLabel?: React.ReactNode;
|
||||
rightContent?: React.ReactNode;
|
||||
className?: string;
|
||||
}) => {
|
||||
return (
|
||||
<label
|
||||
htmlFor={id}
|
||||
className={`flex justify-between items-center py-2 cursor-pointer ${disabled ? 'opacity-50 cursor-not-allowed' : ''} ${className}`}
|
||||
>
|
||||
<div className="relative flex items-center">
|
||||
<input
|
||||
type="radio"
|
||||
id={id}
|
||||
name={name}
|
||||
value={value}
|
||||
checked={checked}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
className="peer sr-only"
|
||||
/>
|
||||
<div
|
||||
className="h-4 w-4 rounded-full border border-gray-400 dark:border-gray-500 mr-4
|
||||
peer-checked:border-[6px] peer-checked:border-black dark:peer-checked:border-white
|
||||
peer-checked:bg-white dark:peer-checked:bg-black
|
||||
transition-all duration-200 ease-in-out"
|
||||
></div>
|
||||
|
||||
{(label || secondaryLabel) && (
|
||||
<div>
|
||||
{label && <p className="text-sm text-gray-900 dark:text-gray-100">{label}</p>}
|
||||
{secondaryLabel && (
|
||||
<p className="text-xs text-gray-500 dark:text-gray-400">{secondaryLabel}</p>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{rightContent && (
|
||||
<div className="flex items-center text-sm text-gray-500 dark:text-gray-400">
|
||||
{rightContent}
|
||||
</div>
|
||||
)}
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
export default CustomRadio;
|
||||
@@ -69,13 +69,6 @@ export const useCostTracking = ({
|
||||
}));
|
||||
}
|
||||
|
||||
console.log(
|
||||
'Model changed from',
|
||||
`${prevProviderRef.current}/${prevModelRef.current}`,
|
||||
'to',
|
||||
`${currentProvider}/${currentModel}`,
|
||||
'- saved costs and restored session token counters'
|
||||
);
|
||||
}
|
||||
|
||||
prevModelRef.current = currentModel || undefined;
|
||||
|
||||
@@ -1,324 +0,0 @@
|
||||
import { useEffect, useMemo, useState, useRef } from 'react';
|
||||
import { Recipe, scanRecipe } from '../recipe';
|
||||
import { createUserMessage } from '../types/message';
|
||||
import { Message } from '../api';
|
||||
|
||||
import { substituteParameters } from '../utils/parameterSubstitution';
|
||||
import { updateSessionUserRecipeValues } from '../api';
|
||||
import { useChatContext } from '../contexts/ChatContext';
|
||||
import { ChatType } from '../types/chat';
|
||||
import { toastError, toastSuccess } from '../toasts';
|
||||
|
||||
export const useRecipeManager = (chat: ChatType, recipe?: Recipe | null) => {
|
||||
const [isParameterModalOpen, setIsParameterModalOpen] = useState(false);
|
||||
const [isRecipeWarningModalOpen, setIsRecipeWarningModalOpen] = useState(false);
|
||||
const [recipeAccepted, setRecipeAccepted] = useState(false);
|
||||
const [isCreateRecipeModalOpen, setIsCreateRecipeModalOpen] = useState(false);
|
||||
const [hasSecurityWarnings, setHasSecurityWarnings] = useState(false);
|
||||
const [readyForAutoUserPrompt, setReadyForAutoUserPrompt] = useState(false);
|
||||
const [recipeError, setRecipeError] = useState<string | null>(null);
|
||||
const recipeParameterValues = chat.recipeParameterValues;
|
||||
|
||||
const chatContext = useChatContext();
|
||||
const messages = chat.messages;
|
||||
|
||||
// Get recipe parameters from deeplink if available
|
||||
const paramsFromConfig =
|
||||
(window.appConfig?.get('recipeParameters') as Record<string, string> | null | undefined) ??
|
||||
null;
|
||||
const recipeParametersFromConfig = useRef<Record<string, string> | null>(paramsFromConfig);
|
||||
|
||||
const messagesRef = useRef(messages);
|
||||
const isCreatingRecipeRef = useRef(false);
|
||||
const hasCheckedRecipeRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
messagesRef.current = messages;
|
||||
}, [messages]);
|
||||
|
||||
const finalRecipe = chat.recipe;
|
||||
const resolvedRecipe = chat.resolvedRecipe;
|
||||
|
||||
// Initialize parameters from deeplink when recipe is loaded (from backend/deeplink)
|
||||
useEffect(() => {
|
||||
if (!chatContext || !finalRecipe) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only initialize if we have params from config and haven't set them yet
|
||||
const hasNoParameters =
|
||||
!chat.recipeParameterValues ||
|
||||
(typeof chat.recipeParameterValues === 'object' &&
|
||||
Object.keys(chat.recipeParameterValues).length === 0);
|
||||
|
||||
if (recipeParametersFromConfig.current && hasNoParameters) {
|
||||
chatContext.setChat({
|
||||
...chatContext.chat,
|
||||
recipeParameterValues: recipeParametersFromConfig.current,
|
||||
});
|
||||
}
|
||||
}, [chatContext, finalRecipe, chat]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!chatContext) return;
|
||||
|
||||
// If we have a recipe from navigation state, always set it and reset acceptance state
|
||||
// This ensures that when loading a new recipe, we start fresh
|
||||
if (recipe) {
|
||||
// Check if this is actually a different recipe (by comparing title and content)
|
||||
const currentRecipe = chatContext.chat.recipe;
|
||||
const isNewRecipe =
|
||||
!currentRecipe ||
|
||||
currentRecipe.title !== recipe.title ||
|
||||
currentRecipe.instructions !== recipe.instructions ||
|
||||
currentRecipe.prompt !== recipe.prompt ||
|
||||
JSON.stringify(currentRecipe.activities) !== JSON.stringify(recipe.activities);
|
||||
|
||||
if (isNewRecipe) {
|
||||
console.log('Setting new recipe config:', recipe.title);
|
||||
// Reset recipe acceptance state when loading a new recipe
|
||||
setRecipeAccepted(false);
|
||||
setIsParameterModalOpen(false);
|
||||
setIsRecipeWarningModalOpen(false);
|
||||
hasCheckedRecipeRef.current = false; // Reset check flag for new recipe
|
||||
|
||||
// Initialize with parameters from deeplink if available
|
||||
const initialParameterValues = recipeParametersFromConfig.current || null;
|
||||
|
||||
chatContext.setChat({
|
||||
...chatContext.chat,
|
||||
recipe: recipe,
|
||||
recipeParameterValues: initialParameterValues,
|
||||
messages: [],
|
||||
});
|
||||
}
|
||||
return;
|
||||
}
|
||||
}, [chatContext, recipe]);
|
||||
|
||||
useEffect(() => {
|
||||
const checkRecipeAcceptance = async () => {
|
||||
// Only check once per recipe load
|
||||
if (hasCheckedRecipeRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (finalRecipe) {
|
||||
hasCheckedRecipeRef.current = true;
|
||||
|
||||
try {
|
||||
const hasAccepted = await window.electron.hasAcceptedRecipeBefore(finalRecipe);
|
||||
|
||||
if (!hasAccepted) {
|
||||
const securityScanResult = await scanRecipe(finalRecipe);
|
||||
setHasSecurityWarnings(securityScanResult.has_security_warnings);
|
||||
|
||||
setIsRecipeWarningModalOpen(true);
|
||||
} else {
|
||||
setRecipeAccepted(true);
|
||||
}
|
||||
} catch {
|
||||
setHasSecurityWarnings(false);
|
||||
setIsRecipeWarningModalOpen(true);
|
||||
}
|
||||
} else {
|
||||
setRecipeAccepted(false);
|
||||
setIsRecipeWarningModalOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
checkRecipeAcceptance();
|
||||
}, [finalRecipe, recipe, chat.messages.length]);
|
||||
|
||||
const filteredParameters = useMemo(() => {
|
||||
return finalRecipe?.parameters ?? [];
|
||||
}, [finalRecipe]);
|
||||
|
||||
// Check if template variables are actually used in the recipe content
|
||||
const requiresParameters = useMemo(() => {
|
||||
return filteredParameters.length > 0;
|
||||
}, [filteredParameters]);
|
||||
|
||||
// Check if all required parameters have been filled in
|
||||
const hasAllRequiredParameters = useMemo(() => {
|
||||
return !requiresParameters || resolvedRecipe != null;
|
||||
}, [requiresParameters, resolvedRecipe]);
|
||||
|
||||
const hasMessages = messages.length > 0;
|
||||
useEffect(() => {
|
||||
// Only show parameter modal if:
|
||||
// 1. Recipe requires parameters
|
||||
// 2. Recipe has been accepted
|
||||
// 3. Not all required parameters have been filled in yet
|
||||
// 4. Parameter modal is not already open (prevent multiple opens)
|
||||
// 5. No messages in chat yet (don't show after conversation has started)
|
||||
if (recipeAccepted && !hasAllRequiredParameters && !isParameterModalOpen && !hasMessages) {
|
||||
setIsParameterModalOpen(true);
|
||||
}
|
||||
}, [
|
||||
hasAllRequiredParameters,
|
||||
recipeAccepted,
|
||||
filteredParameters,
|
||||
isParameterModalOpen,
|
||||
hasMessages,
|
||||
chat.sessionId,
|
||||
finalRecipe?.title,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
!requiresParameters &&
|
||||
chatContext &&
|
||||
finalRecipe &&
|
||||
chatContext.chat.resolvedRecipe !== finalRecipe
|
||||
) {
|
||||
chatContext?.setChat({
|
||||
...chatContext.chat,
|
||||
resolvedRecipe: finalRecipe,
|
||||
});
|
||||
}
|
||||
}, [requiresParameters, finalRecipe, chatContext]);
|
||||
|
||||
useEffect(() => {
|
||||
setReadyForAutoUserPrompt(true);
|
||||
}, []);
|
||||
|
||||
const initialPrompt = useMemo(() => {
|
||||
if (!finalRecipe?.prompt || !recipeAccepted || finalRecipe?.isScheduledExecution) {
|
||||
return '';
|
||||
}
|
||||
return resolvedRecipe?.prompt ?? finalRecipe.prompt;
|
||||
}, [finalRecipe, recipeAccepted, resolvedRecipe]);
|
||||
|
||||
const handleParameterSubmit = async (inputValues: Record<string, string>) => {
|
||||
try {
|
||||
let response = await updateSessionUserRecipeValues({
|
||||
path: {
|
||||
session_id: chat.sessionId,
|
||||
},
|
||||
body: {
|
||||
userRecipeValues: inputValues,
|
||||
},
|
||||
throwOnError: true,
|
||||
});
|
||||
let resolvedRecipe = response.data?.recipe;
|
||||
if (chatContext) {
|
||||
chatContext.setChat({
|
||||
...chatContext.chat,
|
||||
recipeParameterValues: inputValues,
|
||||
resolvedRecipe,
|
||||
});
|
||||
}
|
||||
setIsParameterModalOpen(false);
|
||||
} catch (error) {
|
||||
let error_message = 'unknown error';
|
||||
if (typeof error === 'object' && error !== null && 'message' in error) {
|
||||
error_message = error.message as string;
|
||||
} else if (typeof error === 'string') {
|
||||
error_message = error;
|
||||
}
|
||||
console.error('Failed to render recipe with parameters:', error);
|
||||
toastError({
|
||||
title: 'Recipe Rendering Failed',
|
||||
msg: error_message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const handleRecipeAccept = async () => {
|
||||
try {
|
||||
if (finalRecipe) {
|
||||
await window.electron.recordRecipeHash(finalRecipe);
|
||||
setRecipeAccepted(true);
|
||||
setIsRecipeWarningModalOpen(false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error recording recipe hash:', error);
|
||||
setRecipeAccepted(true);
|
||||
setIsRecipeWarningModalOpen(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleRecipeCancel = () => {
|
||||
setIsRecipeWarningModalOpen(false);
|
||||
window.electron.closeWindow();
|
||||
};
|
||||
|
||||
const handleAutoExecution = (
|
||||
append: (message: Message) => void,
|
||||
isLoading: boolean,
|
||||
onAutoExecute?: () => void
|
||||
) => {
|
||||
if (
|
||||
finalRecipe?.isScheduledExecution &&
|
||||
finalRecipe?.prompt &&
|
||||
(!requiresParameters || recipeParameterValues) &&
|
||||
messages.length === 0 &&
|
||||
!isLoading &&
|
||||
readyForAutoUserPrompt &&
|
||||
recipeAccepted
|
||||
) {
|
||||
const finalPrompt = recipeParameterValues
|
||||
? substituteParameters(finalRecipe.prompt, recipeParameterValues)
|
||||
: finalRecipe.prompt;
|
||||
|
||||
const userMessage = createUserMessage(finalPrompt);
|
||||
append(userMessage);
|
||||
onAutoExecute?.();
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const handleMakeAgent = async () => {
|
||||
if (window.isCreatingRecipe) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isCreatingRecipeRef.current) {
|
||||
return;
|
||||
}
|
||||
|
||||
setIsCreateRecipeModalOpen(true);
|
||||
};
|
||||
|
||||
window.addEventListener('make-agent-from-chat', handleMakeAgent);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('make-agent-from-chat', handleMakeAgent);
|
||||
};
|
||||
}, [chat.sessionId]);
|
||||
|
||||
const handleRecipeCreated = (recipe: Recipe) => {
|
||||
toastSuccess({
|
||||
title: 'Recipe created successfully!',
|
||||
msg: `"${recipe.title}" has been saved and is ready to use.`,
|
||||
});
|
||||
};
|
||||
|
||||
const recipeId: string | null =
|
||||
(window.appConfig.get('recipeId') as string | null | undefined) ?? null;
|
||||
|
||||
return {
|
||||
recipe: finalRecipe,
|
||||
recipeId,
|
||||
recipeParameterValues,
|
||||
filteredParameters,
|
||||
initialPrompt,
|
||||
isParameterModalOpen,
|
||||
setIsParameterModalOpen,
|
||||
readyForAutoUserPrompt,
|
||||
handleParameterSubmit,
|
||||
handleAutoExecution,
|
||||
recipeError,
|
||||
setRecipeError,
|
||||
isRecipeWarningModalOpen,
|
||||
setIsRecipeWarningModalOpen,
|
||||
recipeAccepted,
|
||||
handleRecipeAccept,
|
||||
handleRecipeCancel,
|
||||
hasSecurityWarnings,
|
||||
isCreateRecipeModalOpen,
|
||||
setIsCreateRecipeModalOpen,
|
||||
handleRecipeCreated,
|
||||
};
|
||||
};
|
||||
@@ -1592,9 +1592,7 @@ ipcMain.handle('check-ollama', async () => {
|
||||
return resolve(false);
|
||||
}
|
||||
|
||||
console.log('Raw stdout from ps|grep command:', output);
|
||||
const trimmedOutput = output.trim();
|
||||
console.log('Trimmed stdout:', trimmedOutput);
|
||||
|
||||
const isRunning = trimmedOutput.length > 0;
|
||||
resolve(isRunning);
|
||||
@@ -2187,7 +2185,6 @@ async function appMain() {
|
||||
// Remove any HTML tags for security
|
||||
const sanitizeText = (text: string) => text.replace(/<[^>]*>/g, '');
|
||||
|
||||
console.log('NOTIFY', data);
|
||||
const notification = new Notification({
|
||||
title: sanitizeText(data.title),
|
||||
body: sanitizeText(data.body),
|
||||
|
||||
@@ -33,7 +33,6 @@ export async function encodeRecipe(recipe: Recipe): Promise<string> {
|
||||
}
|
||||
|
||||
export async function decodeRecipe(deeplink: string): Promise<Recipe> {
|
||||
console.log('Decoding recipe from deeplink:', deeplink);
|
||||
|
||||
try {
|
||||
const response = await apiDecodeRecipe({
|
||||
|
||||
@@ -20,13 +20,11 @@ const TELEMETRY_CONFIG_KEY = 'GOOSE_TELEMETRY_ENABLED';
|
||||
const isLauncher = window.location.hash === '#/launcher';
|
||||
|
||||
if (!isLauncher) {
|
||||
console.log('window created, getting goosed connection info');
|
||||
const gooseApiHost = await window.electron.getGoosedHostPort();
|
||||
if (gooseApiHost === null) {
|
||||
window.alert('failed to start goose backend process');
|
||||
return;
|
||||
}
|
||||
console.log('connecting at', gooseApiHost);
|
||||
client.setConfig({
|
||||
baseUrl: gooseApiHost,
|
||||
headers: {
|
||||
|
||||
@@ -1,130 +0,0 @@
|
||||
import { Session } from '../api';
|
||||
import { getApiUrl } from '../config';
|
||||
import { errorMessage } from './conversionUtils';
|
||||
|
||||
/**
|
||||
* In-memory cache for session data
|
||||
* Maps session ID to Session object
|
||||
*/
|
||||
const sessionCache = new Map<string, Session>();
|
||||
|
||||
/**
|
||||
* In-flight request tracking to prevent duplicate fetches
|
||||
* Maps session ID to Promise of Session
|
||||
*/
|
||||
const inFlightRequests = new Map<string, Promise<Session>>();
|
||||
|
||||
/**
|
||||
* Load a session from the server using the /agent/resume endpoint
|
||||
* Implements caching to avoid redundant fetches
|
||||
*
|
||||
* @param sessionId - The unique identifier for the session
|
||||
* @param forceRefresh - If true, bypass cache and fetch fresh data
|
||||
* @returns Promise resolving to the Session object
|
||||
* @throws Error if the request fails or session not found
|
||||
*/
|
||||
export async function loadSession(sessionId: string, forceRefresh = false): Promise<Session> {
|
||||
if (!forceRefresh && sessionCache.has(sessionId)) {
|
||||
return sessionCache.get(sessionId)!;
|
||||
}
|
||||
|
||||
if (inFlightRequests.has(sessionId)) {
|
||||
return inFlightRequests.get(sessionId)!;
|
||||
}
|
||||
|
||||
const fetchPromise = (async () => {
|
||||
try {
|
||||
const url = getApiUrl('/agent/resume');
|
||||
const secretKey = await window.electron.getSecretKey();
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Secret-Key': secretKey,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
session_id: sessionId,
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text().catch(() => 'Unknown error');
|
||||
throw new Error(`Failed to load session: HTTP ${response.status} - ${errorText}`);
|
||||
}
|
||||
|
||||
const session: Session = await response.json();
|
||||
sessionCache.set(sessionId, session);
|
||||
|
||||
return session;
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`Error loading session ${sessionId}: ${errorMessage(error, 'Unknown error')}`
|
||||
);
|
||||
} finally {
|
||||
inFlightRequests.delete(sessionId);
|
||||
}
|
||||
})();
|
||||
|
||||
inFlightRequests.set(sessionId, fetchPromise);
|
||||
return fetchPromise;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear a specific session from the cache
|
||||
* Useful when a session has been updated and needs to be refetched
|
||||
*
|
||||
* @param sessionId - The unique identifier for the session to clear
|
||||
*/
|
||||
export function clearSessionCache(sessionId: string): void {
|
||||
sessionCache.delete(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all sessions from the cache
|
||||
* Useful for logout or when switching contexts
|
||||
*/
|
||||
export function clearAllSessionCache(): void {
|
||||
sessionCache.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a session is currently cached
|
||||
*
|
||||
* @param sessionId - The unique identifier for the session
|
||||
* @returns true if the session is in cache, false otherwise
|
||||
*/
|
||||
export function isSessionCached(sessionId: string): boolean {
|
||||
return sessionCache.has(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a session from cache without fetching
|
||||
* Returns undefined if not cached
|
||||
*
|
||||
* @param sessionId - The unique identifier for the session
|
||||
* @returns The cached Session object or undefined
|
||||
*/
|
||||
export function getCachedSession(sessionId: string): Session | undefined {
|
||||
return sessionCache.get(sessionId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload a session into cache
|
||||
* Useful when you already have session data from another source
|
||||
*
|
||||
* @param session - The Session object to cache
|
||||
*/
|
||||
export function preloadSession(session: Session): void {
|
||||
sessionCache.set(session.id, session);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current cache size
|
||||
* Useful for debugging and monitoring
|
||||
*
|
||||
* @returns The number of sessions currently cached
|
||||
*/
|
||||
export function getCacheSize(): number {
|
||||
return sessionCache.size;
|
||||
}
|
||||
Reference in New Issue
Block a user