ed912b2683
Co-authored-by: Kandev Agent <agent@kandev.dev>
171 lines
6.0 KiB
TypeScript
171 lines
6.0 KiB
TypeScript
"use client";
|
|
|
|
import { memo, useCallback, useState } from "react";
|
|
import { Dialog, DialogContent, DialogTitle } from "@kandev/ui/dialog";
|
|
import { Button } from "@kandev/ui/button";
|
|
import { Label } from "@kandev/ui/label";
|
|
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@kandev/ui/select";
|
|
import { IconX, IconRocket } from "@tabler/icons-react";
|
|
import { useAppStore } from "@/components/state-provider";
|
|
import { useToast } from "@/components/toast-provider";
|
|
import { startQuickChat } from "@/lib/api/domains/workspace-api";
|
|
import type { Repository } from "@/lib/types/http";
|
|
import type { AgentProfileOption } from "@/lib/state/slices/settings/types";
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
type QuickChatPickerDialogProps = {
|
|
open: boolean;
|
|
onOpenChange: (open: boolean) => void;
|
|
workspaceId: string;
|
|
};
|
|
|
|
type FormState = {
|
|
selectedRepoId: string;
|
|
setSelectedRepoId: (id: string) => void;
|
|
selectedAgentId: string;
|
|
setSelectedAgentId: (id: string) => void;
|
|
repositories: Repository[];
|
|
agentProfiles: AgentProfileOption[];
|
|
};
|
|
|
|
const NONE_VALUE = "__none__";
|
|
|
|
function QuickChatFormBody({ state }: { state: FormState }) {
|
|
const { t } = useTranslation();
|
|
const { selectedRepoId, setSelectedRepoId, selectedAgentId, setSelectedAgentId } = state;
|
|
return (
|
|
<div className="p-4 space-y-4">
|
|
<p className="text-sm text-muted-foreground">{t("chat:quickChatDialogIntro")}</p>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="repository">{t("chat:repositoryOptional")}</Label>
|
|
<Select
|
|
value={selectedRepoId || NONE_VALUE}
|
|
onValueChange={(v) => setSelectedRepoId(v === NONE_VALUE ? "" : v)}
|
|
>
|
|
<SelectTrigger id="repository" className="w-full">
|
|
<SelectValue placeholder={t("chat:selectARepository")} />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value={NONE_VALUE}>{t("chat:noRepository")}</SelectItem>
|
|
{state.repositories.map((repo) => (
|
|
<SelectItem key={repo.id} value={repo.id}>
|
|
{repo.name}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
<div className="space-y-2">
|
|
<Label htmlFor="agent">{t("chat:agentOptional")}</Label>
|
|
<Select
|
|
value={selectedAgentId || NONE_VALUE}
|
|
onValueChange={(v) => setSelectedAgentId(v === NONE_VALUE ? "" : v)}
|
|
>
|
|
<SelectTrigger id="agent" className="w-full">
|
|
<SelectValue placeholder={t("chat:useWorkspaceDefault")} />
|
|
</SelectTrigger>
|
|
<SelectContent>
|
|
<SelectItem value={NONE_VALUE}>{t("chat:useWorkspaceDefaultOption")}</SelectItem>
|
|
{state.agentProfiles.map((profile) => (
|
|
<SelectItem key={profile.id} value={profile.id}>
|
|
{profile.label}
|
|
</SelectItem>
|
|
))}
|
|
</SelectContent>
|
|
</Select>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/** Dialog to pick repository and agent for a new quick chat session */
|
|
export const QuickChatPickerDialog = memo(function QuickChatPickerDialog({
|
|
open,
|
|
onOpenChange,
|
|
workspaceId,
|
|
}: QuickChatPickerDialogProps) {
|
|
const { t } = useTranslation();
|
|
const { toast } = useToast();
|
|
const openQuickChat = useAppStore((s) => s.openQuickChat);
|
|
const [isStarting, setIsStarting] = useState(false);
|
|
const [selectedRepoId, setSelectedRepoId] = useState<string>("");
|
|
const [selectedAgentId, setSelectedAgentId] = useState<string>("");
|
|
const repositories = useAppStore((s) => s.repositories.itemsByWorkspaceId?.[workspaceId] ?? []);
|
|
const agentProfiles = useAppStore((s) => s.agentProfiles.items ?? []);
|
|
|
|
const handleStart = useCallback(async () => {
|
|
if (isStarting) return;
|
|
setIsStarting(true);
|
|
try {
|
|
const response = await startQuickChat(workspaceId, {
|
|
repository_id: selectedRepoId || undefined,
|
|
agent_profile_id: selectedAgentId || undefined,
|
|
});
|
|
onOpenChange(false);
|
|
// Open the quick chat modal with the new session
|
|
openQuickChat(response.session_id, workspaceId, undefined, "chat", response.task_id);
|
|
} catch (error) {
|
|
toast({
|
|
title: t("chat:failedToStartQuickChat"),
|
|
description: error instanceof Error ? error.message : t("chat:unknownError"),
|
|
variant: "error",
|
|
});
|
|
} finally {
|
|
setIsStarting(false);
|
|
}
|
|
}, [
|
|
workspaceId,
|
|
selectedRepoId,
|
|
selectedAgentId,
|
|
isStarting,
|
|
onOpenChange,
|
|
openQuickChat,
|
|
toast,
|
|
]);
|
|
|
|
const formState: FormState = {
|
|
selectedRepoId,
|
|
setSelectedRepoId,
|
|
selectedAgentId,
|
|
setSelectedAgentId,
|
|
repositories,
|
|
agentProfiles,
|
|
};
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onOpenChange}>
|
|
<DialogContent
|
|
className="!max-w-md p-0 gap-0 flex flex-col shadow-2xl"
|
|
showCloseButton={false}
|
|
overlayClassName="bg-black/20"
|
|
>
|
|
<DialogTitle className="sr-only">{t("chat:newQuickChat")}</DialogTitle>
|
|
<div className="flex items-center justify-between px-4 py-3 border-b">
|
|
<h2 className="text-lg font-semibold">{t("chat:newQuickChat")}</h2>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
onClick={() => onOpenChange(false)}
|
|
className="cursor-pointer h-8 w-8"
|
|
>
|
|
<IconX className="h-4 w-4" />
|
|
</Button>
|
|
</div>
|
|
<QuickChatFormBody state={formState} />
|
|
<div className="flex justify-end gap-2 px-4 py-3 border-t bg-muted/30">
|
|
<Button variant="outline" onClick={() => onOpenChange(false)} className="cursor-pointer">
|
|
{t("common:cancel")}
|
|
</Button>
|
|
<Button onClick={handleStart} disabled={isStarting} className="cursor-pointer">
|
|
<IconRocket className="h-4 w-4 mr-2" />
|
|
{isStarting ? t("chat:starting") : t("chat:startChat")}
|
|
</Button>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
});
|
|
|
|
/** Alias for backwards compatibility */
|
|
export const QuickChatDialog = QuickChatPickerDialog;
|