-
Notifications
You must be signed in to change notification settings - Fork 14
feat(appkit): reference agent-app, dev-playground chat UI, docs, and template #306
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: agent/v2/5-fromplugin-runagent
Are you sure you want to change the base?
Changes from all commits
3784c43
aae324f
41404e3
51c7bfa
2dfa041
a371f8a
1f64b8f
c5f85c4
1e29c53
755cfbf
2352683
9cc3e2c
d91f9c3
906be67
e4fda01
56584d2
8b43c4f
8d0af9f
098ccc6
8be75bb
1051359
463161e
76e7f67
d95be98
38562da
133c185
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's a lot of the code for the dev playground. Honestly, I'm not sure if that fits in the dev playground - maybe we should move it somewhere? E.g. Move it as a separate template in IMO this is too good to keep it in the dev playground.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, as a second thought - the "Smart dashboard" example is kind of reimplementing Metric Views? Correct me if I'm wrong 🤔
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agentic reviews (aggregated report): Code Review — agent/v2/6-apps-docsContextBranch What works well
P0 — Must fix1. Doc/behavior contradiction on auto-inheritFile: Level 1 documentation says:
But the Configuration Reference says:
These directly contradict each other. A developer following Level 1 will expect their markdown agent to see all plugin tools out of the box, but it won't — they get zero tools. This is the single most confusing thing in the agents DX. Fix: Either (a) add P1 — Should fix before merge2. MCP
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import { CheckCircle2Icon } from "lucide-react"; | ||
| import { useEffect, useState } from "react"; | ||
|
|
||
| interface ActionToastProps { | ||
| /** | ||
| * Latest dispatcher-surfaced action summary. Each new value bumps a | ||
| * render key so the toast re-animates even if the same message arrives | ||
| * twice (e.g. two identical filter calls in a row). | ||
| */ | ||
| message: string | null; | ||
| durationMs?: number; | ||
| } | ||
|
|
||
| /** | ||
| * Non-intrusive bottom-left toast that confirms every agent-driven UI | ||
| * action. Silent success was the worst failure mode before: an action | ||
| * silently not-applied looked identical to one that worked but didn't | ||
| * show its effect. | ||
| */ | ||
| export function ActionToast({ message, durationMs = 2800 }: ActionToastProps) { | ||
| const [visible, setVisible] = useState<{ key: number; text: string } | null>( | ||
| null, | ||
| ); | ||
|
|
||
| useEffect(() => { | ||
| if (!message) return; | ||
| const key = Date.now(); | ||
| setVisible({ key, text: message }); | ||
| const t = setTimeout(() => { | ||
| setVisible((v) => (v?.key === key ? null : v)); | ||
| }, durationMs); | ||
| return () => { | ||
| clearTimeout(t); | ||
| }; | ||
| }, [message, durationMs]); | ||
|
|
||
| if (!visible) return null; | ||
|
|
||
| return ( | ||
| <div | ||
| key={visible.key} | ||
| className="fixed bottom-20 left-4 z-30 rounded-full bg-card border border-border shadow-lg px-3 py-1.5 flex items-center gap-2 animate-in fade-in slide-in-from-bottom-2 duration-200" | ||
| > | ||
| <CheckCircle2Icon className="h-3.5 w-3.5 text-green-500 shrink-0" /> | ||
| <span className="text-xs text-foreground">{visible.text}</span> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| import { | ||
| AlertTriangleIcon, | ||
| ArrowRightIcon, | ||
| CalendarIcon, | ||
| CrosshairIcon, | ||
| DollarSignIcon, | ||
| HighlighterIcon, | ||
| LightbulbIcon, | ||
| MapPinIcon, | ||
| MessageSquareIcon, | ||
| } from "lucide-react"; | ||
| import type { FeedAction } from "../lib/feed-actions"; | ||
|
|
||
| type Variant = "insight" | "anomaly"; | ||
| type Severity = "low" | "medium" | "high"; | ||
|
|
||
| interface ActionableCardProps { | ||
| variant: Variant; | ||
| severity?: Severity; | ||
| title: string; | ||
| description: string; | ||
| actions: FeedAction[]; | ||
| /** Fired for non-ask actions. Route applies them to dashboard state. */ | ||
| onAction: (action: FeedAction) => void; | ||
| /** Fired for `ask` actions. Route forwards the prompt to the chat drawer. */ | ||
| onAsk: (prompt: string) => void; | ||
| } | ||
|
|
||
| // Backgrounds are written as arbitrary 8-digit hex (e.g. `bg-[#eff6ff80]`) | ||
| // instead of Tailwind's `/N` alpha shorthand. Rationale: `bg-blue-50/50` | ||
| // compiles in Tailwind v4 to a pair — an sRGB hex fallback and a | ||
| // `@supports (color-mix)` override that re-mixes in oklab over the oklch | ||
| // palette token. Browsers that support `color-mix` (recent Chrome/Arc) take | ||
| // the oklab path; older embedded Chromiums (e.g. Cursor's built-in browser | ||
| // at the time of writing) fall through to the sRGB hex. Because oklab and | ||
| // sRGB interpolation produce visibly different tints — especially against | ||
| // the dark `--card` token — the same card ends up looking different in each | ||
| // browser. Pinning the colour to a literal hex (no `/N`, no @supports | ||
| // override) keeps all browsers on the same sRGB path and therefore the same | ||
| // visual result. | ||
| const INSIGHT_STYLES = { | ||
| border: "border-blue-200 dark:border-blue-900", | ||
| bg: "bg-[#eff6ff80] dark:bg-[#1624564d]", | ||
| icon: "text-blue-500", | ||
| }; | ||
|
|
||
| const ANOMALY_STYLES: Record< | ||
| Severity, | ||
| { border: string; bg: string; icon: string; badge: string } | ||
| > = { | ||
| low: { | ||
| border: "border-yellow-200 dark:border-yellow-900", | ||
| bg: "bg-[#fefce880] dark:bg-[#4320044d]", | ||
| icon: "text-yellow-500", | ||
| badge: | ||
| "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/50 dark:text-yellow-400", | ||
| }, | ||
| medium: { | ||
| border: "border-orange-200 dark:border-orange-900", | ||
| bg: "bg-[#fff7ed80] dark:bg-[#4413064d]", | ||
| icon: "text-orange-500", | ||
| badge: | ||
| "bg-orange-100 text-orange-700 dark:bg-orange-900/50 dark:text-orange-400", | ||
| }, | ||
| high: { | ||
| border: "border-red-200 dark:border-red-900", | ||
| bg: "bg-[#fef2f280] dark:bg-[#4608094d]", | ||
| icon: "text-red-500", | ||
| badge: "bg-red-100 text-red-700 dark:bg-red-900/50 dark:text-red-400", | ||
| }, | ||
| }; | ||
|
|
||
| function iconForAction(kind: FeedAction["kind"]): React.ReactNode { | ||
| const cls = "h-3 w-3"; | ||
| switch (kind) { | ||
| case "filter_date": | ||
| return <CalendarIcon className={cls} />; | ||
| case "filter_zip": | ||
| return <MapPinIcon className={cls} />; | ||
| case "filter_fare": | ||
| return <DollarSignIcon className={cls} />; | ||
| case "highlight_period": | ||
| return <HighlighterIcon className={cls} />; | ||
| case "highlight_zone": | ||
| return <MapPinIcon className={cls} />; | ||
| case "focus_chart": | ||
| return <CrosshairIcon className={cls} />; | ||
| case "ask": | ||
| return <MessageSquareIcon className={cls} />; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Action chip for a single feed suggestion. The chip's visual weight depends | ||
| * on its kind: structural mutations (filter/highlight/focus) use the primary | ||
| * tint, `ask` uses a neutral outline so the user can tell "this opens the | ||
| * chat" from "this changes the dashboard" without reading the label. | ||
| */ | ||
| function ActionChip({ | ||
| action, | ||
| onAction, | ||
| onAsk, | ||
| }: { | ||
| action: FeedAction; | ||
| onAction: (a: FeedAction) => void; | ||
| onAsk: (prompt: string) => void; | ||
| }) { | ||
| const isAsk = action.kind === "ask"; | ||
| const isHighlight = | ||
| action.kind === "highlight_period" || action.kind === "highlight_zone"; | ||
|
|
||
| return ( | ||
| <button | ||
| type="button" | ||
| onClick={() => { | ||
| if (isAsk) onAsk(action.prompt); | ||
| else onAction(action); | ||
| }} | ||
| className={`inline-flex items-center gap-1 text-[11px] font-medium px-2 py-1 rounded-md transition-colors ${ | ||
| isAsk | ||
| ? "border border-border bg-background text-foreground/80 hover:bg-muted hover:text-foreground" | ||
| : isHighlight | ||
| ? "bg-amber-100 text-amber-800 hover:bg-amber-200 dark:bg-amber-900/40 dark:text-amber-200 dark:hover:bg-amber-900/60" | ||
| : "bg-primary/10 text-primary hover:bg-primary/20" | ||
| }`} | ||
| > | ||
| {iconForAction(action.kind)} | ||
| <span>{action.label}</span> | ||
| {isAsk && <ArrowRightIcon className="h-3 w-3 opacity-70" />} | ||
| </button> | ||
| ); | ||
| } | ||
|
|
||
| export function ActionableCard({ | ||
| variant, | ||
| severity, | ||
| title, | ||
| description, | ||
| actions, | ||
| onAction, | ||
| onAsk, | ||
| }: ActionableCardProps) { | ||
| const isAnomaly = variant === "anomaly"; | ||
| const styles = isAnomaly | ||
| ? ANOMALY_STYLES[severity ?? "low"] | ||
| : { ...INSIGHT_STYLES, badge: "" }; | ||
|
|
||
| return ( | ||
| <div className={`rounded-lg border ${styles.border} ${styles.bg} p-3`}> | ||
| <div className="flex items-start gap-2 mb-2"> | ||
| {isAnomaly ? ( | ||
| <AlertTriangleIcon | ||
| className={`h-4 w-4 ${styles.icon} mt-0.5 shrink-0`} | ||
| /> | ||
| ) : ( | ||
| <LightbulbIcon className={`h-4 w-4 ${styles.icon} mt-0.5 shrink-0`} /> | ||
| )} | ||
| <div className="min-w-0 flex-1"> | ||
| <div className="flex items-start gap-2"> | ||
| <p className="text-sm font-medium text-foreground leading-tight flex-1"> | ||
| {title} | ||
| </p> | ||
| {isAnomaly && severity && ( | ||
| <span | ||
| className={`text-[10px] font-medium px-1.5 py-0.5 rounded shrink-0 ${styles.badge}`} | ||
| > | ||
| {severity} | ||
| </span> | ||
| )} | ||
| </div> | ||
| <p className="text-xs text-muted-foreground mt-1 leading-relaxed"> | ||
| {description} | ||
| </p> | ||
| </div> | ||
| </div> | ||
|
|
||
| {actions.length > 0 && ( | ||
| <div className="flex flex-wrap gap-1.5 pl-6"> | ||
| {actions.map((action, i) => ( | ||
| <ActionChip | ||
| key={`${action.kind}-${i}-${action.label}`} | ||
| action={action} | ||
| onAction={onAction} | ||
| onAsk={onAsk} | ||
| /> | ||
| ))} | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wait, this file shouldn't be modified, right? Because there's no breaking change if we haven't released agent plugin yet?
(BTW, why do we have so many "Changelog" headers? 😄 something is wrong with our changelog generation, I guess)