@aphrody/m3-ai 3.3.2 → 3.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +7 -7
  2. package/dist/A2APeerInspector.d.ts +41 -0
  3. package/dist/A2APeerInspector.d.ts.map +1 -0
  4. package/dist/A2ATaskTimeline.d.ts +28 -0
  5. package/dist/A2ATaskTimeline.d.ts.map +1 -0
  6. package/dist/AgUiEventStream.d.ts +87 -0
  7. package/dist/AgUiEventStream.d.ts.map +1 -0
  8. package/dist/GeminiAccountMenu.d.ts +55 -0
  9. package/dist/GeminiAccountMenu.d.ts.map +1 -0
  10. package/dist/GeminiPersonalContextEdit.d.ts +29 -0
  11. package/dist/GeminiPersonalContextEdit.d.ts.map +1 -0
  12. package/dist/GoogleSpotlightBar.d.ts +32 -0
  13. package/dist/GoogleSpotlightBar.d.ts.map +1 -0
  14. package/dist/Markdown.d.ts +2 -1
  15. package/dist/Markdown.d.ts.map +1 -1
  16. package/dist/McpAppCard.d.ts +24 -0
  17. package/dist/McpAppCard.d.ts.map +1 -0
  18. package/dist/ModelSelector.d.ts.map +1 -1
  19. package/dist/TaskRow.d.ts.map +1 -1
  20. package/dist/ToolCall.d.ts.map +1 -1
  21. package/dist/index.d.ts +15 -0
  22. package/dist/index.d.ts.map +1 -1
  23. package/dist/index.js +3629 -1950
  24. package/dist/index.js.map +32 -1
  25. package/package.json +17 -20
  26. package/src/A2APeerInspector.tsx +378 -0
  27. package/src/A2ATaskTimeline.tsx +218 -0
  28. package/src/AgUiEventStream.ts +488 -0
  29. package/src/ChatComposer.tsx +239 -0
  30. package/src/GeminiAccountMenu.tsx +212 -0
  31. package/src/GeminiPersonalContextEdit.tsx +147 -0
  32. package/src/GoogleSpotlightBar.tsx +165 -0
  33. package/src/InlineBranchCard.tsx +97 -0
  34. package/src/InlineWorkerCard.tsx +182 -0
  35. package/src/Markdown.tsx +701 -0
  36. package/src/McpAppCard.tsx +161 -0
  37. package/src/MessageBubble.tsx +95 -0
  38. package/src/ModelSelector.tsx +300 -0
  39. package/src/TaskCreateForm.tsx +123 -0
  40. package/src/TaskDetail.tsx +224 -0
  41. package/src/TaskList.tsx +136 -0
  42. package/src/TaskPriorityIcon.tsx +39 -0
  43. package/src/TaskRow.tsx +177 -0
  44. package/src/TaskStatusIcon.tsx +39 -0
  45. package/src/ToolCall.tsx +639 -0
  46. package/src/index.ts +107 -0
  47. package/src/internal.tsx +107 -0
  48. package/src/types.ts +212 -0
