@kognitivedev/ui 0.2.11 → 0.2.13
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.
- package/CHANGELOG.md +18 -0
- package/README.md +48 -10
- package/dist/components/message.js +26 -7
- package/package.json +3 -3
- package/src/components/message.tsx +80 -25
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @kognitivedev/ui
|
|
2
2
|
|
|
3
|
+
## 0.2.13
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- release
|
|
8
|
+
|
|
9
|
+
- Updated dependencies []:
|
|
10
|
+
- @kognitivedev/shared@0.2.13
|
|
11
|
+
|
|
12
|
+
## 0.2.12
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- release
|
|
17
|
+
|
|
18
|
+
- Updated dependencies []:
|
|
19
|
+
- @kognitivedev/shared@0.2.12
|
|
20
|
+
|
|
3
21
|
## 0.2.11
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -10,27 +10,65 @@ bun add @kognitivedev/ui
|
|
|
10
10
|
|
|
11
11
|
**Peer dependencies:** `react`, `ai`, `@ai-sdk/react`, `zod`
|
|
12
12
|
|
|
13
|
-
## Quick Start
|
|
13
|
+
## Quick Start: End-to-End
|
|
14
|
+
|
|
15
|
+
### 1. Define an Agent (Server)
|
|
16
|
+
|
|
17
|
+
```tsx
|
|
18
|
+
// lib/agents.ts
|
|
19
|
+
import { createAgent } from "@kognitivedev/agents";
|
|
20
|
+
import { createTool } from "@kognitivedev/tools";
|
|
21
|
+
import { openai } from "@ai-sdk/openai";
|
|
22
|
+
import { z } from "zod";
|
|
23
|
+
|
|
24
|
+
const weatherTool = createTool({
|
|
25
|
+
id: "weather-lookup",
|
|
26
|
+
description: "Get current weather for a city",
|
|
27
|
+
inputSchema: z.object({ city: z.string() }),
|
|
28
|
+
execute: async (input) => ({
|
|
29
|
+
city: input.city, temperature: 22, condition: "Sunny",
|
|
30
|
+
}),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const assistant = createAgent({
|
|
34
|
+
name: "assistant",
|
|
35
|
+
instructions: "You are a helpful assistant. Use tools when needed.",
|
|
36
|
+
model: openai("gpt-4o-mini"),
|
|
37
|
+
tools: [weatherTool],
|
|
38
|
+
maxSteps: 5,
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 2. Create an API Route (Server)
|
|
43
|
+
|
|
44
|
+
```tsx
|
|
45
|
+
// app/api/chat/route.ts
|
|
46
|
+
import { assistant } from "@/lib/agents";
|
|
47
|
+
|
|
48
|
+
export async function POST(req: Request) {
|
|
49
|
+
const { messages, resourceId } = await req.json();
|
|
50
|
+
const result = await assistant.stream({ messages, resourceId: resourceId ?? {} });
|
|
51
|
+
return result.toUIMessageStreamResponse();
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 3. Build the Chat UI (Client)
|
|
14
56
|
|
|
15
57
|
```tsx
|
|
58
|
+
// app/page.tsx
|
|
59
|
+
"use client";
|
|
16
60
|
import { KognitiveUI } from "@kognitivedev/ui";
|
|
17
61
|
|
|
18
|
-
function
|
|
62
|
+
export default function ChatPage() {
|
|
19
63
|
return (
|
|
20
|
-
<KognitiveUI
|
|
64
|
+
<KognitiveUI api="/api/chat" agentName="assistant">
|
|
21
65
|
<KognitiveUI.Thread />
|
|
22
66
|
</KognitiveUI>
|
|
23
67
|
);
|
|
24
68
|
}
|
|
25
69
|
```
|
|
26
70
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
```tsx
|
|
30
|
-
<KognitiveUI api="/api/chat" agentName="assistant" resourceId={{ userId: "user-1" }}>
|
|
31
|
-
<KognitiveUI.Thread allowAttachments />
|
|
32
|
-
</KognitiveUI>
|
|
33
|
-
```
|
|
71
|
+
That's a working chat. `api` points to your route. `agentName` is sent in the request body.
|
|
34
72
|
|
|
35
73
|
## Features
|
|
36
74
|
|
|
@@ -9,19 +9,38 @@ const composer_1 = require("../primitives/composer");
|
|
|
9
9
|
const markdown_content_1 = require("./markdown-content");
|
|
10
10
|
const tool_invocation_1 = require("./tool-invocation");
|
|
11
11
|
const use_message_1 = require("../primitives/message/use-message");
|
|
12
|
+
// ── Inline SVG Icons (no external dependency) ──
|
|
13
|
+
function CopyIcon({ className }) {
|
|
14
|
+
return ((0, jsx_runtime_1.jsxs)("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }), (0, jsx_runtime_1.jsx)("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })] }));
|
|
15
|
+
}
|
|
16
|
+
function CheckIcon({ className }) {
|
|
17
|
+
return ((0, jsx_runtime_1.jsx)("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: (0, jsx_runtime_1.jsx)("polyline", { points: "20 6 9 17 4 12" }) }));
|
|
18
|
+
}
|
|
19
|
+
function PencilIcon({ className }) {
|
|
20
|
+
return ((0, jsx_runtime_1.jsxs)("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)("path", { d: "M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" }), (0, jsx_runtime_1.jsx)("path", { d: "m15 5 4 4" })] }));
|
|
21
|
+
}
|
|
22
|
+
function ThumbUpIcon({ className }) {
|
|
23
|
+
return ((0, jsx_runtime_1.jsxs)("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)("path", { d: "M7 10v12" }), (0, jsx_runtime_1.jsx)("path", { d: "M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z" })] }));
|
|
24
|
+
}
|
|
25
|
+
function ThumbDownIcon({ className }) {
|
|
26
|
+
return ((0, jsx_runtime_1.jsxs)("svg", { className: className, viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [(0, jsx_runtime_1.jsx)("path", { d: "M17 14V2" }), (0, jsx_runtime_1.jsx)("path", { d: "M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z" })] }));
|
|
27
|
+
}
|
|
28
|
+
const iconBtnClass = "inline-flex h-7 w-7 items-center justify-center rounded-md transition-colors hover:bg-zinc-100 dark:hover:bg-zinc-800";
|
|
12
29
|
function MessageInner({ className }) {
|
|
13
30
|
const { message, isEditing } = (0, use_message_1.useMessage)();
|
|
14
31
|
const isUser = message.role === "user";
|
|
15
|
-
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, cn_1.cn)("flex gap-3 py-4", isUser ? "flex-row-reverse" : "flex-row", className), children: [(0, jsx_runtime_1.jsx)("div", { className: (0, cn_1.cn)("flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-sm", isUser
|
|
32
|
+
return ((0, jsx_runtime_1.jsxs)("div", { className: (0, cn_1.cn)("group relative flex gap-3 py-4", isUser ? "flex-row-reverse" : "flex-row", className), children: [(0, jsx_runtime_1.jsx)("div", { className: (0, cn_1.cn)("flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-sm font-medium", isUser
|
|
16
33
|
? "bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400"
|
|
17
|
-
: "bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400"), children: isUser ? "U" : "A" }), (0, jsx_runtime_1.jsxs)("div", { className: (0, cn_1.cn)("flex-1
|
|
34
|
+
: "bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400"), children: isUser ? "U" : "A" }), (0, jsx_runtime_1.jsxs)("div", { className: (0, cn_1.cn)("flex-1 min-w-0", isUser ? "text-right" : "text-left"), children: [isEditing ? ((0, jsx_runtime_1.jsx)(composer_1.ComposerPrimitive.EditRoot, { className: "space-y-2", children: ({ value, setValue, submit, cancel }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("textarea", { value: value, onChange: (e) => setValue(e.target.value), className: "w-full resize-none rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 dark:border-zinc-600 dark:bg-zinc-900 dark:text-zinc-100", rows: 3, autoFocus: true }), (0, jsx_runtime_1.jsxs)("div", { className: "flex gap-2", children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: submit, className: "rounded-lg bg-blue-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-blue-700", children: "Save & Send" }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: cancel, className: "rounded-lg border border-zinc-300 px-3 py-1.5 text-xs text-zinc-600 hover:bg-zinc-100 dark:border-zinc-600 dark:text-zinc-400 dark:hover:bg-zinc-800", children: "Cancel" })] })] })) })) : ((0, jsx_runtime_1.jsx)(message_1.MessagePrimitive.Content, { components: {
|
|
18
35
|
Text: ({ text }) => (0, jsx_runtime_1.jsx)(markdown_content_1.MarkdownContent, { text: text }),
|
|
19
36
|
ToolInvocation: (props) => (0, jsx_runtime_1.jsx)(tool_invocation_1.ToolInvocation, Object.assign({}, props)),
|
|
20
|
-
} })), !isEditing && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(message_1.MessagePrimitive.Role, { match: "user", children: (0, jsx_runtime_1.jsx)(action_bar_1.ActionBarPrimitive.Root, { className: "flex gap-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
? "
|
|
24
|
-
: "text-zinc-400 hover:
|
|
37
|
+
} })), !isEditing && ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(message_1.MessagePrimitive.Role, { match: "user", children: (0, jsx_runtime_1.jsx)(action_bar_1.ActionBarPrimitive.Root, { className: "flex gap-0.5 pt-1 opacity-0 transition-opacity group-hover:opacity-100", children: (0, jsx_runtime_1.jsx)(action_bar_1.ActionBarPrimitive.Edit, { className: (0, cn_1.cn)(iconBtnClass, "text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200"), children: (0, jsx_runtime_1.jsx)(PencilIcon, { className: "h-3.5 w-3.5" }) }) }) }), (0, jsx_runtime_1.jsx)(message_1.MessagePrimitive.Role, { match: "assistant", children: (0, jsx_runtime_1.jsxs)(action_bar_1.ActionBarPrimitive.Root, { className: "flex gap-0.5 pt-1 opacity-0 transition-opacity group-hover:opacity-100", children: [(0, jsx_runtime_1.jsx)(action_bar_1.ActionBarPrimitive.Copy, { className: (0, cn_1.cn)(iconBtnClass, "text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200"), children: (copied) => copied
|
|
38
|
+
? (0, jsx_runtime_1.jsx)(CheckIcon, { className: "h-3.5 w-3.5 text-emerald-500" })
|
|
39
|
+
: (0, jsx_runtime_1.jsx)(CopyIcon, { className: "h-3.5 w-3.5" }) }), (0, jsx_runtime_1.jsx)(action_bar_1.ActionBarPrimitive.Feedback, { className: "flex gap-0.5", children: ({ selected, onPositive, onNegative }) => ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)("button", { type: "button", onClick: onPositive, className: (0, cn_1.cn)(iconBtnClass, selected === "positive"
|
|
40
|
+
? "text-emerald-500 bg-emerald-50 dark:bg-emerald-900/20"
|
|
41
|
+
: "text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200"), "aria-label": "Good response", children: (0, jsx_runtime_1.jsx)(ThumbUpIcon, { className: "h-3.5 w-3.5" }) }), (0, jsx_runtime_1.jsx)("button", { type: "button", onClick: onNegative, className: (0, cn_1.cn)(iconBtnClass, selected === "negative"
|
|
42
|
+
? "text-red-500 bg-red-50 dark:bg-red-900/20"
|
|
43
|
+
: "text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200"), "aria-label": "Bad response", children: (0, jsx_runtime_1.jsx)(ThumbDownIcon, { className: "h-3.5 w-3.5" }) })] })) })] }) })] }))] })] }));
|
|
25
44
|
}
|
|
26
45
|
function Message({ message, index, className }) {
|
|
27
46
|
return ((0, jsx_runtime_1.jsx)(message_1.MessagePrimitive.Root, { message: message, index: index, children: (0, jsx_runtime_1.jsx)(MessageInner, { className: className }) }));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kognitivedev/ui",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.13",
|
|
4
4
|
"main": "dist/index.js",
|
|
5
5
|
"types": "dist/index.d.ts",
|
|
6
6
|
"publishConfig": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
"test": "vitest run"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@kognitivedev/shared": "^0.2.
|
|
16
|
+
"@kognitivedev/shared": "^0.2.13",
|
|
17
17
|
"clsx": "^2.1.0",
|
|
18
18
|
"tailwind-merge": "^3.0.0"
|
|
19
19
|
},
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"zod": ">=3.23.0"
|
|
25
25
|
},
|
|
26
26
|
"optionalDependencies": {
|
|
27
|
-
"@kognitivedev/tools": "^0.2.
|
|
27
|
+
"@kognitivedev/tools": "^0.2.13"
|
|
28
28
|
},
|
|
29
29
|
"devDependencies": {
|
|
30
30
|
"typescript": "^5.0.0",
|
|
@@ -14,6 +14,54 @@ export interface MessageProps {
|
|
|
14
14
|
className?: string;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
// ── Inline SVG Icons (no external dependency) ──
|
|
18
|
+
|
|
19
|
+
function CopyIcon({ className }: { className?: string }) {
|
|
20
|
+
return (
|
|
21
|
+
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
22
|
+
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
|
|
23
|
+
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
|
24
|
+
</svg>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function CheckIcon({ className }: { className?: string }) {
|
|
29
|
+
return (
|
|
30
|
+
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
31
|
+
<polyline points="20 6 9 17 4 12" />
|
|
32
|
+
</svg>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function PencilIcon({ className }: { className?: string }) {
|
|
37
|
+
return (
|
|
38
|
+
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
39
|
+
<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z" />
|
|
40
|
+
<path d="m15 5 4 4" />
|
|
41
|
+
</svg>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function ThumbUpIcon({ className }: { className?: string }) {
|
|
46
|
+
return (
|
|
47
|
+
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
48
|
+
<path d="M7 10v12" />
|
|
49
|
+
<path d="M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z" />
|
|
50
|
+
</svg>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function ThumbDownIcon({ className }: { className?: string }) {
|
|
55
|
+
return (
|
|
56
|
+
<svg className={className} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
|
|
57
|
+
<path d="M17 14V2" />
|
|
58
|
+
<path d="M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z" />
|
|
59
|
+
</svg>
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const iconBtnClass = "inline-flex h-7 w-7 items-center justify-center rounded-md transition-colors hover:bg-zinc-100 dark:hover:bg-zinc-800";
|
|
64
|
+
|
|
17
65
|
function MessageInner({ className }: { className?: string }) {
|
|
18
66
|
const { message, isEditing } = useMessage();
|
|
19
67
|
const isUser = message.role === "user";
|
|
@@ -21,7 +69,7 @@ function MessageInner({ className }: { className?: string }) {
|
|
|
21
69
|
return (
|
|
22
70
|
<div
|
|
23
71
|
className={cn(
|
|
24
|
-
"flex gap-3 py-4",
|
|
72
|
+
"group relative flex gap-3 py-4",
|
|
25
73
|
isUser ? "flex-row-reverse" : "flex-row",
|
|
26
74
|
className,
|
|
27
75
|
)}
|
|
@@ -29,7 +77,7 @@ function MessageInner({ className }: { className?: string }) {
|
|
|
29
77
|
{/* Avatar */}
|
|
30
78
|
<div
|
|
31
79
|
className={cn(
|
|
32
|
-
"flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-sm",
|
|
80
|
+
"flex h-8 w-8 shrink-0 items-center justify-center rounded-full text-sm font-medium",
|
|
33
81
|
isUser
|
|
34
82
|
? "bg-blue-100 text-blue-600 dark:bg-blue-900/30 dark:text-blue-400"
|
|
35
83
|
: "bg-zinc-100 text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400",
|
|
@@ -39,8 +87,7 @@ function MessageInner({ className }: { className?: string }) {
|
|
|
39
87
|
</div>
|
|
40
88
|
|
|
41
89
|
{/* Content */}
|
|
42
|
-
<div className={cn("flex-1
|
|
43
|
-
{/* Edit composer (replaces content when editing) */}
|
|
90
|
+
<div className={cn("flex-1 min-w-0", isUser ? "text-right" : "text-left")}>
|
|
44
91
|
{isEditing ? (
|
|
45
92
|
<ComposerPrimitive.EditRoot className="space-y-2">
|
|
46
93
|
{({ value, setValue, submit, cancel }) => (
|
|
@@ -48,21 +95,22 @@ function MessageInner({ className }: { className?: string }) {
|
|
|
48
95
|
<textarea
|
|
49
96
|
value={value}
|
|
50
97
|
onChange={(e) => setValue(e.target.value)}
|
|
51
|
-
className="w-full resize-none rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 dark:border-zinc-600 dark:bg-zinc-900"
|
|
98
|
+
className="w-full resize-none rounded-lg border border-zinc-300 bg-white px-3 py-2 text-sm outline-none focus:border-blue-500 focus:ring-1 focus:ring-blue-500 dark:border-zinc-600 dark:bg-zinc-900 dark:text-zinc-100"
|
|
52
99
|
rows={3}
|
|
100
|
+
autoFocus
|
|
53
101
|
/>
|
|
54
102
|
<div className="flex gap-2">
|
|
55
103
|
<button
|
|
56
104
|
type="button"
|
|
57
105
|
onClick={submit}
|
|
58
|
-
className="rounded-lg bg-blue-600 px-3 py-1 text-xs font-medium text-white hover:bg-blue-700"
|
|
106
|
+
className="rounded-lg bg-blue-600 px-3 py-1.5 text-xs font-medium text-white hover:bg-blue-700"
|
|
59
107
|
>
|
|
60
|
-
Save & Send
|
|
108
|
+
Save & Send
|
|
61
109
|
</button>
|
|
62
110
|
<button
|
|
63
111
|
type="button"
|
|
64
112
|
onClick={cancel}
|
|
65
|
-
className="rounded-lg border border-zinc-300 px-3 py-1 text-xs text-zinc-600 hover:bg-zinc-100 dark:border-zinc-600 dark:text-zinc-400 dark:hover:bg-zinc-800"
|
|
113
|
+
className="rounded-lg border border-zinc-300 px-3 py-1.5 text-xs text-zinc-600 hover:bg-zinc-100 dark:border-zinc-600 dark:text-zinc-400 dark:hover:bg-zinc-800"
|
|
66
114
|
>
|
|
67
115
|
Cancel
|
|
68
116
|
</button>
|
|
@@ -79,50 +127,57 @@ function MessageInner({ className }: { className?: string }) {
|
|
|
79
127
|
/>
|
|
80
128
|
)}
|
|
81
129
|
|
|
82
|
-
{/* Action bar */}
|
|
130
|
+
{/* Action bar — icon buttons, visible on hover */}
|
|
83
131
|
{!isEditing && (
|
|
84
132
|
<>
|
|
85
|
-
{/* User messages: edit button */}
|
|
86
133
|
<MessagePrimitive.Role match="user">
|
|
87
|
-
<ActionBarPrimitive.Root className="flex gap-
|
|
88
|
-
<ActionBarPrimitive.Edit
|
|
134
|
+
<ActionBarPrimitive.Root className="flex gap-0.5 pt-1 opacity-0 transition-opacity group-hover:opacity-100">
|
|
135
|
+
<ActionBarPrimitive.Edit
|
|
136
|
+
className={cn(iconBtnClass, "text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200")}
|
|
137
|
+
>
|
|
138
|
+
<PencilIcon className="h-3.5 w-3.5" />
|
|
139
|
+
</ActionBarPrimitive.Edit>
|
|
89
140
|
</ActionBarPrimitive.Root>
|
|
90
141
|
</MessagePrimitive.Role>
|
|
91
142
|
|
|
92
|
-
{/* Assistant messages: copy + feedback */}
|
|
93
143
|
<MessagePrimitive.Role match="assistant">
|
|
94
|
-
<ActionBarPrimitive.Root className="flex gap-
|
|
95
|
-
<ActionBarPrimitive.Copy className="
|
|
96
|
-
{(copied: boolean) =>
|
|
144
|
+
<ActionBarPrimitive.Root className="flex gap-0.5 pt-1 opacity-0 transition-opacity group-hover:opacity-100">
|
|
145
|
+
<ActionBarPrimitive.Copy className={cn(iconBtnClass, "text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200")}>
|
|
146
|
+
{(copied: boolean) =>
|
|
147
|
+
copied
|
|
148
|
+
? <CheckIcon className="h-3.5 w-3.5 text-emerald-500" />
|
|
149
|
+
: <CopyIcon className="h-3.5 w-3.5" />
|
|
150
|
+
}
|
|
97
151
|
</ActionBarPrimitive.Copy>
|
|
98
|
-
|
|
152
|
+
|
|
153
|
+
<ActionBarPrimitive.Feedback className="flex gap-0.5">
|
|
99
154
|
{({ selected, onPositive, onNegative }) => (
|
|
100
155
|
<>
|
|
101
156
|
<button
|
|
102
157
|
type="button"
|
|
103
158
|
onClick={onPositive}
|
|
104
159
|
className={cn(
|
|
105
|
-
|
|
160
|
+
iconBtnClass,
|
|
106
161
|
selected === "positive"
|
|
107
|
-
? "
|
|
108
|
-
: "text-zinc-400 hover:
|
|
162
|
+
? "text-emerald-500 bg-emerald-50 dark:bg-emerald-900/20"
|
|
163
|
+
: "text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200",
|
|
109
164
|
)}
|
|
110
165
|
aria-label="Good response"
|
|
111
166
|
>
|
|
112
|
-
|
|
167
|
+
<ThumbUpIcon className="h-3.5 w-3.5" />
|
|
113
168
|
</button>
|
|
114
169
|
<button
|
|
115
170
|
type="button"
|
|
116
171
|
onClick={onNegative}
|
|
117
172
|
className={cn(
|
|
118
|
-
|
|
173
|
+
iconBtnClass,
|
|
119
174
|
selected === "negative"
|
|
120
|
-
? "
|
|
121
|
-
: "text-zinc-400 hover:
|
|
175
|
+
? "text-red-500 bg-red-50 dark:bg-red-900/20"
|
|
176
|
+
: "text-zinc-400 hover:text-zinc-700 dark:hover:text-zinc-200",
|
|
122
177
|
)}
|
|
123
178
|
aria-label="Bad response"
|
|
124
179
|
>
|
|
125
|
-
|
|
180
|
+
<ThumbDownIcon className="h-3.5 w-3.5" />
|
|
126
181
|
</button>
|
|
127
182
|
</>
|
|
128
183
|
)}
|