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:
Douwe Osinga
2026-03-26 17:36:58 -04:00
committed by GitHub
parent 38f11d84db
commit 830b77c9f1
28 changed files with 4 additions and 1246 deletions
-15
View File
@@ -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);
}
};
-1
View File
@@ -412,7 +412,6 @@ export default function ChatInput({
provider = configModelAndProvider.provider;
}
if (!model || !provider) {
console.log('No model or provider found');
setIsTokenLimitLoaded(true);
return;
}
+1 -1
View File
@@ -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;
-7
View File
@@ -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;
-324
View File
@@ -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,
};
};
-3
View File
@@ -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),
-1
View File
@@ -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({
-2
View File
@@ -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: {
-130
View File
@@ -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;
}