feat: implement full context-scoped AI chat UI in AIChatPanel

- Added AIChatPanel component with context header, user and AI message handling.
- Integrated streaming responses via IPC and error handling for chat mutations.
- Enhanced user experience with input handling and auto-scrolling features.
- Updated AppShell to derive AI chat context from the current route.
- Introduced ScrollArea component for better scrolling behavior in various dialogs.
- Added support for Tailwind typography and improved global styles.
- Updated project and task dialogs to utilize ScrollArea for better UX.
This commit is contained in:
Roberto Musso
2026-02-24 12:02:06 +01:00
parent 00a43e0fbc
commit 5eb19e022e
20 changed files with 962 additions and 91 deletions

View File

@@ -23,6 +23,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover
import { Calendar } from '@/components/ui/calendar';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import { ScrollArea } from '@/components/ui/scroll-area';
import { cn } from '@/lib/utils';
import type { TaskItem } from './TaskRow';
@@ -275,7 +276,8 @@ export function EditTaskDialog({ task, open, onOpenChange }: EditTaskDialogProps
</PopoverTrigger>
<PopoverContent className="w-64 p-2" align="start">
{knownAssignees.length > 0 && (
<div className="max-h-36 overflow-y-auto flex flex-col gap-0.5 mb-2">
<ScrollArea className="max-h-36 mb-2">
<div className="flex flex-col gap-0.5">
{knownAssignees.map((name) => (
<Button
key={name}
@@ -293,7 +295,8 @@ export function EditTaskDialog({ task, open, onOpenChange }: EditTaskDialogProps
<span className="truncate">{name}</span>
</Button>
))}
</div>
</div>
</ScrollArea>
)}
{knownAssignees.length === 0 && (
<p className="text-xs text-muted-foreground px-2 py-1 mb-2">No existing assignees</p>

View File

@@ -23,6 +23,7 @@ import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover
import { Calendar } from '@/components/ui/calendar';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import { ScrollArea } from '@/components/ui/scroll-area';
import { cn } from '@/lib/utils';
const NO_CLIENT = '__no_client__';
@@ -512,7 +513,8 @@ export function NewTaskDialog({ open, onOpenChange, defaultProjectId, defaultSta
<PopoverContent className="w-64 p-2" align="start">
{/* Known assignees list */}
{knownAssignees.length > 0 && (
<div className="max-h-36 overflow-y-auto flex flex-col gap-0.5 mb-2">
<ScrollArea className="max-h-36 mb-2">
<div className="flex flex-col gap-0.5">
{knownAssignees.map((name) => (
<Button
key={name}
@@ -530,7 +532,8 @@ export function NewTaskDialog({ open, onOpenChange, defaultProjectId, defaultSta
<span className="truncate">{name}</span>
</Button>
))}
</div>
</div>
</ScrollArea>
)}
{knownAssignees.length === 0 && (
<p className="text-xs text-muted-foreground px-2 py-1 mb-2">No existing assignees</p>

View File

@@ -21,6 +21,7 @@ import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Separator } from '@/components/ui/separator';
import { Input } from '@/components/ui/input';
import { ScrollArea } from '@/components/ui/scroll-area';
import { trpc } from '@/lib/trpc';
import { PriorityBadge } from './PriorityBadge';
import { parseAssignees, type TaskItem } from './TaskRow';
@@ -194,7 +195,8 @@ export function TaskDetailDialog({ task, open, onOpenChange, onEdit, onDelete }:
<TabsContent value="comment" className="px-6 py-4 min-h-[120px] flex flex-col gap-4">
{/* Comment list */}
<div className="flex flex-col gap-4 max-h-[260px] overflow-y-auto">
<ScrollArea className="max-h-[260px]">
<div className="flex flex-col gap-4">
{(!comments || comments.length === 0) ? (
<p className="text-sm text-muted-foreground italic">No comments yet.</p>
) : (
@@ -223,6 +225,7 @@ export function TaskDetailDialog({ task, open, onOpenChange, onEdit, onDelete }:
))
)}
</div>
</ScrollArea>
{/* Add comment input */}
<form