import { useState, useEffect, useRef } from 'react'; import { X } from 'lucide-react'; import { Dialog, DialogOverlay, DialogPortal } from '@/components/ui/dialog'; import * as DialogPrimitive from '@radix-ui/react-dialog'; import { cardStyle } from '@/components/Card'; import type { TaskInfo } from 'widgets/Chat'; import { useClaude } from '@/Screens/Dashboard/Chat/useClaude'; import { useOpenCode } from '@/Screens/Dashboard/Chat/useOpenCode'; import { EmbeddableChat } from '@/Screens/Dashboard/Chat/EmbeddableChat'; import { useVisibleClaudeModels, useVisibleOpenCodeModels } from '@/state/useModels'; import { useSettings } from '@/state/useSettings'; import type { TaskSummary } from 'widgets/FileBrowser'; const playDing = () => { const ctx = new AudioContext(); const t = ctx.currentTime; const play = (freq: number, start: number, dur: number) => { const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.type = 'sine'; osc.frequency.setValueAtTime(freq, t + start); gain.gain.setValueAtTime(0.8, t + start); gain.gain.setValueAtTime(0.8, t + start + dur * 0.6); gain.gain.exponentialRampToValueAtTime(0.001, t + start + dur); osc.connect(gain).connect(ctx.destination); osc.start(t + start); osc.stop(t + start + dur); }; play(880, 0, 0.4); play(1100, 0.25, 0.4); play(1320, 0.5, 0.6); setTimeout(() => ctx.close(), 1500); }; type InnerProps = { defaultInput: string; cwd: { root?: string; path: string }; initialModel: string | null; onProviderChange: (p: 'claude' | 'opencode') => void; }; const ClaudeInner = ({ defaultInput, cwd, initialModel, onProviderChange, taskInfo, }: InnerProps & { taskInfo: TaskInfo }) => { const chat = useClaude(undefined, initialModel, { replaceUrl: false, taskInfo }); const models = useVisibleClaudeModels(); const wasGenerating = useRef(false); useEffect(() => { if (wasGenerating.current && !chat.isGenerating) playDing(); wasGenerating.current = chat.isGenerating; }, [chat.isGenerating]); return ( ); }; const OpenCodeInner = ({ defaultInput, cwd, initialModel, onProviderChange, taskInfo, }: InnerProps & { taskInfo: TaskInfo }) => { const chat = useOpenCode(undefined, initialModel, { replaceUrl: false, taskInfo }); const models = useVisibleOpenCodeModels(); const wasGenerating = useRef(false); useEffect(() => { if (wasGenerating.current && !chat.isGenerating) playDing(); wasGenerating.current = chat.isGenerating; }, [chat.isGenerating]); return ( ); }; type TaskRunnerModalProps = { open: boolean; onOpenChange: (open: boolean) => void; task: TaskSummary; entryName: string; entryType: 'file' | 'directory'; cwd: { root?: string; path: string }; }; export const TaskRunnerModal = ({ open, onOpenChange, task, entryName, entryType, cwd }: TaskRunnerModalProps) => { const { settings } = useSettings(); const taskSettings = settings.tasks; const [provider, setProvider] = useState<'claude' | 'opencode'>(taskSettings.defaultProvider); const defaultInput = `Read the task instructions at ${task.filePath} and execute them on the ${entryType}: ${entryName}`; const taskInfo: TaskInfo = { taskName: task.name, taskDirName: task.dirName, entryName, entryType }; return ( ev.preventDefault()} className="fixed left-[50%] top-[50%] z-[700] translate-x-[-50%] translate-y-[-50%] flex flex-col overflow-hidden rounded-xl border-2 border-duck-dark/30 shadow-2xl w-[90vw] max-w-3xl h-[80vh] duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95" style={cardStyle({ backgroundColor: 'rgba(255, 255, 255, 0.97)', backgroundImage: ` linear-gradient(to right, rgba(20, 83, 45, 0.04) 1px, transparent 1px), linear-gradient(to bottom, rgba(20, 83, 45, 0.04) 1px, transparent 1px) `, })} > {/* Header */}
{task.name} {entryName}
{/* Chat */} {provider === 'claude' ? ( ) : ( )}
); };