Skip to content
Closed
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
75 changes: 75 additions & 0 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -523,3 +523,78 @@
background-color: rgba(248, 81, 73, 0.35);
}
}

@keyframes session-locator-highlight-fade {
0% {
opacity: 0;
border-color: color-mix(in srgb, var(--primary) 14%, transparent);
box-shadow: 0 0 0 0 color-mix(in srgb, var(--primary) 0%, transparent);
}

10% {
opacity: 1;
border-color: color-mix(in srgb, var(--primary) 30%, transparent);
box-shadow: 0 0 0 0 color-mix(in srgb, var(--primary) 0%, transparent);
}

40% {
opacity: 1;
border-color: color-mix(in srgb, var(--primary) 46%, transparent);
box-shadow: 0 0 0 6px color-mix(in srgb, var(--primary) 12%, transparent);
}

68% {
opacity: 1;
border-color: color-mix(in srgb, var(--primary) 26%, transparent);
box-shadow: 0 0 0 2px color-mix(in srgb, var(--primary) 6%, transparent);
}

100% {
opacity: 0;
border-color: color-mix(in srgb, var(--primary) 14%, transparent);
box-shadow: 0 0 0 10px color-mix(in srgb, var(--primary) 0%, transparent);
}
}

.session-locator-turn-highlight {
isolation: isolate;
position: relative;
}

.session-locator-turn-highlight::after {
content: "";
position: absolute;
inset: -10px;
pointer-events: none;
border-radius: calc(1rem + 10px);
border: 1px solid color-mix(in srgb, var(--primary) 28%, transparent);
background: transparent;
animation: session-locator-highlight-fade 1500ms
cubic-bezier(0.22, 1, 0.36, 1) forwards;
}

.session-locator-part-highlight {
isolation: isolate;
position: relative;
}

.session-locator-part-highlight::after {
content: "";
position: absolute;
inset-block: -8px;
inset-inline: -6px;
pointer-events: none;
border-radius: calc(0.75rem + 6px);
border: 1px solid color-mix(in srgb, var(--primary) 30%, transparent);
background: transparent;
animation: session-locator-highlight-fade 1500ms
cubic-bezier(0.22, 1, 0.36, 1) forwards;
}