@@ -0,0 +1,165 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Benchmark: assets/ui/google-desktop-spotlight-bar.png (manifest item 1)
3
+ // Dimensions: ~1266x450px, border-radius 32dp (BRAND_CORNER_PROMPT_BAR_PX), Alt + Space capsule
4
+
5
+ "use client";
6
+
7
+ import { CircleButton, Icon } from "@aphrody/m3-primitives";
8
+ import { clsx } from "clsx";
9
+ import { type ReactNode, useState } from "react";
10
+
11
+ export interface GoogleSpotlightBarProps {
12
+ /** Placeholder text */
13
+ placeholder?: string;
14
+ /** Active query draft */
15
+ query?: string;
16
+ /** On change callback */
17
+ onQueryChange?: (val: string) => void;
18
+ /** Triggered on submit */
19
+ onSubmit?: (query: string) => void;
20
+ /** File/attachment action handler */
21
+ onAttach?: () => void;
22
+ /** Screen capture / share window handler */
23
+ onScreenCapture?: () => void;
24
+ /** Google Lens multimodal query handler */
25
+ onGoogleLens?: () => void;
26
+ /** User avatar element or image URL */
27
+ avatar?: ReactNode;
28
+ /** Additional container classes */
29
+ className?: string;
30
+ }
31
+
32
+ /**
33
+ * Floating Spotlight Assistant Bar & Search Capsule (Alt + Space).
34
+ * Benchmark parity: `google-desktop-spotlight-bar.png` (manifest item 1).
35
+ * Features:
36
+ * - Floating translucent frameless capsule with 32dp corner radius (--gemini-corner-prompt-bar).
37
+ * - Soft desktop elevation shadow.
38
+ * - Global shortcut pill indicator ('Alt + Space').
39
+ * - Multimodal action buttons (+ for attach, screen capture, Google Lens).
40
+ */
41
+ export function GoogleSpotlightBar({
42
+ placeholder = "Ask anything or start typing...",
43
+ query: controlledQuery,
44
+ onQueryChange,
45
+ onSubmit,
46
+ onAttach,
47
+ onScreenCapture,
48
+ onGoogleLens,
49
+ avatar,
50
+ className,
51
+ }: GoogleSpotlightBarProps) {
52
+ const [internalQuery, setInternalQuery] = useState("");
53
+ const query = controlledQuery !== undefined ? controlledQuery : internalQuery;
54
+
55
+ const handleChange = (val: string) => {
56
+ if (controlledQuery === undefined) {
57
+ setInternalQuery(val);
58
+ }
59
+ onQueryChange?.(val);
60
+ };
61
+
62
+ const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
63
+ if (e.key === "Enter" && query.trim().length > 0) {
64
+ e.preventDefault();
65
+ onSubmit?.(query);
66
+ }
67
+ };
68
+
69
+ return (
70
+ <search
71
+ aria-label="Google Spotlight Assistant"
72
+ className={clsx(
73
+ "google-spotlight-capsule relative mx-auto flex w-full max-w-[840px] items-center gap-3",
74
+ "rounded-[var(--gemini-corner-prompt-bar,32px)] bg-surface-container-high/90 px-5 py-3.5 backdrop-blur-xl",
75
+ "border border-outline-variant/30 text-on-surface shadow-md3-3 transition-all duration-200",
76
+ "focus-within:border-primary/50 focus-within:bg-surface-container-highest/95 focus-within:shadow-md3-4",
77
+ className,
78
+ )}
79
+ >
80
+ {/* Google Colorful G Logo */}
81
+ <div className="flex h-9 w-9 shrink-0 items-center justify-center">
82
+ <svg viewBox="0 0 24 24" width="24" height="24" aria-hidden="true">
83
+ <path
84
+ fill="#4285F4"
85
+ d="M23.745 12.27c0-.7-.06-1.4-.19-2.07H12v4.51h6.6c-.29 1.52-1.14 2.8-2.4 3.64v3.03h3.88c2.27-2.09 3.665-5.17 3.665-9.11z"
86
+ />
87
+ <path
88
+ fill="#34A853"
89
+ d="M12 24c3.24 0 5.95-1.08 7.93-2.91l-3.88-3.03c-1.08.72-2.45 1.16-4.05 1.16-3.12 0-5.77-2.1-6.72-4.93H1.25v3.13C3.28 21.43 7.33 24 12 24z"
90
+ />
91
+ <path
92
+ fill="#FBBC05"
93
+ d="M5.28 14.29c-.25-.72-.38-1.49-.38-2.29s.14-1.57.38-2.29V6.57H1.25C.45 8.16 0 9.98 0 12s.45 3.84 1.25 5.43l4.03-3.14z"
94
+ />
95
+ <path
96
+ fill="#EA4335"
97
+ d="M12 4.75c1.77 0 3.35.61 4.6 1.8l3.42-3.42C17.95 1.19 15.24 0 12 0 7.33 0 3.28 2.57 1.25 6.57l4.03 3.14c.95-2.83 3.6-4.96 6.72-4.96z"
98
+ />
99
+ </svg>
100
+ </div>
101
+
102
+ {/* Main multimodal text input */}
103
+ <input
104
+ type="text"
105
+ value={query}
106
+ onChange={(e) => handleChange(e.target.value)}
107
+ onKeyDown={handleKeyDown}
108
+ placeholder={placeholder}
109
+ aria-label={placeholder}
110
+ className="block flex-1 border-0 bg-transparent px-1 text-body-large text-on-surface caret-primary outline-none placeholder:text-on-surface-variant focus:outline-none focus:ring-0"
111
+ />
112
+
113
+ {/* Multimodal Actions (Attach, Screen, Lens) */}
114
+ <div className="flex shrink-0 items-center gap-1.5">
115
+ {onAttach && (
116
+ <CircleButton
117
+ variant="standard"
118
+ size="sm"
119
+ onClick={onAttach}
120
+ aria-label="Add attachments"
121
+ title="Attach file"
122
+ >
123
+ <Icon name="add" size={20} />
124
+ </CircleButton>
125
+ )}
126
+
127
+ {onScreenCapture && (
128
+ <CircleButton
129
+ variant="standard"
130
+ size="sm"
131
+ onClick={onScreenCapture}
132
+ aria-label="Capture screen"
133
+ title="Share or capture screen"
134
+ >
135
+ <Icon name="screenshot" size={20} />
136
+ </CircleButton>
137
+ )}
138
+
139
+ {onGoogleLens && (
140
+ <CircleButton
141
+ variant="standard"
142
+ size="sm"
143
+ onClick={onGoogleLens}
144
+ aria-label="Google Lens"
145
+ title="Search with Google Lens"
146
+ className="hover:text-primary"
147
+ >
148
+ <Icon name="center_focus_strong" size={20} />
149
+ </CircleButton>
150
+ )}
151
+
152
+ {/* Global Shortcut Pill: Alt + Space */}
153
+ <div
154
+ aria-hidden="true"
155
+ className="ml-1 hidden select-none items-center rounded-full bg-surface-container px-2.5 py-1 text-label-small font-medium text-on-surface-variant sm:flex"
156
+ >
157
+ Alt + Space
158
+ </div>
159
+
160
+ {/* User avatar if provided */}
161
+ {avatar && <div className="ml-1 shrink-0">{avatar}</div>}
162
+ </div>
163
+ </search>
164
+ );
165
+ }
@@ -0,0 +1,97 @@
1
+ // SPDX-License-Identifier: Apache-2.0 AND MIT
2
+ // Derived from SpaceUI (packages/aphrody-spaceui/packages/ai/src/InlineBranchCard.tsx), MIT, rebuilt on Material Design 3.
3
+
4
+ import { useId, useState } from "react";
5
+ import { clsx } from "clsx";
6
+ import { AnimatePresence, motion } from "motion/react";
7
+ import { Icon } from "@aphrody/m3-primitives";
8
+ import { Markdown } from "./Markdown";
9
+ import { CircularIndicator, StatusChip, expandMotion, focusRing } from "./internal";
10
+
11
+ interface InlineBranchCardProps {
12
+ description: string;
13
+ completedAt: string | null;
14
+ conclusion?: string | null;
15
+ className?: string;
16
+ /** Start expanded (uncontrolled). */
17
+ defaultExpanded?: boolean;
18
+ }
19
+
20
+ /** A thinking branch as an M3 filled card: header, status chip, expandable conclusion. */
21
+ function InlineBranchCard({
22
+ description,
23
+ completedAt,
24
+ conclusion,
25
+ className,
26
+ defaultExpanded = false,
27
+ }: InlineBranchCardProps) {
28
+ const [expanded, setExpanded] = useState(defaultExpanded);
29
+ const isRunning = !completedAt;
30
+ const contentId = useId();
31
+ const canExpand = !!conclusion;
32
+
33
+ return (
34
+ <div className={clsx("group flex min-w-0 flex-col items-start", className)}>
35
+ <div className="min-w-0 max-w-full overflow-hidden rounded-medium bg-surface-container text-on-surface">
36
+ <button
37
+ type="button"
38
+ onClick={() => canExpand && setExpanded((v) => !v)}
39
+ aria-expanded={canExpand ? expanded : undefined}
40
+ aria-controls={canExpand ? contentId : undefined}
41
+ className={clsx(
42
+ "flex w-full items-start gap-3 rounded-medium px-4 py-3 text-left transition-colors duration-200 ease-standard",
43
+ canExpand && "hover:bg-on-surface/8 active:bg-on-surface/10",
44
+ focusRing,
45
+ )}
46
+ >
47
+ <div className="flex size-8 shrink-0 items-center justify-center">
48
+ {isRunning ? (
49
+ <CircularIndicator size={20} label="Thinking" />
50
+ ) : (
51
+ <div className="flex size-8 items-center justify-center rounded-full bg-primary-container text-on-primary-container">
52
+ <Icon name="alt_route" size={18} />
53
+ </div>
54
+ )}
55
+ </div>
56
+ <div className="min-w-0 flex-1 self-center">
57
+ <div className="flex items-center gap-2">
58
+ <div className="line-clamp-2 min-w-0 flex-1 text-title-small text-on-surface">
59
+ {description}
60
+ </div>
61
+ <StatusChip tone={isRunning ? "primary" : "success"}>
62
+ {isRunning ? "thinking" : "done"}
63
+ </StatusChip>
64
+ </div>
65
+ </div>
66
+ {canExpand && (
67
+ <Icon
68
+ name="expand_more"
69
+ size={20}
70
+ className={clsx(
71
+ "mt-1.5 shrink-0 text-on-surface-variant transition-transform duration-300 ease-emphasized",
72
+ expanded ? "rotate-180" : "",
73
+ )}
74
+ />
75
+ )}
76
+ </button>
77
+ <AnimatePresence initial={false}>
78
+ {expanded && conclusion ? (
79
+ <motion.div
80
+ id={contentId}
81
+ key="conclusion"
82
+ {...expandMotion}
83
+ className="overflow-hidden"
84
+ >
85
+ <div className="border-t border-outline-variant px-4 py-3">
86
+ <Markdown content={conclusion} size="small" className="text-on-surface-variant" />
87
+ </div>
88
+ </motion.div>
89
+ ) : null}
90
+ </AnimatePresence>
91
+ </div>
92
+ </div>
93
+ );
94
+ }
95
+
96
+ export { InlineBranchCard };
97
+ export type { InlineBranchCardProps };
@@ -0,0 +1,182 @@
1
+ // SPDX-License-Identifier: Apache-2.0 AND MIT
2
+ // Derived from SpaceUI (packages/aphrody-spaceui/packages/ai/src/InlineWorkerCard.tsx), MIT, rebuilt on Material Design 3.
3
+
4
+ import { useId, useMemo, useState } from "react";
5
+ import { clsx } from "clsx";
6
+ import { AnimatePresence, motion } from "motion/react";
7
+ import { Button, Icon } from "@aphrody/m3-primitives";
8
+ import { ToolCall } from "./ToolCall";
9
+ import { Markdown } from "./Markdown";
10
+ import type { TranscriptStep } from "./types";
11
+ import { pairTranscriptSteps } from "./types";
12
+ import { CircularIndicator, StatusChip, type ChipTone, expandMotion, focusRing } from "./internal";
13
+
14
+ /** Collapse runs of 3+ spaces to 2, preventing Markdown 4-space code blocks. */
15
+ function stripExcessWhitespace(text: string): string {
16
+ return text.replace(/ {3,}/g, " ");
17
+ }
18
+
19
+ interface InlineWorkerCardProps {
20
+ title: string;
21
+ status: string;
22
+ toolCallCount: number;
23
+ liveStatus?: string | null;
24
+ transcript: TranscriptStep[];
25
+ isTranscriptLoading?: boolean;
26
+ onCopyLogs?: () => void;
27
+ onCancel?: () => void;
28
+ className?: string;
29
+ /** Start expanded (uncontrolled). */
30
+ defaultExpanded?: boolean;
31
+ }
32
+
33
+ function statusTone(status: string): ChipTone {
34
+ switch (status) {
35
+ case "running":
36
+ return "primary";
37
+ case "completed":
38
+ return "success";
39
+ case "failed":
40
+ case "error":
41
+ case "cancelled":
42
+ return "error";
43
+ default:
44
+ return "neutral";
45
+ }
46
+ }
47
+
48
+ /** A worker run as an M3 filled card: header, status chip, expandable transcript. */
49
+ function InlineWorkerCard({
50
+ title,
51
+ status,
52
+ toolCallCount,
53
+ liveStatus,
54
+ transcript,
55
+ isTranscriptLoading,
56
+ onCopyLogs,
57
+ onCancel,
58
+ className,
59
+ defaultExpanded = false,
60
+ }: InlineWorkerCardProps) {
61
+ const [expanded, setExpanded] = useState(defaultExpanded);
62
+ const contentId = useId();
63
+
64
+ const items = useMemo(() => pairTranscriptSteps(transcript), [transcript]);
65
+
66
+ const isRunning = status === "running";
67
+ const isDone = status === "completed";
68
+ const tone = statusTone(status);
69
+
70
+ return (
71
+ <div className={clsx("group flex min-w-0 flex-col items-start", className)}>
72
+ <div className="min-w-0 max-w-full overflow-hidden rounded-medium bg-surface-container text-on-surface">
73
+ <button
74
+ type="button"
75
+ onClick={() => setExpanded((v) => !v)}
76
+ aria-expanded={expanded}
77
+ aria-controls={contentId}
78
+ className={clsx(
79
+ "flex w-full items-start gap-3 rounded-medium px-4 py-3 text-left transition-colors duration-200 ease-standard",
80
+ "hover:bg-on-surface/8 active:bg-on-surface/10",
81
+ focusRing,
82
+ )}
83
+ >
84
+ <div className="flex size-8 shrink-0 items-center justify-center">
85
+ {isRunning ? (
86
+ <CircularIndicator size={20} label="Worker running" />
87
+ ) : isDone ? (
88
+ <div className="flex size-8 items-center justify-center rounded-full bg-success-container text-on-success-container">
89
+ <Icon name="check_circle" size={18} filled />
90
+ </div>
91
+ ) : tone === "error" ? (
92
+ <div className="flex size-8 items-center justify-center rounded-full bg-error-container text-on-error-container">
93
+ <Icon name="error" size={18} filled />
94
+ </div>
95
+ ) : (
96
+ <div className="flex size-8 items-center justify-center rounded-full bg-surface-container-highest text-on-surface-variant">
97
+ <Icon name="build" size={18} />
98
+ </div>
99
+ )}
100
+ </div>
101
+
102
+ <div className="min-w-0 flex-1">
103
+ <div className="flex items-center gap-2">
104
+ <div className="line-clamp-2 min-w-0 flex-1 text-title-small text-on-surface">
105
+ {title}
106
+ </div>
107
+ <StatusChip tone={tone}>{status}</StatusChip>
108
+ </div>
109
+ <div className="mt-1 flex items-center gap-2 text-body-small text-on-surface-variant">
110
+ <span>{toolCallCount} tool calls</span>
111
+ {liveStatus ? <span className="truncate text-outline">{liveStatus}</span> : null}
112
+ </div>
113
+ </div>
114
+
115
+ <Icon
116
+ name="expand_more"
117
+ size={20}
118
+ className={clsx(
119
+ "mt-1.5 shrink-0 text-on-surface-variant transition-transform duration-300 ease-emphasized",
120
+ expanded ? "rotate-180" : "",
121
+ )}
122
+ />
123
+ </button>
124
+
125
+ <AnimatePresence initial={false}>
126
+ {expanded ? (
127
+ <motion.div
128
+ id={contentId}
129
+ key="transcript"
130
+ {...expandMotion}
131
+ className="overflow-hidden"
132
+ >
133
+ <div className="flex flex-col gap-2 border-t border-outline-variant px-4 py-3">
134
+ {isTranscriptLoading ? (
135
+ <div className="flex items-center gap-2 text-body-small text-on-surface-variant">
136
+ <CircularIndicator size={16} label="Loading transcript" />
137
+ Loading worker transcript...
138
+ </div>
139
+ ) : items.length > 0 ? (
140
+ items.map((item) =>
141
+ item.kind === "tool" ? (
142
+ <ToolCall key={item.pair.id} pair={item.pair} />
143
+ ) : (
144
+ <Markdown
145
+ key={`text-${item.text}`}
146
+ content={stripExcessWhitespace(item.text)}
147
+ size="small"
148
+ className="text-on-surface-variant"
149
+ />
150
+ ),
151
+ )
152
+ ) : (
153
+ <div className="text-body-small text-on-surface-variant">No tool calls yet.</div>
154
+ )}
155
+ </div>
156
+ </motion.div>
157
+ ) : null}
158
+ </AnimatePresence>
159
+ </div>
160
+
161
+ {(onCopyLogs || (onCancel && isRunning)) && (
162
+ <div className="mt-1 flex gap-1 opacity-0 transition-opacity duration-200 ease-standard group-focus-within:opacity-100 group-hover:opacity-100">
163
+ {onCopyLogs && (
164
+ <Button variant="text" size="xs" onClick={onCopyLogs} title="Copy logs">
165
+ <Icon name="content_copy" size={18} />
166
+ Copy
167
+ </Button>
168
+ )}
169
+ {onCancel && isRunning && (
170
+ <Button variant="text" size="xs" onClick={onCancel} title="Cancel">
171
+ <Icon name="stop" size={18} />
172
+ Cancel
173
+ </Button>
174
+ )}
175
+ </div>
176
+ )}
177
+ </div>
178
+ );
179
+ }
180
+
181
+ export { InlineWorkerCard };
182
+ export type { InlineWorkerCardProps };