mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-04-25 00:16:29 +02:00
commit
f68887496b
9 changed files with 2807 additions and 69 deletions
|
|
@ -348,6 +348,10 @@ export function setupIpcHandlers() {
|
|||
'runs:list': async (_event, args) => {
|
||||
return runsCore.listRuns(args.cursor);
|
||||
},
|
||||
'runs:delete': async (_event, args) => {
|
||||
await runsCore.deleteRun(args.runId);
|
||||
return { success: true };
|
||||
},
|
||||
'models:list': async () => {
|
||||
return await listOnboardingModels();
|
||||
},
|
||||
|
|
|
|||
|
|
@ -2306,6 +2306,17 @@ function App() {
|
|||
onSelectRun: (runIdToLoad) => {
|
||||
void navigateToView({ type: 'chat', runId: runIdToLoad })
|
||||
},
|
||||
onDeleteRun: async (runIdToDelete) => {
|
||||
try {
|
||||
await window.ipc.invoke('runs:delete', { runId: runIdToDelete })
|
||||
if (runId === runIdToDelete) {
|
||||
void navigateToView({ type: 'chat', runId: null })
|
||||
}
|
||||
await loadRuns()
|
||||
} catch (err) {
|
||||
console.error('Failed to delete run:', err)
|
||||
}
|
||||
},
|
||||
onSelectBackgroundTask: (taskName) => {
|
||||
void navigateToView({ type: 'task', name: taskName })
|
||||
},
|
||||
|
|
|
|||
|
|
@ -667,7 +667,7 @@ export function OnboardingModal({ open, onComplete }: OnboardingModalProps) {
|
|||
<Input
|
||||
value={activeConfig.model}
|
||||
onChange={(e) => updateProviderConfig(llmProvider, { model: e.target.value })}
|
||||
placeholder="Enter model ID"
|
||||
placeholder="Enter model"
|
||||
/>
|
||||
) : (
|
||||
<Select
|
||||
|
|
|
|||
|
|
@ -374,7 +374,7 @@ function ModelSettings({ dialogOpen }: { dialogOpen: boolean }) {
|
|||
<Input
|
||||
value={activeConfig.model}
|
||||
onChange={(e) => updateConfig(provider, { model: e.target.value })}
|
||||
placeholder="Enter model ID"
|
||||
placeholder="Enter model"
|
||||
/>
|
||||
) : (
|
||||
<Select
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import {
|
|||
LoaderIcon,
|
||||
Settings,
|
||||
Square,
|
||||
SquarePen,
|
||||
Trash2,
|
||||
} from "lucide-react"
|
||||
|
||||
|
|
@ -27,6 +26,15 @@ import {
|
|||
CollapsibleContent,
|
||||
CollapsibleTrigger,
|
||||
} from "@/components/ui/collapsible"
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog"
|
||||
import { Button } from "@/components/ui/button"
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
|
|
@ -128,6 +136,7 @@ const SERVICE_LABELS: Record<string, string> = {
|
|||
type TasksActions = {
|
||||
onNewChat: () => void
|
||||
onSelectRun: (runId: string) => void
|
||||
onDeleteRun: (runId: string) => void
|
||||
onSelectBackgroundTask?: (taskName: string) => void
|
||||
}
|
||||
|
||||
|
|
@ -1004,19 +1013,10 @@ function TasksSection({
|
|||
backgroundTasks?: BackgroundTaskItem[]
|
||||
selectedBackgroundTask?: string | null
|
||||
}) {
|
||||
const [pendingDeleteRunId, setPendingDeleteRunId] = useState<string | null>(null)
|
||||
|
||||
return (
|
||||
<SidebarGroup className="flex-1 flex flex-col overflow-hidden">
|
||||
{/* Sticky New Chat button - matches Knowledge section height */}
|
||||
<div className="sticky top-0 z-10 bg-sidebar border-b border-sidebar-border py-0.5">
|
||||
<SidebarMenu>
|
||||
<SidebarMenuItem>
|
||||
<SidebarMenuButton onClick={actions?.onNewChat} className="gap-2">
|
||||
<SquarePen className="size-4 shrink-0" />
|
||||
<span className="text-sm">New chat</span>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
</SidebarMenu>
|
||||
</div>
|
||||
<SidebarGroupContent className="flex-1 overflow-y-auto">
|
||||
{/* Background Tasks Section */}
|
||||
{backgroundTasks.length > 0 && (
|
||||
|
|
@ -1054,7 +1054,7 @@ function TasksSection({
|
|||
</div>
|
||||
<SidebarMenu>
|
||||
{runs.map((run) => (
|
||||
<SidebarMenuItem key={run.id}>
|
||||
<SidebarMenuItem key={run.id} className="group/chat-item">
|
||||
<SidebarMenuButton
|
||||
isActive={currentRunId === run.id}
|
||||
onClick={() => actions?.onSelectRun(run.id)}
|
||||
|
|
@ -1065,10 +1065,23 @@ function TasksSection({
|
|||
) : null}
|
||||
<span className="min-w-0 flex-1 truncate text-sm">{run.title || '(Untitled chat)'}</span>
|
||||
{run.createdAt ? (
|
||||
<span className="shrink-0 text-[10px] text-muted-foreground">
|
||||
<span className={`shrink-0 text-[10px] text-muted-foreground${processingRunIds?.has(run.id) ? '' : ' group-hover/chat-item:hidden'}`}>
|
||||
{formatRunTime(run.createdAt)}
|
||||
</span>
|
||||
) : null}
|
||||
{!processingRunIds?.has(run.id) && (
|
||||
<button
|
||||
type="button"
|
||||
className="shrink-0 hidden group-hover/chat-item:flex items-center justify-center text-muted-foreground hover:text-destructive transition-colors"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
setPendingDeleteRunId(run.id)
|
||||
}}
|
||||
aria-label="Delete chat"
|
||||
>
|
||||
<Trash2 className="size-3.5" />
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
</SidebarMenuButton>
|
||||
</SidebarMenuItem>
|
||||
|
|
@ -1077,6 +1090,35 @@ function TasksSection({
|
|||
</>
|
||||
)}
|
||||
</SidebarGroupContent>
|
||||
|
||||
{/* Delete confirmation dialog */}
|
||||
<Dialog open={!!pendingDeleteRunId} onOpenChange={(open) => { if (!open) setPendingDeleteRunId(null) }}>
|
||||
<DialogContent showCloseButton={false} className="sm:max-w-sm">
|
||||
<DialogHeader>
|
||||
<DialogTitle>Delete chat</DialogTitle>
|
||||
<DialogDescription>
|
||||
Are you sure you want to delete this chat?
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter>
|
||||
<Button variant="outline" size="sm" onClick={() => setPendingDeleteRunId(null)}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="destructive"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
if (pendingDeleteRunId) {
|
||||
actions?.onDeleteRun(pendingDeleteRunId)
|
||||
}
|
||||
setPendingDeleteRunId(null)
|
||||
}}
|
||||
>
|
||||
Delete
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</SidebarGroup>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -12,6 +12,7 @@ export interface IRunsRepo {
|
|||
fetch(id: string): Promise<z.infer<typeof Run>>;
|
||||
list(cursor?: string): Promise<z.infer<typeof ListRunsResponse>>;
|
||||
appendEvents(runId: string, events: z.infer<typeof RunEvent>[]): Promise<void>;
|
||||
delete(id: string): Promise<void>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -236,4 +237,9 @@ export class FSRunsRepo implements IRunsRepo {
|
|||
...(nextCursor ? { nextCursor } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
async delete(id: string): Promise<void> {
|
||||
const filePath = path.join(WorkDir, 'runs', `${id}.jsonl`);
|
||||
await fsp.unlink(filePath);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import { IRunsRepo } from "./repo.js";
|
|||
import { IAgentRuntime } from "../agents/runtime.js";
|
||||
import { IBus } from "../application/lib/bus.js";
|
||||
import { IAbortRegistry } from "./abort-registry.js";
|
||||
import { IRunsLock } from "./lock.js";
|
||||
import { forceCloseAllMcpClients } from "../mcp/mcp.js";
|
||||
|
||||
export async function createRun(opts: z.infer<typeof CreateRunOptions>): Promise<z.infer<typeof Run>> {
|
||||
|
|
@ -65,6 +66,19 @@ export async function stop(runId: string, force: boolean = false): Promise<void>
|
|||
// This avoids duplicate events and ensures proper sequencing.
|
||||
}
|
||||
|
||||
export async function deleteRun(runId: string): Promise<void> {
|
||||
const runsLock = container.resolve<IRunsLock>('runsLock');
|
||||
if (!await runsLock.lock(runId)) {
|
||||
throw new Error(`Cannot delete run ${runId}: run is currently active`);
|
||||
}
|
||||
try {
|
||||
const repo = container.resolve<IRunsRepo>('runsRepo');
|
||||
await repo.delete(runId);
|
||||
} finally {
|
||||
await runsLock.release(runId);
|
||||
}
|
||||
}
|
||||
|
||||
export async function fetchRun(runId: string): Promise<z.infer<typeof Run>> {
|
||||
const repo = container.resolve<IRunsRepo>('runsRepo');
|
||||
return repo.fetch(runId);
|
||||
|
|
|
|||
|
|
@ -173,6 +173,12 @@ const ipcSchemas = {
|
|||
}),
|
||||
res: ListRunsResponse,
|
||||
},
|
||||
'runs:delete': {
|
||||
req: z.object({
|
||||
runId: z.string(),
|
||||
}),
|
||||
res: z.object({ success: z.boolean() }),
|
||||
},
|
||||
'runs:events': {
|
||||
req: z.null(),
|
||||
res: z.null(),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue