devnonla-ui 0.2.0 → 0.4.0
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/README.md +3 -3
- package/package.json +2 -1
- package/src/app/App.tsx +4 -4
- package/src/button/Button.tsx +1 -1
- package/src/button/ButtonCopy.tsx +11 -21
- package/src/calendar/Calendar.tsx +1 -1
- package/src/chat/AgentPanel.tsx +3 -3
- package/src/chat/index.ts +35 -51
- package/src/chat/message-ui/ChatError.tsx +2 -4
- package/src/chat/message-ui/ChatInput.tsx +5 -9
- package/src/chat/message-ui/ChatThinking.tsx +2 -3
- package/src/chat/message-ui/ChatUserMessage.tsx +4 -5
- package/src/chat/message-ui/ChatWelcome.tsx +3 -8
- package/src/chat/message-ui/MermaidBlock.tsx +7 -47
- package/src/chat/tool-ui/BackgroundTaskToolUI.tsx +8 -11
- package/src/chat/tool-ui/BackgroundTasksBar.tsx +13 -14
- package/src/chat/tool-ui/CallAgentToolUI.tsx +10 -4
- package/src/chat/tool-ui/ChatToolCall.tsx +11 -17
- package/src/chat/tool-ui/GetCurrentTimeToolUI.tsx +2 -2
- package/src/chat/tool-ui/ReadSkillToolUI.tsx +3 -3
- package/src/chat/tool-ui/RunJsToolUI.tsx +2 -2
- package/src/chat/tool-ui/ToolUiTrailing.tsx +3 -7
- package/src/chat/tool-ui/WebFetchToolUI.tsx +22 -13
- package/src/checkbox/Checkbox.tsx +1 -1
- package/src/codeblock/CodeBlock.tsx +17 -19
- package/src/colorpicker/ColorPicker.tsx +694 -30
- package/src/colorpicker/color.ts +246 -0
- package/src/datepicker/DatePicker.tsx +3 -3
- package/src/desktop/DesktopHeader.tsx +15 -20
- package/src/desktop/DesktopIcon.tsx +1 -1
- package/src/desktop/DesktopWindow.tsx +54 -30
- package/src/form/Form.tsx +1 -1
- package/src/form/FormItem.tsx +2 -2
- package/src/form/index.ts +23 -24
- package/src/form/variants/FieldColor.tsx +16 -51
- package/src/index.ts +167 -196
- package/src/input/Input.tsx +14 -12
- package/src/input/SearchInput.tsx +5 -2
- package/src/lib/sizes.ts +1 -1
- package/src/modal/Modal.tsx +3 -0
- package/src/pagination/Pagination.tsx +528 -51
- package/src/scroll/OverlayScroll.tsx +58 -37
- package/src/select/Select.tsx +2 -2
- package/src/spin/Spin.tsx +14 -17
- package/src/splitter/Splitter.tsx +330 -0
- package/src/splitter/sizes.ts +67 -0
- package/src/styles.css +28 -3
- package/src/switch/Switch.tsx +1 -1
- package/src/table/Table.tsx +43 -18
- package/src/timepicker/TimePicker.tsx +2 -2
package/README.md
CHANGED
|
@@ -108,14 +108,14 @@ import { DesktopStage, DesktopHeader, MeadowDesktop, DesktopWindow, MeadowShell,
|
|
|
108
108
|
|
|
109
109
|
<DesktopStage>
|
|
110
110
|
<MeadowDesktop>{icons}</MeadowDesktop>
|
|
111
|
-
<DesktopHeader
|
|
112
|
-
<DesktopWindow title="Tools" expanded={false} onClose={close} onToggleExpand={toggle}>
|
|
111
|
+
<DesktopHeader left={nav} right={apps} />
|
|
112
|
+
<DesktopWindow title="Tools" left={nav} right={actions} expanded={false} onClose={close} onToggleExpand={toggle}>
|
|
113
113
|
{children}
|
|
114
114
|
</DesktopWindow>
|
|
115
115
|
</DesktopStage>
|
|
116
116
|
```
|
|
117
117
|
|
|
118
|
-
`MeadowShell` is the login/setup backdrop. `Menu` is the glass picker (trigger + items + hover action) used by AgentsMenu. Wallpaper defaults to the bundled meadow; pass `src` on `MeadowWallpaper` to swap.
|
|
118
|
+
`MeadowShell` is the login/setup backdrop. `DesktopWindow` chrome is inline: traffic lights, `left`, a flex middle (title — double-click to expand), then `right`. A child can fill the same slots with `WindowHeader`. `Menu` is the glass picker (trigger + items + hover action) used by AgentsMenu. Wallpaper defaults to the bundled meadow; pass `src` on `MeadowWallpaper` to swap.
|
|
119
119
|
|
|
120
120
|
# Chat
|
|
121
121
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devnonla-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "NonlaUI — React controls for Nonla Agents",
|
|
6
6
|
"author": "devlangla <devlangla0x01@gmail.com>",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"clsx": "^2.1.1",
|
|
50
50
|
"date-fns": "^4.4.0",
|
|
51
51
|
"highlight.js": "^11.12.0",
|
|
52
|
+
"lucide-react": "^1.45.0",
|
|
52
53
|
"mermaid": "^11.14.0",
|
|
53
54
|
"react-day-picker": "^10.0.1",
|
|
54
55
|
"react-markdown": "^10.1.0",
|
package/src/app/App.tsx
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { type ReactNode, useLayoutEffect, useMemo, useState } from "react";
|
|
2
1
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
3
|
-
import {
|
|
2
|
+
import { type ReactNode, useLayoutEffect, useMemo, useState } from "react";
|
|
3
|
+
import { type ControlSize, ControlSizeContext, normalizeSize } from "../lib/sizes";
|
|
4
|
+
import { MessageHolder, message } from "../message/message";
|
|
4
5
|
import { ConfirmHolder, Modal } from "../modal/Modal";
|
|
5
|
-
import { ControlSizeContext, normalizeSize, type ControlSize } from "../lib/sizes";
|
|
6
6
|
import { applyNonlaTheme, type NonlaThemeConfig } from "../theme";
|
|
7
|
-
import { AppContext, useAppConfig, usePopupContainer, useToken
|
|
7
|
+
import { AppContext, type NonlaAppConfig, useAppConfig, usePopupContainer, useToken } from "./context";
|
|
8
8
|
|
|
9
9
|
export type { NonlaAppConfig };
|
|
10
10
|
export { useAppConfig, usePopupContainer, useToken };
|
package/src/button/Button.tsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type AnchorHTMLAttributes, type ButtonHTMLAttributes, type CSSProperties, type ForwardRefExoticComponent, type MouseEvent, type ReactNode, type RefAttributes,
|
|
1
|
+
import { type AnchorHTMLAttributes, type ButtonHTMLAttributes, type CSSProperties, createContext, type ForwardRefExoticComponent, forwardRef, type MouseEvent, type ReactNode, type RefAttributes, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { cn } from "../lib/cn";
|
|
3
3
|
import type { ControlSize } from "../lib/sizes";
|
|
4
4
|
import { controlHeightVar, controlRadiusVar, getSizeTokens, useControlSize } from "../lib/sizes";
|
|
@@ -1,32 +1,21 @@
|
|
|
1
|
+
import { Check, Copy } from "lucide-react";
|
|
1
2
|
import { type ComponentProps, useEffect, useState } from "react";
|
|
2
3
|
import { cn } from "../lib/cn";
|
|
4
|
+
import { type ControlSize, getSizeTokens, useControlSize } from "../lib/sizes";
|
|
3
5
|
|
|
4
|
-
export type ButtonCopyProps = Omit<ComponentProps<"button">, "children"> & {
|
|
6
|
+
export type ButtonCopyProps = Omit<ComponentProps<"button">, "children" | "size"> & {
|
|
5
7
|
text?: string;
|
|
6
8
|
getText?: () => string;
|
|
7
9
|
/** Idle tooltip / aria-label. Copied state uses "Copied". */
|
|
8
10
|
label?: string;
|
|
11
|
+
size?: ControlSize;
|
|
9
12
|
};
|
|
10
13
|
|
|
11
|
-
function
|
|
12
|
-
return (
|
|
13
|
-
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" aria-hidden>
|
|
14
|
-
<rect x="8" y="8" width="14" height="14" rx="2" stroke="currentColor" strokeWidth="2" />
|
|
15
|
-
<path d="M4 16V4c0-1.1.9-2 2-2h10" stroke="currentColor" strokeWidth="2" strokeLinecap="round" />
|
|
16
|
-
</svg>
|
|
17
|
-
);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function CheckIcon({ size = 14 }: { size?: number }) {
|
|
21
|
-
return (
|
|
22
|
-
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" aria-hidden>
|
|
23
|
-
<path d="M5 13l4 4L19 7" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" />
|
|
24
|
-
</svg>
|
|
25
|
-
);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function ButtonCopy({ text, getText, label = "Copy", className, onClick, ...props }: ButtonCopyProps) {
|
|
14
|
+
export function ButtonCopy({ text, getText, label = "Copy", className, onClick, size, ...props }: ButtonCopyProps) {
|
|
29
15
|
const [copied, setCopied] = useState(false);
|
|
16
|
+
const tok = getSizeTokens(useControlSize(size));
|
|
17
|
+
const icon = tok.icon;
|
|
18
|
+
const box = icon + 14;
|
|
30
19
|
|
|
31
20
|
useEffect(() => {
|
|
32
21
|
if (!copied) return;
|
|
@@ -40,7 +29,8 @@ export function ButtonCopy({ text, getText, label = "Copy", className, onClick,
|
|
|
40
29
|
{...props}
|
|
41
30
|
title={copied ? "Copied" : label}
|
|
42
31
|
aria-label={copied ? "Copied" : label}
|
|
43
|
-
className={cn("inline-flex
|
|
32
|
+
className={cn("inline-flex cursor-pointer shrink-0 items-center justify-center text-muted-foreground transition-colors duration-200 hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring", className)}
|
|
33
|
+
style={{ width: box, height: box }}
|
|
44
34
|
onClick={async (e) => {
|
|
45
35
|
onClick?.(e);
|
|
46
36
|
if (e.defaultPrevented) return;
|
|
@@ -54,7 +44,7 @@ export function ButtonCopy({ text, getText, label = "Copy", className, onClick,
|
|
|
54
44
|
}
|
|
55
45
|
}}
|
|
56
46
|
>
|
|
57
|
-
{copied ? <
|
|
47
|
+
{copied ? <Check size={icon} aria-hidden /> : <Copy size={icon} aria-hidden />}
|
|
58
48
|
</button>
|
|
59
49
|
);
|
|
60
50
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type CSSProperties, useEffect, useRef } from "react";
|
|
2
|
-
import {
|
|
2
|
+
import { type DayButtonProps, DayPicker, type DayPickerProps, getDefaultClassNames } from "react-day-picker";
|
|
3
3
|
import { cn } from "../lib/cn";
|
|
4
4
|
|
|
5
5
|
export type CalendarProps = DayPickerProps;
|
package/src/chat/AgentPanel.tsx
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { type ReactNode, useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
2
2
|
import { cn } from "../lib/cn";
|
|
3
3
|
import { OverlayScroll, type OverlayScrollVisibility } from "../scroll/OverlayScroll";
|
|
4
|
-
import
|
|
4
|
+
import { Shimmer } from "../shimmer/Shimmer";
|
|
5
5
|
import { parseBgTaskRef } from "./common/bgTasks";
|
|
6
|
+
import type { AgentMessage, AgentPanelEndpoint, AgentToolAction, AgentToolHook } from "./common/types";
|
|
6
7
|
import { useAgentStream } from "./common/useAgentStream";
|
|
7
8
|
import { formatToolName } from "./common/utils";
|
|
8
9
|
import { ChatAgentMessage } from "./message-ui/ChatAgentMessage";
|
|
@@ -14,8 +15,7 @@ import { ChatUserMessage } from "./message-ui/ChatUserMessage";
|
|
|
14
15
|
import { ChatWelcome } from "./message-ui/ChatWelcome";
|
|
15
16
|
import { BackgroundTaskToolUI } from "./tool-ui/BackgroundTaskToolUI";
|
|
16
17
|
import { ChatToolCall } from "./tool-ui/ChatToolCall";
|
|
17
|
-
import {
|
|
18
|
-
import { Shimmer } from "../shimmer/Shimmer";
|
|
18
|
+
import { type AgentToolUI, resolveToolUI } from "./tool-ui/registry";
|
|
19
19
|
|
|
20
20
|
export type AgentPanelProps = {
|
|
21
21
|
/** POST URL, or a function that returns an SSE `Response`. */
|
package/src/chat/index.ts
CHANGED
|
@@ -1,58 +1,42 @@
|
|
|
1
|
-
export {
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
export {
|
|
5
|
-
export type {
|
|
6
|
-
|
|
7
|
-
export {
|
|
1
|
+
export type { AgentPanelProps } from "./AgentPanel";
|
|
2
|
+
export { AgentPanel } from "./AgentPanel";
|
|
3
|
+
export type { ChatBgTask, ChatBgTaskStatus } from "./common/bgTasks";
|
|
4
|
+
export { formatBgElapsed, parseBgTaskRef } from "./common/bgTasks";
|
|
5
|
+
export type { AgentSseCallbacks, AgentSseEvent, ParseSseResult } from "./common/sse";
|
|
6
|
+
export { normalizeSseEvent, parseSseStream } from "./common/sse";
|
|
7
|
+
export type { AgentHistoryMessage, AgentMessage, AgentMessageRole, AgentPanelEndpoint, AgentStreamRequest, AgentToolAction, AgentToolCallEvent, AgentToolHook, AgentToolNameMatch, AgentToolResultEvent } from "./common/types";
|
|
8
|
+
export type { UseAgentStreamOptions } from "./common/useAgentStream";
|
|
9
|
+
export { buildAgentHistory, useAgentStream } from "./common/useAgentStream";
|
|
10
|
+
export { formatToolName, isCallAgentToolName, matchesToolHook, matchesToolName, parseCallAgentToolTargetId, prettyJson } from "./common/utils";
|
|
8
11
|
export type { ChatAgentMessageProps } from "./message-ui/ChatAgentMessage";
|
|
9
|
-
|
|
10
|
-
export { ChatError } from "./message-ui/ChatError";
|
|
12
|
+
export { ChatAgentMessage } from "./message-ui/ChatAgentMessage";
|
|
11
13
|
export type { ChatErrorProps } from "./message-ui/ChatError";
|
|
12
|
-
|
|
13
|
-
export {
|
|
14
|
-
export
|
|
15
|
-
|
|
16
|
-
export {
|
|
17
|
-
export
|
|
18
|
-
export type {
|
|
19
|
-
export {
|
|
14
|
+
export { ChatError } from "./message-ui/ChatError";
|
|
15
|
+
export type { ChatInputProps } from "./message-ui/ChatInput";
|
|
16
|
+
export { ChatInput } from "./message-ui/ChatInput";
|
|
17
|
+
export type { ChatMarkdownProps, ChatMarkdownStreamState } from "./message-ui/ChatMarkdown";
|
|
18
|
+
export { ChatMarkdown, chatBodyClass, chatMarkdownClass, chatMarkdownComponents, createChatMarkdownComponents } from "./message-ui/ChatMarkdown";
|
|
19
|
+
export { ChatSpinner } from "./message-ui/ChatSpinner";
|
|
20
|
+
export type { ChatThinkingProps } from "./message-ui/ChatThinking";
|
|
21
|
+
export { ChatThinking } from "./message-ui/ChatThinking";
|
|
22
|
+
export type { ChatUserMessageProps } from "./message-ui/ChatUserMessage";
|
|
23
|
+
export { ChatUserMessage } from "./message-ui/ChatUserMessage";
|
|
24
|
+
export type { ChatWelcomeProps } from "./message-ui/ChatWelcome";
|
|
25
|
+
export { ChatWelcome } from "./message-ui/ChatWelcome";
|
|
26
|
+
export type { MermaidBlockProps } from "./message-ui/MermaidBlock";
|
|
27
|
+
export { MermaidBlock } from "./message-ui/MermaidBlock";
|
|
28
|
+
export type { BackgroundTasksBarProps } from "./tool-ui/BackgroundTasksBar";
|
|
29
|
+
export { BackgroundTasksBar } from "./tool-ui/BackgroundTasksBar";
|
|
30
|
+
export { BackgroundTaskToolUI } from "./tool-ui/BackgroundTaskToolUI";
|
|
20
31
|
export { CallAgentToolUI } from "./tool-ui/CallAgentToolUI";
|
|
21
|
-
export {
|
|
32
|
+
export type { ChatToolCallProps } from "./tool-ui/ChatToolCall";
|
|
33
|
+
export { ChatToolCall } from "./tool-ui/ChatToolCall";
|
|
22
34
|
export { GetCurrentTimeToolUI } from "./tool-ui/GetCurrentTimeToolUI";
|
|
23
35
|
export { ReadSkillToolUI } from "./tool-ui/ReadSkillToolUI";
|
|
24
36
|
export { RunJsToolUI } from "./tool-ui/RunJsToolUI";
|
|
25
|
-
export {
|
|
26
|
-
export {
|
|
37
|
+
export type { AgentToolUI, AgentToolUIName } from "./tool-ui/registry";
|
|
38
|
+
export { builtinToolUis, matchesToolUIName, resolveToolUI } from "./tool-ui/registry";
|
|
27
39
|
export { ToolUiTrailing } from "./tool-ui/ToolUiTrailing";
|
|
28
|
-
export type {
|
|
29
|
-
|
|
30
|
-
export {
|
|
31
|
-
export type { ChatInputProps } from "./message-ui/ChatInput";
|
|
32
|
-
|
|
33
|
-
export { ChatWelcome } from "./message-ui/ChatWelcome";
|
|
34
|
-
export type { ChatWelcomeProps } from "./message-ui/ChatWelcome";
|
|
35
|
-
|
|
36
|
-
export { ChatSpinner } from "./message-ui/ChatSpinner";
|
|
37
|
-
|
|
38
|
-
export { ChatMarkdown, createChatMarkdownComponents, chatMarkdownComponents, chatMarkdownClass, chatBodyClass } from "./message-ui/ChatMarkdown";
|
|
39
|
-
export type { ChatMarkdownProps, ChatMarkdownStreamState } from "./message-ui/ChatMarkdown";
|
|
40
|
-
|
|
41
|
-
export { MermaidBlock } from "./message-ui/MermaidBlock";
|
|
42
|
-
export type { MermaidBlockProps } from "./message-ui/MermaidBlock";
|
|
43
|
-
|
|
44
|
-
export { AgentPanel } from "./AgentPanel";
|
|
45
|
-
export type { AgentPanelProps } from "./AgentPanel";
|
|
46
|
-
|
|
47
|
-
export { useAgentStream, buildAgentHistory } from "./common/useAgentStream";
|
|
48
|
-
export type { UseAgentStreamOptions } from "./common/useAgentStream";
|
|
49
|
-
|
|
50
|
-
export { parseSseStream, normalizeSseEvent } from "./common/sse";
|
|
51
|
-
export type { AgentSseEvent, AgentSseCallbacks, ParseSseResult } from "./common/sse";
|
|
52
|
-
|
|
53
|
-
export type { AgentMessage, AgentMessageRole, AgentHistoryMessage, AgentStreamRequest, AgentPanelEndpoint, AgentToolAction, AgentToolHook, AgentToolCallEvent, AgentToolResultEvent, AgentToolNameMatch } from "./common/types";
|
|
54
|
-
|
|
55
|
-
export { formatToolName, prettyJson, isCallAgentToolName, parseCallAgentToolTargetId, matchesToolName, matchesToolHook } from "./common/utils";
|
|
56
|
-
|
|
57
|
-
export { parseBgTaskRef, formatBgElapsed } from "./common/bgTasks";
|
|
58
|
-
export type { ChatBgTask, ChatBgTaskStatus } from "./common/bgTasks";
|
|
40
|
+
export type { ChatToolMessage, ToolUIProps } from "./tool-ui/types";
|
|
41
|
+
export { isToolRunning } from "./tool-ui/types";
|
|
42
|
+
export { WebFetchToolUI } from "./tool-ui/WebFetchToolUI";
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CircleX } from "lucide-react";
|
|
1
2
|
import type { ReactNode } from "react";
|
|
2
3
|
import { cn } from "../../lib/cn";
|
|
3
4
|
|
|
@@ -13,10 +14,7 @@ export function ChatError({ children, className }: ChatErrorProps) {
|
|
|
13
14
|
role="alert"
|
|
14
15
|
className="flex items-center gap-2 rounded-lg border border-destructive/40 bg-[color-mix(in_oklab,var(--destructive)_14%,var(--nonla-surface))] px-3 py-2 text-(length:--chat-body-size) leading-(--chat-body-leading) text-destructive"
|
|
15
16
|
>
|
|
16
|
-
<
|
|
17
|
-
<circle cx="8" cy="8" r="5.75" stroke="currentColor" strokeWidth="1.4" />
|
|
18
|
-
<path d="M6 6l4 4M10 6l-4 4" stroke="currentColor" strokeWidth="1.4" strokeLinecap="round" />
|
|
19
|
-
</svg>
|
|
17
|
+
<CircleX className="size-4 shrink-0" aria-hidden />
|
|
20
18
|
<div className="min-w-0">{children}</div>
|
|
21
19
|
</div>
|
|
22
20
|
</div>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
+
import { ArrowUp, Eraser, Square } from "lucide-react";
|
|
1
2
|
import { type KeyboardEvent as ReactKeyboardEvent, type ReactNode, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
2
|
-
import { FluentIcon } from "../../icon/FluentIcon";
|
|
3
3
|
import { cn } from "../../lib/cn";
|
|
4
4
|
|
|
5
5
|
export type ChatInputProps = {
|
|
@@ -128,8 +128,8 @@ export function ChatInput({ generating = false, placeholder = "Message…", disa
|
|
|
128
128
|
|
|
129
129
|
<div className="flex items-center gap-1.5 pb-2 px-2">
|
|
130
130
|
{onClear ? (
|
|
131
|
-
<button type="button" onClick={onClear} title="
|
|
132
|
-
<
|
|
131
|
+
<button type="button" onClick={onClear} title="Clear chat" aria-label="Clear chat" className="inline-flex size-6 shrink-0 items-center justify-center rounded-lg border-0 bg-transparent cursor-pointer hover:bg-ink-hover">
|
|
132
|
+
<Eraser size={16} aria-hidden />
|
|
133
133
|
</button>
|
|
134
134
|
) : null}
|
|
135
135
|
{toolbar}
|
|
@@ -137,9 +137,7 @@ export function ChatInput({ generating = false, placeholder = "Message…", disa
|
|
|
137
137
|
|
|
138
138
|
{generating ? (
|
|
139
139
|
<button type="button" onClick={onCancel} title="Stop" className="w-6 h-6 rounded-full bg-brand text-(--nonla-solid-fg) flex items-center justify-center shrink-0 cursor-pointer hover:bg-brand/90 active:scale-95 transition-all duration-100 border-0">
|
|
140
|
-
<
|
|
141
|
-
<rect x="1.5" y="1.5" width="9" height="9" rx="2" fill="currentColor" />
|
|
142
|
-
</svg>
|
|
140
|
+
<Square size={12} fill="currentColor" strokeWidth={0} aria-hidden />
|
|
143
141
|
<span className="sr-only">Stop</span>
|
|
144
142
|
</button>
|
|
145
143
|
) : (
|
|
@@ -150,9 +148,7 @@ export function ChatInput({ generating = false, placeholder = "Message…", disa
|
|
|
150
148
|
title="Send (Enter)"
|
|
151
149
|
className={cn("w-6 h-6 rounded-full flex items-center justify-center shrink-0 transition-all duration-100 border-0", canSend ? "bg-foreground text-background cursor-pointer" : "bg-border text-muted-foreground cursor-not-allowed opacity-50")}
|
|
152
150
|
>
|
|
153
|
-
<
|
|
154
|
-
<path d="M6 9.5V2.5M6 2.5L3 5.5M6 2.5L9 5.5" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" />
|
|
155
|
-
</svg>
|
|
151
|
+
<ArrowUp size={14} strokeWidth={2.5} aria-hidden />
|
|
156
152
|
<span className="sr-only">Send</span>
|
|
157
153
|
</button>
|
|
158
154
|
)}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ChevronRight } from "lucide-react";
|
|
1
2
|
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
2
3
|
import { cn } from "../../lib/cn";
|
|
3
4
|
import { Shimmer } from "../../shimmer/Shimmer";
|
|
@@ -49,9 +50,7 @@ export function ChatThinking({ thinking, duration = 0, streaming = false, classN
|
|
|
49
50
|
>
|
|
50
51
|
<summary className="cursor-pointer select-none text-sm font-medium flex items-center gap-1 py-0.5 list-none [&::-webkit-details-marker]:hidden">
|
|
51
52
|
<Shimmer active={streaming} className="text-tertiary-foreground">{label}</Shimmer>
|
|
52
|
-
<
|
|
53
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
54
|
-
</svg>
|
|
53
|
+
<ChevronRight className="size-3 shrink-0 opacity-0 text-tertiary-foreground transition-[opacity,transform] duration-150 group-hover/thinking:opacity-100 group-open/thinking:opacity-100 group-open/thinking:rotate-90" aria-hidden />
|
|
55
54
|
</summary>
|
|
56
55
|
|
|
57
56
|
<div ref={bodyRef} className="pt-2 max-h-40 min-w-0 overflow-y-auto overflow-x-hidden mb-2 text-[14px] leading-normal">
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { ChevronDown } from "lucide-react";
|
|
1
2
|
import { useEffect, useRef, useState } from "react";
|
|
2
3
|
import { cn } from "../../lib/cn";
|
|
3
4
|
|
|
@@ -28,8 +29,8 @@ export function ChatUserMessage({ content, className }: ChatUserMessageProps) {
|
|
|
28
29
|
return (
|
|
29
30
|
<div
|
|
30
31
|
className={cn(
|
|
31
|
-
"nonla-chat-user relative isolate mt-6 mb-3 mx-4 overflow-hidden rounded-xl px-3 py-
|
|
32
|
-
"border border-
|
|
32
|
+
"nonla-chat-user relative isolate mt-6 mb-3 mx-4 overflow-hidden rounded-xl px-3 py-1.5",
|
|
33
|
+
"border border-solid border-border bg-[color-mix(in_oklab,var(--nonla-ink)_7%,transparent)] backdrop-blur-xl",
|
|
33
34
|
className,
|
|
34
35
|
)}
|
|
35
36
|
>
|
|
@@ -54,9 +55,7 @@ export function ChatUserMessage({ content, className }: ChatUserMessageProps) {
|
|
|
54
55
|
}}
|
|
55
56
|
className="flex w-full cursor-pointer items-center justify-center border-0 bg-transparent pt-1 text-muted-foreground hover:text-foreground"
|
|
56
57
|
>
|
|
57
|
-
<
|
|
58
|
-
<path d="M2.5 4.5 6 8l3.5-3.5" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" />
|
|
59
|
-
</svg>
|
|
58
|
+
<ChevronDown size={12} className={cn("transition-transform duration-200", isExpanded && "rotate-180")} aria-hidden />
|
|
60
59
|
</button>
|
|
61
60
|
) : null}
|
|
62
61
|
</div>
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ReactNode } from "react";
|
|
2
|
+
import { Button } from "../../button/Button";
|
|
2
3
|
import { cn } from "../../lib/cn";
|
|
3
4
|
|
|
4
5
|
export type ChatWelcomeProps = {
|
|
@@ -53,15 +54,9 @@ export function ChatWelcome({ name, description, avatar, starters = [...DEFAULT_
|
|
|
53
54
|
{starters.length > 0 && onStarter ? (
|
|
54
55
|
<div className="mt-8 flex flex-wrap items-center justify-center gap-2 max-w-130">
|
|
55
56
|
{starters.map((text) => (
|
|
56
|
-
<
|
|
57
|
-
key={text}
|
|
58
|
-
type="button"
|
|
59
|
-
disabled={disabled}
|
|
60
|
-
onClick={() => onStarter(text)}
|
|
61
|
-
className="px-3 py-1.5 rounded-lg border border-border bg-transparent text-[12px] font-medium text-tertiary-foreground cursor-pointer transition-colors duration-150 font-[inherit] hover:border-border hover:bg-muted/60 hover:text-foreground disabled:opacity-40 disabled:cursor-not-allowed"
|
|
62
|
-
>
|
|
57
|
+
<Button key={text} type="default" disabled={disabled} onClick={() => onStarter(text)}>
|
|
63
58
|
{text}
|
|
64
|
-
</
|
|
59
|
+
</Button>
|
|
65
60
|
))}
|
|
66
61
|
</div>
|
|
67
62
|
) : null}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Download, Maximize2, Minimize2, Moon, Sun } from "lucide-react";
|
|
1
2
|
import { useCallback, useEffect, useId, useRef, useState } from "react";
|
|
2
3
|
import { CodeBlock } from "../../codeblock/CodeBlock";
|
|
3
4
|
import { cn } from "../../lib/cn";
|
|
@@ -12,47 +13,6 @@ type MermaidTheme = "light" | "dark";
|
|
|
12
13
|
|
|
13
14
|
const btnClass = "flex items-center gap-1 px-2 py-1 rounded-lg border border-border text-muted-foreground text-xs cursor-pointer transition-colors hover:text-primary hover:border-primary/30 bg-transparent";
|
|
14
15
|
|
|
15
|
-
function IconSun({ size = 14 }: { size?: number }) {
|
|
16
|
-
return (
|
|
17
|
-
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" aria-hidden>
|
|
18
|
-
<circle cx="12" cy="12" r="4" stroke="currentColor" strokeWidth="1.75" />
|
|
19
|
-
<path d="M12 3v2M12 19v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M3 12h2M19 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" />
|
|
20
|
-
</svg>
|
|
21
|
-
);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function IconMoon({ size = 14 }: { size?: number }) {
|
|
25
|
-
return (
|
|
26
|
-
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" aria-hidden>
|
|
27
|
-
<path d="M21 14.5A8.5 8.5 0 1 1 12 3a7 7 0 0 0 9 11.5z" stroke="currentColor" strokeWidth="1.75" strokeLinejoin="round" />
|
|
28
|
-
</svg>
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function IconDownload({ size = 14 }: { size?: number }) {
|
|
33
|
-
return (
|
|
34
|
-
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" aria-hidden>
|
|
35
|
-
<path d="M12 4v12M7 12l5 5 5-5M5 20h14" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" />
|
|
36
|
-
</svg>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function IconFullScreen({ size = 14 }: { size?: number }) {
|
|
41
|
-
return (
|
|
42
|
-
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" aria-hidden>
|
|
43
|
-
<path d="M8 4H4v4M16 4h4v4M8 20H4v-4M16 20h4v-4" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" />
|
|
44
|
-
</svg>
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function IconQuitFullScreen({ size = 16 }: { size?: number }) {
|
|
49
|
-
return (
|
|
50
|
-
<svg width={size} height={size} viewBox="0 0 24 24" fill="none" aria-hidden>
|
|
51
|
-
<path d="M9 4v5H4M15 4v5h5M9 20v-5H4M15 20v-5h5" stroke="currentColor" strokeWidth="1.75" strokeLinecap="round" strokeLinejoin="round" />
|
|
52
|
-
</svg>
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
16
|
export function MermaidBlock({ children, className }: MermaidBlockProps) {
|
|
57
17
|
const id = useId().replace(/:/g, "_");
|
|
58
18
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
@@ -216,13 +176,13 @@ export function MermaidBlock({ children, className }: MermaidBlockProps) {
|
|
|
216
176
|
<div className={cn("my-3 last:mb-0 group relative rounded-xl border border-border overflow-hidden", surfaceClass, className)}>
|
|
217
177
|
<div className="absolute top-2 right-2 z-10 flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
218
178
|
<button type="button" onClick={toggleTheme} className={cn(btnClass, surfaceClass)} title={isDark ? "Light theme" : "Dark theme"}>
|
|
219
|
-
{isDark ? <
|
|
179
|
+
{isDark ? <Sun size={14} aria-hidden /> : <Moon size={14} aria-hidden />}
|
|
220
180
|
</button>
|
|
221
181
|
<button type="button" onClick={downloadSvg} className={cn(btnClass, surfaceClass)} title="Download SVG" disabled={!svgContent}>
|
|
222
|
-
<
|
|
182
|
+
<Download size={14} aria-hidden />
|
|
223
183
|
</button>
|
|
224
184
|
<button type="button" onClick={openFullscreen} className={cn(btnClass, surfaceClass)} title="Fullscreen">
|
|
225
|
-
<
|
|
185
|
+
<Maximize2 size={14} aria-hidden />
|
|
226
186
|
</button>
|
|
227
187
|
</div>
|
|
228
188
|
<div ref={containerRef} className="flex justify-center p-6 overflow-x-auto [&_svg]:max-w-full" />
|
|
@@ -237,13 +197,13 @@ export function MermaidBlock({ children, className }: MermaidBlockProps) {
|
|
|
237
197
|
>
|
|
238
198
|
<div className="fixed top-5 right-5 z-50 flex items-center gap-1.5">
|
|
239
199
|
<button type="button" onClick={toggleTheme} className={cn(btnClass, surfaceClass, "px-3 py-2 text-sm shadow-md")} title={isDark ? "Light theme" : "Dark theme"}>
|
|
240
|
-
{isDark ? <
|
|
200
|
+
{isDark ? <Sun size={16} aria-hidden /> : <Moon size={16} aria-hidden />}
|
|
241
201
|
</button>
|
|
242
202
|
<button type="button" onClick={downloadSvg} className={cn(btnClass, surfaceClass, "px-3 py-2 text-sm shadow-md")} title="Download SVG" disabled={!svgContent}>
|
|
243
|
-
<
|
|
203
|
+
<Download size={16} aria-hidden />
|
|
244
204
|
</button>
|
|
245
205
|
<button type="button" onClick={closeFullscreen} className={cn(btnClass, surfaceClass, "px-3 py-2 text-sm shadow-md")} title="Exit fullscreen">
|
|
246
|
-
<
|
|
206
|
+
<Minimize2 size={16} aria-hidden />
|
|
247
207
|
<span>Exit</span>
|
|
248
208
|
</button>
|
|
249
209
|
</div>
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { useEffect, useState } from "react";
|
|
2
2
|
import { Shimmer } from "../../shimmer/Shimmer";
|
|
3
|
+
import { Spin } from "../../spin/Spin";
|
|
3
4
|
import { formatBgElapsed, parseBgTaskRef } from "../common/bgTasks";
|
|
4
5
|
import { formatToolName, timestampMs } from "../common/utils";
|
|
5
|
-
import { ChatSpinner } from "../message-ui/ChatSpinner";
|
|
6
6
|
import { ToolUiBadge } from "./ToolUiBadge";
|
|
7
7
|
import type { ToolUIProps } from "./types";
|
|
8
8
|
|
|
@@ -20,16 +20,13 @@ export function BackgroundTaskToolUI({ msg, assistantLabel = "Assistant", assist
|
|
|
20
20
|
<div className="mt-1 animate-fadeIn">
|
|
21
21
|
<ToolUiBadge show={showAvatar} label={assistantLabel} color={assistantColor} />
|
|
22
22
|
<div className="px-4 pb-1">
|
|
23
|
-
<div className="flex gap-
|
|
24
|
-
<
|
|
25
|
-
<
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
<span className="shrink-0 text-[14px] tabular-nums text-muted-foreground">{formatBgElapsed(timestampMs(msg.timestamp), now)}</span>
|
|
31
|
-
</div>
|
|
32
|
-
</div>
|
|
23
|
+
<div className="flex items-center gap-2 py-1">
|
|
24
|
+
<Spin variant="agent" size="small" className="shrink-0" />
|
|
25
|
+
<Shimmer className="min-w-0 truncate text-[14px] font-medium text-muted-foreground">
|
|
26
|
+
{label}
|
|
27
|
+
<span className="font-normal text-tertiary-foreground"> in background</span>
|
|
28
|
+
</Shimmer>
|
|
29
|
+
<span className="shrink-0 text-[13px] tabular-nums text-tertiary-foreground">{formatBgElapsed(timestampMs(msg.timestamp), now)}</span>
|
|
33
30
|
</div>
|
|
34
31
|
</div>
|
|
35
32
|
</div>
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { ChevronDown, Square } from "lucide-react";
|
|
1
2
|
import { type ReactNode, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
2
|
-
import { FluentIcon } from "../../icon/FluentIcon";
|
|
3
3
|
import { cn } from "../../lib/cn";
|
|
4
4
|
import { Modal } from "../../modal/Modal";
|
|
5
5
|
import { Popconfirm } from "../../popconfirm/Popconfirm";
|
|
6
|
+
import { Spin } from "../../spin/Spin";
|
|
6
7
|
import type { ChatBgTask } from "../common/bgTasks";
|
|
7
8
|
import { formatBgElapsed } from "../common/bgTasks";
|
|
8
9
|
import { formatToolName } from "../common/utils";
|
|
9
|
-
import { ChatSpinner } from "../message-ui/ChatSpinner";
|
|
10
10
|
|
|
11
11
|
function TaskLogsModal({
|
|
12
12
|
task,
|
|
@@ -27,7 +27,7 @@ function TaskLogsModal({
|
|
|
27
27
|
|
|
28
28
|
return (
|
|
29
29
|
<Modal open={open} onCancel={onClose} footer={null} title={task ? formatToolName(task.toolName) : "Logs"} width={560}>
|
|
30
|
-
<pre ref={preRef} className="m-0 max-h-[50vh] overflow-auto rounded-md border border-border-subtle bg-muted px-3 py-2 font-mono text-[
|
|
30
|
+
<pre ref={preRef} className="m-0 max-h-[50vh] overflow-auto rounded-md border border-border-subtle bg-muted px-3 py-2 font-mono text-[13px] leading-relaxed whitespace-pre-wrap break-all text-foreground">
|
|
31
31
|
{logs || "Waiting for output…"}
|
|
32
32
|
</pre>
|
|
33
33
|
</Modal>
|
|
@@ -48,16 +48,15 @@ function TaskRow({
|
|
|
48
48
|
onLogs: () => void;
|
|
49
49
|
}) {
|
|
50
50
|
return (
|
|
51
|
-
<div className="flex items-center gap-
|
|
52
|
-
<
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
<FluentIcon name="document-text-24" size={12} />
|
|
51
|
+
<div className="flex items-center gap-1 px-1.5 py-0.5">
|
|
52
|
+
<button type="button" onClick={onLogs} title="Logs" aria-label={`Logs ${formatToolName(task.toolName)}`} className="flex min-w-0 flex-1 cursor-pointer items-center gap-2 rounded-md px-1 py-[3px] text-left transition-colors hover:bg-secondary">
|
|
53
|
+
<Spin variant="agent" size="small" className="shrink-0" />
|
|
54
|
+
<span className="min-w-0 flex-1 truncate text-[13px] font-medium text-foreground">{formatToolName(task.toolName)}</span>
|
|
55
|
+
<span className="shrink-0 text-[12px] tabular-nums text-tertiary-foreground">{formatBgElapsed(task.startedAt, now)}</span>
|
|
57
56
|
</button>
|
|
58
57
|
<Popconfirm title={`Stop ${formatToolName(task.toolName)}?`} okText="Stop" okType="danger" onConfirm={onCancel} getPopupContainer={() => document.body}>
|
|
59
|
-
<button type="button" disabled={cancelling} title="Stop" aria-label={`Stop ${formatToolName(task.toolName)}`} className="inline-flex
|
|
60
|
-
|
|
58
|
+
<button type="button" disabled={cancelling} title="Stop" aria-label={`Stop ${formatToolName(task.toolName)}`} className="inline-flex size-5 shrink-0 items-center justify-center rounded cursor-pointer text-destructive transition-colors hover:bg-destructive/10 disabled:opacity-40">
|
|
59
|
+
<Square size={10} fill="currentColor" strokeWidth={0} aria-hidden />
|
|
61
60
|
</button>
|
|
62
61
|
</Popconfirm>
|
|
63
62
|
</div>
|
|
@@ -118,10 +117,10 @@ export function BackgroundTasksBar({ children, tasks = [], cancellingIds, onCanc
|
|
|
118
117
|
{children}
|
|
119
118
|
|
|
120
119
|
{tasks.length > 0 ? (
|
|
121
|
-
<div className="absolute
|
|
120
|
+
<div className="absolute inset-x-6 bottom-[calc(100%)] z-20 overflow-hidden rounded-t-lg border-x border-t border-border">
|
|
122
121
|
<div className={cn("flex h-7.5 items-center gap-1 px-1", open && "border-b border-border-subtle")}>
|
|
123
|
-
<button type="button" onClick={() => setOpen((v) => !v)} className="flex min-w-0 flex-1 font-medium items-center gap-1 rounded-md px-1.5 py-1 text-[
|
|
124
|
-
|
|
122
|
+
<button type="button" onClick={() => setOpen((v) => !v)} className="flex min-w-0 flex-1 cursor-pointer font-medium items-center gap-1 rounded-md px-1.5 py-1 text-[13px] text-muted-foreground/90 transition-colors hover:text-foreground" aria-expanded={open} aria-label={label}>
|
|
123
|
+
<ChevronDown size={13} className={cn("shrink-0 transition-transform duration-150", !open && "-rotate-90")} aria-hidden />
|
|
125
124
|
<span className="truncate">{label}</span>
|
|
126
125
|
</button>
|
|
127
126
|
</div>
|
|
@@ -2,6 +2,7 @@ import { type ReactNode, useEffect, useRef, useState } from "react";
|
|
|
2
2
|
import { FluentIcon } from "../../icon/FluentIcon";
|
|
3
3
|
import { cn } from "../../lib/cn";
|
|
4
4
|
import { Shimmer } from "../../shimmer/Shimmer";
|
|
5
|
+
import { Spin } from "../../spin/Spin";
|
|
5
6
|
import { formatBgElapsed, parseBgTaskRef } from "../common/bgTasks";
|
|
6
7
|
import { parseJsonObject, prettyJson, timestampMs } from "../common/utils";
|
|
7
8
|
import { ChatMarkdown } from "../message-ui/ChatMarkdown";
|
|
@@ -111,6 +112,7 @@ export function CallAgentToolUI({ msg, assistantLabel = "Assistant", assistantCo
|
|
|
111
112
|
const hasRequest = requestMessage.length > 0;
|
|
112
113
|
const composing = !hasOutput && !hasError && !hasRequest;
|
|
113
114
|
const awaitingReply = bgRunning || (!hasOutput && !hasError && hasRequest);
|
|
115
|
+
const calling = composing || awaitingReply;
|
|
114
116
|
const showCallee = awaitingReply || failed || parsed != null;
|
|
115
117
|
const calleeName = msg.toolLabel?.replace(/^Call\s+/i, "") ?? "Agent";
|
|
116
118
|
const statusLabel = failed && !awaitingReply ? (parsed?.error ?? "Call failed") : null;
|
|
@@ -121,11 +123,15 @@ export function CallAgentToolUI({ msg, assistantLabel = "Assistant", assistantCo
|
|
|
121
123
|
<div className="px-4 py-1">
|
|
122
124
|
<div className={cn("overflow-hidden rounded-xl border bg-card", failed ? "border-destructive/35" : "border-border")}>
|
|
123
125
|
<div className="flex items-center gap-2 border-b border-border-subtle px-3 py-2">
|
|
124
|
-
|
|
125
|
-
|
|
126
|
+
{calling ? (
|
|
127
|
+
<Spin variant="agent" size="small" className="shrink-0" />
|
|
128
|
+
) : (
|
|
129
|
+
<FluentIcon name="chat-multiple-24" size={13} className="shrink-0 text-muted-foreground" />
|
|
130
|
+
)}
|
|
131
|
+
<Shimmer active={calling} className="min-w-0 flex-1 truncate text-[14px] font-medium text-foreground">Calling {calleeName}</Shimmer>
|
|
126
132
|
{bgRunning ? <span className="text-[14px] tabular-nums text-muted-foreground">{formatBgElapsed(timestampMs(msg.timestamp), now)}</span> : null}
|
|
127
133
|
{statusLabel ? <span className="max-w-40 truncate text-[13px] italic text-destructive">{statusLabel}</span> : null}
|
|
128
|
-
<ToolUiTrailing
|
|
134
|
+
<ToolUiTrailing failed={failed && !awaitingReply} />
|
|
129
135
|
</div>
|
|
130
136
|
|
|
131
137
|
{composing || hasRequest || showCallee ? (
|
|
@@ -154,7 +160,7 @@ export function CallAgentToolUI({ msg, assistantLabel = "Assistant", assistantCo
|
|
|
154
160
|
</ExpandableBody>
|
|
155
161
|
) : (
|
|
156
162
|
<ExpandableBody>
|
|
157
|
-
<pre className="m-0 font-mono text-[
|
|
163
|
+
<pre className="m-0 font-mono text-[13px] leading-normal break-all whitespace-pre-wrap text-muted-foreground">{prettyJson(msg.toolOutput)}</pre>
|
|
158
164
|
</ExpandableBody>
|
|
159
165
|
)}
|
|
160
166
|
</div>
|