@media (prefers-reduced-motion: reduce) {
.session-locator-turn-highlight::after,
.session-locator-part-highlight::after {
animation: none;
opacity: 1;
}
}
6 changes: 3 additions & 3 deletions src/components/ai-elements/message.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,9 @@ export const MessageContent = ({
}: MessageContentProps) => (
<div
className={cn(
"is-user:dark flex min-w-0 flex-col gap-2 overflow-hidden text-sm",
"group-[.is-user]:ml-auto group-[.is-user]:w-fit group-[.is-user]:max-w-full group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
"group-[.is-assistant]:w-full group-[.is-assistant]:text-foreground",
"is-user:dark flex min-w-0 flex-col gap-2 text-sm",
"group-[.is-user]:ml-auto group-[.is-user]:w-fit group-[.is-user]:max-w-full group-[.is-user]:overflow-hidden group-[.is-user]:rounded-lg group-[.is-user]:bg-secondary group-[.is-user]:px-4 group-[.is-user]:py-3 group-[.is-user]:text-foreground",
"group-[.is-assistant]:w-full group-[.is-assistant]:overflow-visible group-[.is-assistant]:text-foreground",
className
)}
{...props}
Expand Down
42 changes: 31 additions & 11 deletions src/components/chat/agent-plan-overlay.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client"

import { memo, useMemo, useState } from "react"
import { memo, useMemo, useState, type CSSProperties } from "react"
import { useTranslations } from "next-intl"
import { Badge } from "@/components/ui/badge"
import { Button } from "@/components/ui/button"
Expand All @@ -22,9 +22,12 @@ interface AgentPlanOverlayProps {
planKey?: string | null
visible?: boolean
defaultExpanded?: boolean
className?: string
panelWidthPx?: number
panelMaxHeightPx?: number
}

function getLatestPlanEntries(message: LiveMessage | null): PlanEntryInfo[] {
export function getLatestPlanEntries(message: LiveMessage | null): PlanEntryInfo[] {
if (!message) return []

for (let i = message.content.length - 1; i >= 0; i -= 1) {
Expand Down Expand Up @@ -106,6 +109,9 @@ export const AgentPlanOverlay = memo(function AgentPlanOverlay({
planKey,
visible = true,
defaultExpanded = true,
className,
panelWidthPx,
panelMaxHeightPx,
}: AgentPlanOverlayProps) {
const t = useTranslations("Folder.chat.agentPlanOverlay")
const liveEntries = useMemo(
Expand Down Expand Up @@ -136,6 +142,17 @@ export const AgentPlanOverlay = memo(function AgentPlanOverlay({
const [collapsedByPlanKey, setCollapsedByPlanKey] = useState<
Record<string, boolean>
>({})
const panelStyle: CSSProperties | undefined = panelWidthPx
? {
width: `${panelWidthPx}px`,
maxWidth: "100%",
...(panelMaxHeightPx
? { maxHeight: `${panelMaxHeightPx}px` }
: null),
}
: panelMaxHeightPx
? { maxHeight: `${panelMaxHeightPx}px` }
: undefined
const isExpanded = !(
collapsedByPlanKey[currentPlanStateKey] ?? !resolvedDefaultExpanded
)
Expand All @@ -146,12 +163,12 @@ export const AgentPlanOverlay = memo(function AgentPlanOverlay({

if (!isExpanded) {
return (
<div className="pointer-events-none absolute right-8 top-4 z-20 flex">
<div className={cn("pointer-events-auto flex", className)}>
<Button
type="button"
variant="secondary"
size="sm"
className="cursor-pointer pointer-events-auto shadow-md bg-secondary/70 hover:bg-secondary"
className="h-8 w-44 justify-between gap-2 cursor-pointer shadow-md bg-secondary/70 hover:bg-secondary"
onClick={() =>
setCollapsedByPlanKey((prev) => ({
...prev,
Expand All @@ -160,10 +177,12 @@ export const AgentPlanOverlay = memo(function AgentPlanOverlay({
}
>
<ListTodoIcon className="h-4 w-4" />
{t("collapsedSummary", {
completed: completedCount,
total: resolvedEntries.length,
})}
<span className="min-w-0 flex-1 truncate text-center">
{t("collapsedSummary", {
completed: completedCount,
total: resolvedEntries.length,
})}
</span>
<ChevronUpIcon className="h-4 w-4" />
</Button>
</div>
Expand All @@ -172,10 +191,11 @@ export const AgentPlanOverlay = memo(function AgentPlanOverlay({

return (
<div
className="pointer-events-none absolute right-8 top-4 z-20 flex max-w-[min(22rem,calc(100%-2rem))]"
className={cn("pointer-events-auto flex min-h-0 min-w-0 max-h-full", className)}
style={panelStyle}
data-plan-key={currentPlanKey ?? undefined}
>
<div className="pointer-events-auto w-72 max-w-full rounded-xl border bg-card/60 hover:bg-card/95 shadow-lg backdrop-blur transition-colors supports-[backdrop-filter]:bg-card/50 supports-[backdrop-filter]:hover:bg-card/85">
<div className="flex min-h-0 max-h-full w-full max-w-full flex-col rounded-xl border bg-card/60 shadow-lg backdrop-blur transition-colors hover:bg-card/95 supports-[backdrop-filter]:bg-card/50 supports-[backdrop-filter]:hover:bg-card/85">
<div className="flex items-center justify-between border-b px-3 py-2">
<div className="flex items-center gap-2 min-w-0">
<ListTodoIcon className="h-4 w-4 text-muted-foreground" />
Expand All @@ -200,7 +220,7 @@ export const AgentPlanOverlay = memo(function AgentPlanOverlay({
</Button>
</div>

<div className="max-h-96 overflow-y-auto p-3 space-y-2">
<div className="min-h-0 flex-1 overflow-y-auto p-3 space-y-2">
{resolvedEntries.map((entry, index) => (
<div
key={`${entry.content}-${index}`}
Expand Down
Loading