Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 24 additions & 3 deletions src/features/ai/components/chat/chat-header.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { History } from "lucide-react";
import { History, Plus } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { ProviderIcon } from "@/features/ai/components/icons/provider-icons";
import { useSettingsStore } from "@/features/settings/store";
Expand All @@ -9,6 +9,8 @@ import { cn } from "@/utils/cn";
import { useAIChatStore } from "../../store/store";
import ChatHistoryDropdown from "../history/sidebar";
import { AgentSelector } from "../selectors/agent-selector";
import { AGENT_OPTIONS } from "@/features/ai/types/ai-chat";
import { AcpStreamHandler } from "@/features/ai/services/acp-stream-handler";

function EditableChatTitle({
title,
Expand Down Expand Up @@ -94,6 +96,7 @@ export function ChatHeader({ onDeleteChat }: ChatHeaderProps) {
const setIsChatHistoryVisible = useAIChatStore((state) => state.setIsChatHistoryVisible);
const updateChatTitle = useAIChatStore((state) => state.updateChatTitle);
const switchToChat = useAIChatStore((state) => state.switchToChat);
const createNewChat = useAIChatStore((state) => state.createNewChat);

const { openSettingsDialog } = useUIState();
const currentChat = chats.find((chat) => chat.id === currentChatId);
Expand Down Expand Up @@ -121,6 +124,26 @@ export function ChatHeader({ onDeleteChat }: ChatHeaderProps) {
</div>

<div className="flex shrink-0 items-center gap-1.5">
<AgentSelector variant="header" onOpenSettings={() => openSettingsDialog("ai")} />

<PaneIconButton
type="button"
onClick={() => {
const newChatId = createNewChat(currentAgentId);
const nextAgentInfo = AGENT_OPTIONS.find((a) => a.id === currentAgentId);
if (nextAgentInfo?.isAcp) {
void AcpStreamHandler.warmup(currentAgentId, newChatId).catch((error) => {
console.error(`Failed to prepare ${currentAgentId} session:`, error);
});
}
}}
tooltip="New Chat"
tooltipSide="bottom"
aria-label="New chat"
>
<Plus />
</PaneIconButton>

<PaneIconButton
type="button"
ref={historyButtonRef}
Expand All @@ -131,8 +154,6 @@ export function ChatHeader({ onDeleteChat }: ChatHeaderProps) {
>
<History />
</PaneIconButton>

<AgentSelector variant="header" onOpenSettings={() => openSettingsDialog("ai")} />
</div>

<ChatHistoryDropdown
Expand Down
13 changes: 3 additions & 10 deletions src/features/ai/components/chat/chat-message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,18 +152,11 @@ export const ChatMessage = memo(function ChatMessage({
(!message.content || message.content.trim().length === 0) &&
(!message.toolCalls || message.toolCalls.length === 0)
) {
if (isAcpAgent(currentAgentId())) {
return null;
}

return (
<div className="flex items-center gap-2 editor-font text-text-lighter text-xs">
<span className="flex items-center gap-1">
<span className="inline-block size-1.5 animate-pulse rounded-full bg-text-lighter/70" />
<span className="inline-block size-1.5 animate-pulse rounded-full bg-text-lighter/70 [animation-delay:150ms]" />
<span className="inline-block size-1.5 animate-pulse rounded-full bg-text-lighter/70 [animation-delay:300ms]" />
<div className="flex w-full items-center editor-font text-xs">
<span className="bg-[linear-gradient(110deg,var(--color-text-lighter),45%,var(--color-text),55%,var(--color-text-lighter))] bg-size-[200%_100%] bg-clip-text text-transparent animate-shimmer">
Thinking...
</span>
<span>thinking...</span>
</div>
);
}
Expand Down
14 changes: 12 additions & 2 deletions src/features/ai/components/selectors/acp-config-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ import type { SessionConfigOption } from "@/features/ai/types/acp";
import Select from "@/ui/select";
import { cn } from "@/utils/cn";

function formatModelName(name: string): string {
return name
.replace(/^\+/, "")
.split("-")
.map((part) =>
part.toLowerCase() === "gpt" ? "GPT" : part.charAt(0).toUpperCase() + part.slice(1),
)
.join("-");
}

interface AcpConfigSelectorProps {
option: SessionConfigOption;
onChange: (value: string) => void;
Expand All @@ -28,14 +38,14 @@ export function AcpConfigSelector({
value={option.kind.currentValue || option.kind.options[0]?.id || ""}
options={option.kind.options.map((value) => ({
value: value.id,
label: value.name,
label: formatModelName(value.name),
}))}
onChange={onChange}
size="xs"
variant="secondary"
open={open}
onOpenChange={onOpenChange}
className={cn("w-fit min-w-[132px] max-w-[220px]", className)}
className={cn("w-fit min-w-0 max-w-[220px]", className)}
menuClassName={menuClassName}
aria-label={option.name}
title={option.description || option.name}
Expand Down
31 changes: 19 additions & 12 deletions src/features/ai/components/selectors/agent-selector.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { invoke } from "@tauri-apps/api/core";
import { ChevronDown, Download, LoaderCircle, Plus, Search, Settings2 } from "lucide-react";

Check warning on line 2 in src/features/ai/components/selectors/agent-selector.tsx

View workflow job for this annotation

GitHub Actions / Bun — typecheck, frontend check

eslint(no-unused-vars)

Identifier 'Plus' is imported but never used.
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { ProviderIcon } from "@/features/ai/components/icons/provider-icons";
import { AcpStreamHandler } from "@/features/ai/services/acp-stream-handler";
Expand Down Expand Up @@ -28,7 +28,6 @@
const { showToast } = useToast();
const getCurrentAgentId = useAIChatStore((state) => state.getCurrentAgentId);
const setSelectedAgentId = useAIChatStore((state) => state.setSelectedAgentId);
const createNewChat = useAIChatStore((state) => state.createNewChat);
const changeCurrentChatAgent = useAIChatStore((state) => state.changeCurrentChatAgent);

const triggerRef = useRef<HTMLButtonElement>(null);
Expand Down Expand Up @@ -148,19 +147,19 @@
}
}

if (variant === "header") {
const newChatId = createNewChat(agentId);
const nextAgentInfo = AGENT_OPTIONS.find((a) => a.id === agentId);
if (nextAgentInfo?.isAcp) {
void AcpStreamHandler.warmup(agentId, newChatId).catch((error) => {
changeCurrentChatAgent(agentId);

const nextAgentInfo = AGENT_OPTIONS.find((a) => a.id === agentId);
if (nextAgentInfo?.isAcp) {
const activeChatId = useAIChatStore.getState().currentChatId;
if (activeChatId) {
void AcpStreamHandler.warmup(agentId, activeChatId).catch((error) => {
console.error(`Failed to prepare ${agentId} session:`, error);
});
}
} else {
changeCurrentChatAgent(agentId);
}
},
[variant, currentAgentId, setSelectedAgentId, changeCurrentChatAgent, createNewChat],
[variant, currentAgentId, setSelectedAgentId, changeCurrentChatAgent],
);

const handleInstallAgent = useCallback(
Expand Down Expand Up @@ -232,9 +231,17 @@
ref={triggerRef}
onClick={() => setIsOpen(!isOpen)}
type="button"
tooltip="New chat"
tooltip="Select Agent"
className="gap-1 px-1.5 w-auto text-text hover:text-text hover:bg-hover"
>
<Plus />
<ProviderIcon providerId={currentAgentId} size={14} className="text-text-lighter" />
<ChevronDown
size={12}
className={cn(
"text-text-lighter transition-transform opacity-70",
isOpen && "rotate-180",
)}
/>
</PaneIconButton>
) : (
<Button
Expand Down Expand Up @@ -274,7 +281,7 @@
/>
</div>

<div className="min-h-0 flex-1 overflow-y-auto p-1.5 [overscroll-behavior:contain]">
<div className="min-h-0 flex-1 overflow-y-auto p-1.5 overscroll-contain flex flex-col gap-0.5">
{filteredItems.length === 0 ? (
<div className="p-4 text-center text-text-lighter text-xs">No results found</div>
) : (
Expand Down
Loading
Loading