gigaclaw 1.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/LICENSE +26 -0
- package/README.md +237 -0
- package/api/CLAUDE.md +19 -0
- package/api/index.js +265 -0
- package/bin/cli.js +823 -0
- package/bin/local.sh +85 -0
- package/bin/postinstall.js +63 -0
- package/config/index.js +26 -0
- package/config/instrumentation.js +62 -0
- package/drizzle/0000_initial.sql +52 -0
- package/drizzle/0001_nostalgic_sersi.sql +11 -0
- package/drizzle/0002_black_daimon_hellstrom.sql +19 -0
- package/drizzle/0003_rename_code_workspaces.sql +5 -0
- package/drizzle/meta/0000_snapshot.json +321 -0
- package/drizzle/meta/0001_snapshot.json +390 -0
- package/drizzle/meta/0002_snapshot.json +411 -0
- package/drizzle/meta/0003_snapshot.json +419 -0
- package/drizzle/meta/_journal.json +34 -0
- package/lib/actions.js +44 -0
- package/lib/ai/agent.js +86 -0
- package/lib/ai/index.js +342 -0
- package/lib/ai/model.js +180 -0
- package/lib/ai/tools.js +269 -0
- package/lib/ai/web-search.js +42 -0
- package/lib/auth/actions.js +28 -0
- package/lib/auth/config.js +27 -0
- package/lib/auth/edge-config.js +27 -0
- package/lib/auth/index.js +27 -0
- package/lib/auth/middleware.js +62 -0
- package/lib/channels/base.js +56 -0
- package/lib/channels/index.js +15 -0
- package/lib/channels/telegram.js +148 -0
- package/lib/chat/actions.js +579 -0
- package/lib/chat/api.js +140 -0
- package/lib/chat/components/app-sidebar.js +213 -0
- package/lib/chat/components/app-sidebar.jsx +279 -0
- package/lib/chat/components/chat-header.js +192 -0
- package/lib/chat/components/chat-header.jsx +223 -0
- package/lib/chat/components/chat-input.js +236 -0
- package/lib/chat/components/chat-input.jsx +249 -0
- package/lib/chat/components/chat-nav-context.js +11 -0
- package/lib/chat/components/chat-nav-context.jsx +11 -0
- package/lib/chat/components/chat-page.js +99 -0
- package/lib/chat/components/chat-page.jsx +121 -0
- package/lib/chat/components/chat.js +153 -0
- package/lib/chat/components/chat.jsx +199 -0
- package/lib/chat/components/chats-page.js +367 -0
- package/lib/chat/components/chats-page.jsx +394 -0
- package/lib/chat/components/code-mode-toggle.js +132 -0
- package/lib/chat/components/code-mode-toggle.jsx +163 -0
- package/lib/chat/components/crons-page.js +172 -0
- package/lib/chat/components/crons-page.jsx +244 -0
- package/lib/chat/components/greeting.js +11 -0
- package/lib/chat/components/greeting.jsx +16 -0
- package/lib/chat/components/icons.js +805 -0
- package/lib/chat/components/icons.jsx +751 -0
- package/lib/chat/components/index.js +20 -0
- package/lib/chat/components/message.js +363 -0
- package/lib/chat/components/message.jsx +422 -0
- package/lib/chat/components/messages.js +65 -0
- package/lib/chat/components/messages.jsx +74 -0
- package/lib/chat/components/notifications-page.js +56 -0
- package/lib/chat/components/notifications-page.jsx +87 -0
- package/lib/chat/components/page-layout.js +21 -0
- package/lib/chat/components/page-layout.jsx +28 -0
- package/lib/chat/components/pull-requests-page.js +103 -0
- package/lib/chat/components/pull-requests-page.jsx +113 -0
- package/lib/chat/components/settings-layout.js +39 -0
- package/lib/chat/components/settings-layout.jsx +53 -0
- package/lib/chat/components/settings-secrets-page.js +216 -0
- package/lib/chat/components/settings-secrets-page.jsx +264 -0
- package/lib/chat/components/sidebar-history-item.js +138 -0
- package/lib/chat/components/sidebar-history-item.jsx +119 -0
- package/lib/chat/components/sidebar-history.js +167 -0
- package/lib/chat/components/sidebar-history.jsx +220 -0
- package/lib/chat/components/sidebar-user-nav.js +61 -0
- package/lib/chat/components/sidebar-user-nav.jsx +77 -0
- package/lib/chat/components/swarm-page.js +157 -0
- package/lib/chat/components/swarm-page.jsx +210 -0
- package/lib/chat/components/tool-call.js +89 -0
- package/lib/chat/components/tool-call.jsx +107 -0
- package/lib/chat/components/triggers-page.js +153 -0
- package/lib/chat/components/triggers-page.jsx +221 -0
- package/lib/chat/components/ui/combobox.js +98 -0
- package/lib/chat/components/ui/combobox.jsx +114 -0
- package/lib/chat/components/ui/confirm-dialog.js +53 -0
- package/lib/chat/components/ui/confirm-dialog.jsx +57 -0
- package/lib/chat/components/ui/dropdown-menu.js +194 -0
- package/lib/chat/components/ui/dropdown-menu.jsx +215 -0
- package/lib/chat/components/ui/rename-dialog.js +78 -0
- package/lib/chat/components/ui/rename-dialog.jsx +74 -0
- package/lib/chat/components/ui/scroll-area.js +13 -0
- package/lib/chat/components/ui/scroll-area.jsx +17 -0
- package/lib/chat/components/ui/separator.js +21 -0
- package/lib/chat/components/ui/separator.jsx +18 -0
- package/lib/chat/components/ui/sheet.js +75 -0
- package/lib/chat/components/ui/sheet.jsx +95 -0
- package/lib/chat/components/ui/sidebar.js +228 -0
- package/lib/chat/components/ui/sidebar.jsx +246 -0
- package/lib/chat/components/ui/tooltip.js +56 -0
- package/lib/chat/components/ui/tooltip.jsx +66 -0
- package/lib/chat/components/upgrade-dialog.js +151 -0
- package/lib/chat/components/upgrade-dialog.jsx +170 -0
- package/lib/chat/utils.js +11 -0
- package/lib/code/actions.js +153 -0
- package/lib/code/code-page.js +22 -0
- package/lib/code/code-page.jsx +25 -0
- package/lib/code/index.js +1 -0
- package/lib/code/terminal-view.js +201 -0
- package/lib/code/terminal-view.jsx +224 -0
- package/lib/code/ws-proxy.js +80 -0
- package/lib/cron.js +246 -0
- package/lib/db/api-keys.js +163 -0
- package/lib/db/chats.js +168 -0
- package/lib/db/code-workspaces.js +110 -0
- package/lib/db/index.js +52 -0
- package/lib/db/notifications.js +99 -0
- package/lib/db/schema.js +66 -0
- package/lib/db/update-check.js +96 -0
- package/lib/db/users.js +89 -0
- package/lib/paths.js +42 -0
- package/lib/tools/create-job.js +97 -0
- package/lib/tools/docker.js +146 -0
- package/lib/tools/github.js +271 -0
- package/lib/tools/openai.js +35 -0
- package/lib/tools/telegram.js +292 -0
- package/lib/triggers.js +104 -0
- package/lib/utils/render-md.js +111 -0
- package/package.json +118 -0
- package/setup/lib/auth.mjs +81 -0
- package/setup/lib/env.mjs +21 -0
- package/setup/lib/fs-utils.mjs +20 -0
- package/setup/lib/github.mjs +149 -0
- package/setup/lib/prerequisites.mjs +155 -0
- package/setup/lib/prompts.mjs +267 -0
- package/setup/lib/providers.mjs +105 -0
- package/setup/lib/sync.mjs +125 -0
- package/setup/lib/targets.mjs +45 -0
- package/setup/lib/telegram-verify.mjs +63 -0
- package/setup/lib/telegram.mjs +76 -0
- package/setup/setup-cloud.mjs +833 -0
- package/setup/setup-local.mjs +377 -0
- package/setup/setup-telegram.mjs +265 -0
- package/setup/setup.mjs +87 -0
- package/templates/.dockerignore +5 -0
- package/templates/.env.example +104 -0
- package/templates/.github/workflows/auto-merge.yml +117 -0
- package/templates/.github/workflows/notify-job-failed.yml +64 -0
- package/templates/.github/workflows/notify-pr-complete.yml +119 -0
- package/templates/.github/workflows/rebuild-event-handler.yml +121 -0
- package/templates/.github/workflows/run-job.yml +89 -0
- package/templates/.github/workflows/upgrade-event-handler.yml +62 -0
- package/templates/.gitignore.template +45 -0
- package/templates/.pi/extensions/env-sanitizer/index.ts +48 -0
- package/templates/.pi/extensions/env-sanitizer/package.json +5 -0
- package/templates/CLAUDE.md +29 -0
- package/templates/CLAUDE.md.template +308 -0
- package/templates/app/api/[...gigaclaw]/route.js +1 -0
- package/templates/app/api/auth/[...nextauth]/route.js +1 -0
- package/templates/app/chat/[chatId]/page.js +9 -0
- package/templates/app/chats/page.js +7 -0
- package/templates/app/code/[codeWorkspaceId]/page.js +9 -0
- package/templates/app/components/ascii-logo.jsx +12 -0
- package/templates/app/components/login-form.jsx +92 -0
- package/templates/app/components/setup-form.jsx +82 -0
- package/templates/app/components/theme-provider.jsx +11 -0
- package/templates/app/components/theme-toggle.jsx +38 -0
- package/templates/app/components/ui/button.jsx +21 -0
- package/templates/app/components/ui/card.jsx +23 -0
- package/templates/app/components/ui/input.jsx +10 -0
- package/templates/app/components/ui/label.jsx +10 -0
- package/templates/app/crons/page.js +5 -0
- package/templates/app/globals.css +90 -0
- package/templates/app/layout.js +33 -0
- package/templates/app/login/page.js +15 -0
- package/templates/app/notifications/page.js +7 -0
- package/templates/app/page.js +7 -0
- package/templates/app/pull-requests/page.js +7 -0
- package/templates/app/settings/crons/page.js +5 -0
- package/templates/app/settings/layout.js +7 -0
- package/templates/app/settings/page.js +5 -0
- package/templates/app/settings/secrets/page.js +5 -0
- package/templates/app/settings/triggers/page.js +5 -0
- package/templates/app/stream/chat/route.js +1 -0
- package/templates/app/swarm/page.js +7 -0
- package/templates/app/triggers/page.js +5 -0
- package/templates/config/CODE_PLANNING.md +14 -0
- package/templates/config/CRONS.json +56 -0
- package/templates/config/HEARTBEAT.md +3 -0
- package/templates/config/JOB_AGENT.md +30 -0
- package/templates/config/JOB_PLANNING.md +240 -0
- package/templates/config/JOB_SUMMARY.md +130 -0
- package/templates/config/SKILL_BUILDING_GUIDE.md +96 -0
- package/templates/config/SOUL.md +48 -0
- package/templates/config/TRIGGERS.json +58 -0
- package/templates/config/WEB_SEARCH_AVAILABLE.md +5 -0
- package/templates/config/WEB_SEARCH_UNAVAILABLE.md +3 -0
- package/templates/docker/claude-code-job/Dockerfile +34 -0
- package/templates/docker/claude-code-job/entrypoint.sh +149 -0
- package/templates/docker/claude-code-workspace/.tmux.conf +5 -0
- package/templates/docker/claude-code-workspace/Dockerfile +61 -0
- package/templates/docker/claude-code-workspace/entrypoint.sh +51 -0
- package/templates/docker/event-handler/Dockerfile +20 -0
- package/templates/docker/event-handler/ecosystem.config.cjs +7 -0
- package/templates/docker/pi-coding-agent-job/Dockerfile +51 -0
- package/templates/docker/pi-coding-agent-job/entrypoint.sh +164 -0
- package/templates/docker-compose.local.yml +78 -0
- package/templates/docker-compose.yml +64 -0
- package/templates/instrumentation.js +6 -0
- package/templates/middleware.js +23 -0
- package/templates/next.config.mjs +3 -0
- package/templates/postcss.config.mjs +5 -0
- package/templates/public/favicon.ico +0 -0
- package/templates/server.js +25 -0
- package/templates/skills/LICENSE +21 -0
- package/templates/skills/README.md +119 -0
- package/templates/skills/brave-search/SKILL.md +79 -0
- package/templates/skills/brave-search/content.js +86 -0
- package/templates/skills/brave-search/package-lock.json +621 -0
- package/templates/skills/brave-search/package.json +14 -0
- package/templates/skills/brave-search/search.js +199 -0
- package/templates/skills/browser-tools/SKILL.md +196 -0
- package/templates/skills/browser-tools/browser-content.js +103 -0
- package/templates/skills/browser-tools/browser-cookies.js +35 -0
- package/templates/skills/browser-tools/browser-eval.js +53 -0
- package/templates/skills/browser-tools/browser-hn-scraper.js +108 -0
- package/templates/skills/browser-tools/browser-nav.js +44 -0
- package/templates/skills/browser-tools/browser-pick.js +162 -0
- package/templates/skills/browser-tools/browser-screenshot.js +34 -0
- package/templates/skills/browser-tools/browser-start.js +87 -0
- package/templates/skills/browser-tools/package-lock.json +2556 -0
- package/templates/skills/browser-tools/package.json +19 -0
- package/templates/skills/google-docs/SKILL.md +23 -0
- package/templates/skills/google-docs/create.sh +69 -0
- package/templates/skills/google-drive/SKILL.md +47 -0
- package/templates/skills/google-drive/delete.sh +47 -0
- package/templates/skills/google-drive/download.sh +50 -0
- package/templates/skills/google-drive/list.sh +41 -0
- package/templates/skills/google-drive/upload.sh +76 -0
- package/templates/skills/kie-ai/SKILL.md +38 -0
- package/templates/skills/kie-ai/generate-image.sh +77 -0
- package/templates/skills/kie-ai/generate-video.sh +69 -0
- package/templates/skills/llm-secrets/SKILL.md +34 -0
- package/templates/skills/llm-secrets/llm-secrets.js +33 -0
- package/templates/skills/modify-self/SKILL.md +12 -0
- package/templates/skills/youtube-transcript/SKILL.md +41 -0
- package/templates/skills/youtube-transcript/package-lock.json +24 -0
- package/templates/skills/youtube-transcript/package.json +8 -0
- package/templates/skills/youtube-transcript/transcript.js +84 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useState, useEffect, useRef } from "react";
|
|
4
|
+
import { cn } from "../../utils.js";
|
|
5
|
+
const DropdownContext = createContext({ open: false, onOpenChange: () => {
|
|
6
|
+
} });
|
|
7
|
+
function DropdownMenu({ children, open: controlledOpen, onOpenChange: controlledOnOpenChange }) {
|
|
8
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
9
|
+
const open = controlledOpen !== void 0 ? controlledOpen : internalOpen;
|
|
10
|
+
const onOpenChange = controlledOnOpenChange || setInternalOpen;
|
|
11
|
+
return /* @__PURE__ */ jsx(DropdownContext.Provider, { value: { open, onOpenChange }, children: /* @__PURE__ */ jsx("div", { className: "relative inline-block", children }) });
|
|
12
|
+
}
|
|
13
|
+
function DropdownMenuTrigger({ children, asChild, ...props }) {
|
|
14
|
+
const { open, onOpenChange } = useContext(DropdownContext);
|
|
15
|
+
const handleClick = (e) => {
|
|
16
|
+
e.stopPropagation();
|
|
17
|
+
onOpenChange(!open);
|
|
18
|
+
};
|
|
19
|
+
const child = Array.isArray(children) ? children[0] : children;
|
|
20
|
+
const childIsInteractive = child && typeof child === "object" && "type" in child && (child.type === "button" || child.type === "a");
|
|
21
|
+
if ((asChild || childIsInteractive) && child && typeof child === "object" && "props" in child) {
|
|
22
|
+
return /* @__PURE__ */ jsx(
|
|
23
|
+
child.type,
|
|
24
|
+
{
|
|
25
|
+
...child.props,
|
|
26
|
+
onClick: (e) => {
|
|
27
|
+
child.props.onClick?.(e);
|
|
28
|
+
handleClick(e);
|
|
29
|
+
},
|
|
30
|
+
"aria-expanded": open,
|
|
31
|
+
"data-state": open ? "open" : "closed"
|
|
32
|
+
}
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
return /* @__PURE__ */ jsx(
|
|
36
|
+
"span",
|
|
37
|
+
{
|
|
38
|
+
role: "button",
|
|
39
|
+
tabIndex: 0,
|
|
40
|
+
onClick: handleClick,
|
|
41
|
+
onKeyDown: (e) => {
|
|
42
|
+
if (e.key === "Enter" || e.key === " ") handleClick(e);
|
|
43
|
+
},
|
|
44
|
+
"aria-expanded": open,
|
|
45
|
+
"data-state": open ? "open" : "closed",
|
|
46
|
+
...props,
|
|
47
|
+
children
|
|
48
|
+
}
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
function DropdownMenuContent({ children, className, align = "start", side = "bottom", sideOffset = 4, ...props }) {
|
|
52
|
+
const { open, onOpenChange } = useContext(DropdownContext);
|
|
53
|
+
const ref = useRef(null);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
if (!open) return;
|
|
56
|
+
const handleClickOutside = (e) => {
|
|
57
|
+
if (ref.current && !ref.current.contains(e.target)) {
|
|
58
|
+
onOpenChange(false);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const handleEsc = (e) => {
|
|
62
|
+
if (e.key === "Escape") onOpenChange(false);
|
|
63
|
+
};
|
|
64
|
+
setTimeout(() => document.addEventListener("click", handleClickOutside), 0);
|
|
65
|
+
document.addEventListener("keydown", handleEsc);
|
|
66
|
+
return () => {
|
|
67
|
+
document.removeEventListener("click", handleClickOutside);
|
|
68
|
+
document.removeEventListener("keydown", handleEsc);
|
|
69
|
+
};
|
|
70
|
+
}, [open, onOpenChange]);
|
|
71
|
+
if (!open) return null;
|
|
72
|
+
return /* @__PURE__ */ jsx(
|
|
73
|
+
"div",
|
|
74
|
+
{
|
|
75
|
+
ref,
|
|
76
|
+
className: cn(
|
|
77
|
+
"absolute z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-background/80 backdrop-blur-sm p-1 text-foreground shadow-lg",
|
|
78
|
+
side === "bottom" && `top-full mt-1`,
|
|
79
|
+
side === "top" && `bottom-full mb-1`,
|
|
80
|
+
align === "end" && "right-0",
|
|
81
|
+
align === "start" && "left-0",
|
|
82
|
+
className
|
|
83
|
+
),
|
|
84
|
+
...props,
|
|
85
|
+
children
|
|
86
|
+
}
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
function DropdownMenuItem({ children, className, onClick, asChild, ...props }) {
|
|
90
|
+
const { onOpenChange } = useContext(DropdownContext);
|
|
91
|
+
const handleClick = (e) => {
|
|
92
|
+
onClick?.(e);
|
|
93
|
+
onOpenChange(false);
|
|
94
|
+
};
|
|
95
|
+
if (asChild && children) {
|
|
96
|
+
const child = Array.isArray(children) ? children[0] : children;
|
|
97
|
+
if (child && typeof child === "object" && "props" in child) {
|
|
98
|
+
return /* @__PURE__ */ jsx(
|
|
99
|
+
child.type,
|
|
100
|
+
{
|
|
101
|
+
...child.props,
|
|
102
|
+
className: cn(
|
|
103
|
+
"relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-background focus:bg-background",
|
|
104
|
+
child.props.className,
|
|
105
|
+
className
|
|
106
|
+
),
|
|
107
|
+
onClick: (e) => {
|
|
108
|
+
child.props.onClick?.(e);
|
|
109
|
+
handleClick(e);
|
|
110
|
+
},
|
|
111
|
+
role: "menuitem"
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return /* @__PURE__ */ jsx(
|
|
117
|
+
"div",
|
|
118
|
+
{
|
|
119
|
+
role: "menuitem",
|
|
120
|
+
className: cn(
|
|
121
|
+
"relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-background focus:bg-background",
|
|
122
|
+
className
|
|
123
|
+
),
|
|
124
|
+
onClick: handleClick,
|
|
125
|
+
...props,
|
|
126
|
+
children
|
|
127
|
+
}
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
function DropdownMenuSeparator({ className }) {
|
|
131
|
+
return /* @__PURE__ */ jsx("div", { className: cn("-mx-1 my-1 h-px bg-border", className) });
|
|
132
|
+
}
|
|
133
|
+
function DropdownMenuLabel({ children, className }) {
|
|
134
|
+
return /* @__PURE__ */ jsx("div", { className: cn("px-2 py-1.5 text-sm font-semibold", className), children });
|
|
135
|
+
}
|
|
136
|
+
function DropdownMenuGroup({ children }) {
|
|
137
|
+
return /* @__PURE__ */ jsx("div", { children });
|
|
138
|
+
}
|
|
139
|
+
const SubContext = createContext({ open: false, setOpen: () => {
|
|
140
|
+
} });
|
|
141
|
+
function DropdownMenuSub({ children }) {
|
|
142
|
+
const [open, setOpen] = useState(false);
|
|
143
|
+
return /* @__PURE__ */ jsx(SubContext.Provider, { value: { open, setOpen }, children: /* @__PURE__ */ jsx("div", { className: "relative", children }) });
|
|
144
|
+
}
|
|
145
|
+
function DropdownMenuSubTrigger({ children, className }) {
|
|
146
|
+
const { open, setOpen } = useContext(SubContext);
|
|
147
|
+
return /* @__PURE__ */ jsxs(
|
|
148
|
+
"div",
|
|
149
|
+
{
|
|
150
|
+
role: "menuitem",
|
|
151
|
+
"aria-haspopup": "menu",
|
|
152
|
+
"aria-expanded": open,
|
|
153
|
+
className: cn(
|
|
154
|
+
"relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-background focus:bg-background justify-between",
|
|
155
|
+
className
|
|
156
|
+
),
|
|
157
|
+
onClick: (e) => {
|
|
158
|
+
e.stopPropagation();
|
|
159
|
+
setOpen((v) => !v);
|
|
160
|
+
},
|
|
161
|
+
children: [
|
|
162
|
+
/* @__PURE__ */ jsx("span", { className: "flex items-center gap-2", children }),
|
|
163
|
+
/* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("polyline", { points: "9 18 15 12 9 6" }) })
|
|
164
|
+
]
|
|
165
|
+
}
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
function DropdownMenuSubContent({ children, className }) {
|
|
169
|
+
const { open } = useContext(SubContext);
|
|
170
|
+
if (!open) return null;
|
|
171
|
+
return /* @__PURE__ */ jsx(
|
|
172
|
+
"div",
|
|
173
|
+
{
|
|
174
|
+
role: "menu",
|
|
175
|
+
className: cn(
|
|
176
|
+
"absolute left-full top-0 z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-background/80 backdrop-blur-sm p-1 text-foreground shadow-lg ml-1",
|
|
177
|
+
className
|
|
178
|
+
),
|
|
179
|
+
children
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
export {
|
|
184
|
+
DropdownMenu,
|
|
185
|
+
DropdownMenuContent,
|
|
186
|
+
DropdownMenuGroup,
|
|
187
|
+
DropdownMenuItem,
|
|
188
|
+
DropdownMenuLabel,
|
|
189
|
+
DropdownMenuSeparator,
|
|
190
|
+
DropdownMenuSub,
|
|
191
|
+
DropdownMenuSubContent,
|
|
192
|
+
DropdownMenuSubTrigger,
|
|
193
|
+
DropdownMenuTrigger
|
|
194
|
+
};
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, useState, useEffect, useRef } from 'react';
|
|
4
|
+
import { cn } from '../../utils.js';
|
|
5
|
+
|
|
6
|
+
const DropdownContext = createContext({ open: false, onOpenChange: () => {} });
|
|
7
|
+
|
|
8
|
+
export function DropdownMenu({ children, open: controlledOpen, onOpenChange: controlledOnOpenChange }) {
|
|
9
|
+
const [internalOpen, setInternalOpen] = useState(false);
|
|
10
|
+
const open = controlledOpen !== undefined ? controlledOpen : internalOpen;
|
|
11
|
+
const onOpenChange = controlledOnOpenChange || setInternalOpen;
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<DropdownContext.Provider value={{ open, onOpenChange }}>
|
|
15
|
+
<div className="relative inline-block">{children}</div>
|
|
16
|
+
</DropdownContext.Provider>
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function DropdownMenuTrigger({ children, asChild, ...props }) {
|
|
21
|
+
const { open, onOpenChange } = useContext(DropdownContext);
|
|
22
|
+
const handleClick = (e) => {
|
|
23
|
+
e.stopPropagation();
|
|
24
|
+
onOpenChange(!open);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Slot pattern: when asChild=true (or when the child is already a button/a),
|
|
28
|
+
// clone the child and merge in our handler instead of wrapping in another element.
|
|
29
|
+
// This prevents <button><button> nesting which causes React hydration errors.
|
|
30
|
+
const child = Array.isArray(children) ? children[0] : children;
|
|
31
|
+
const childIsInteractive =
|
|
32
|
+
child && typeof child === 'object' && 'type' in child &&
|
|
33
|
+
(child.type === 'button' || child.type === 'a');
|
|
34
|
+
|
|
35
|
+
if ((asChild || childIsInteractive) && child && typeof child === 'object' && 'props' in child) {
|
|
36
|
+
return (
|
|
37
|
+
<child.type
|
|
38
|
+
{...child.props}
|
|
39
|
+
onClick={(e) => {
|
|
40
|
+
child.props.onClick?.(e);
|
|
41
|
+
handleClick(e);
|
|
42
|
+
}}
|
|
43
|
+
aria-expanded={open}
|
|
44
|
+
data-state={open ? 'open' : 'closed'}
|
|
45
|
+
/>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Default: render a semantically neutral <span> (not <button>) so callers
|
|
50
|
+
// that pass <button> children never produce invalid <button><button> nesting.
|
|
51
|
+
return (
|
|
52
|
+
<span
|
|
53
|
+
role="button"
|
|
54
|
+
tabIndex={0}
|
|
55
|
+
onClick={handleClick}
|
|
56
|
+
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') handleClick(e); }}
|
|
57
|
+
aria-expanded={open}
|
|
58
|
+
data-state={open ? 'open' : 'closed'}
|
|
59
|
+
{...props}
|
|
60
|
+
>
|
|
61
|
+
{children}
|
|
62
|
+
</span>
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function DropdownMenuContent({ children, className, align = 'start', side = 'bottom', sideOffset = 4, ...props }) {
|
|
67
|
+
const { open, onOpenChange } = useContext(DropdownContext);
|
|
68
|
+
const ref = useRef(null);
|
|
69
|
+
|
|
70
|
+
useEffect(() => {
|
|
71
|
+
if (!open) return;
|
|
72
|
+
const handleClickOutside = (e) => {
|
|
73
|
+
if (ref.current && !ref.current.contains(e.target)) {
|
|
74
|
+
onOpenChange(false);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const handleEsc = (e) => {
|
|
78
|
+
if (e.key === 'Escape') onOpenChange(false);
|
|
79
|
+
};
|
|
80
|
+
setTimeout(() => document.addEventListener('click', handleClickOutside), 0);
|
|
81
|
+
document.addEventListener('keydown', handleEsc);
|
|
82
|
+
return () => {
|
|
83
|
+
document.removeEventListener('click', handleClickOutside);
|
|
84
|
+
document.removeEventListener('keydown', handleEsc);
|
|
85
|
+
};
|
|
86
|
+
}, [open, onOpenChange]);
|
|
87
|
+
|
|
88
|
+
if (!open) return null;
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<div
|
|
92
|
+
ref={ref}
|
|
93
|
+
className={cn(
|
|
94
|
+
'absolute z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-background/80 backdrop-blur-sm p-1 text-foreground shadow-lg',
|
|
95
|
+
side === 'bottom' && `top-full mt-1`,
|
|
96
|
+
side === 'top' && `bottom-full mb-1`,
|
|
97
|
+
align === 'end' && 'right-0',
|
|
98
|
+
align === 'start' && 'left-0',
|
|
99
|
+
className
|
|
100
|
+
)}
|
|
101
|
+
{...props}
|
|
102
|
+
>
|
|
103
|
+
{children}
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export function DropdownMenuItem({ children, className, onClick, asChild, ...props }) {
|
|
109
|
+
const { onOpenChange } = useContext(DropdownContext);
|
|
110
|
+
const handleClick = (e) => {
|
|
111
|
+
onClick?.(e);
|
|
112
|
+
onOpenChange(false);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// When asChild=true, clone the single child element and merge in our
|
|
116
|
+
// click handler + className instead of wrapping in a <div>.
|
|
117
|
+
// This prevents React from seeing `asChild` as an unknown DOM attribute.
|
|
118
|
+
if (asChild && children) {
|
|
119
|
+
const child = Array.isArray(children) ? children[0] : children;
|
|
120
|
+
if (child && typeof child === 'object' && 'props' in child) {
|
|
121
|
+
return (
|
|
122
|
+
<child.type
|
|
123
|
+
{...child.props}
|
|
124
|
+
className={cn(
|
|
125
|
+
'relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-background focus:bg-background',
|
|
126
|
+
child.props.className,
|
|
127
|
+
className
|
|
128
|
+
)}
|
|
129
|
+
onClick={(e) => {
|
|
130
|
+
child.props.onClick?.(e);
|
|
131
|
+
handleClick(e);
|
|
132
|
+
}}
|
|
133
|
+
role="menuitem"
|
|
134
|
+
/>
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<div
|
|
141
|
+
role="menuitem"
|
|
142
|
+
className={cn(
|
|
143
|
+
'relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-background focus:bg-background',
|
|
144
|
+
className
|
|
145
|
+
)}
|
|
146
|
+
onClick={handleClick}
|
|
147
|
+
{...props}
|
|
148
|
+
>
|
|
149
|
+
{children}
|
|
150
|
+
</div>
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function DropdownMenuSeparator({ className }) {
|
|
155
|
+
return <div className={cn('-mx-1 my-1 h-px bg-border', className)} />;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function DropdownMenuLabel({ children, className }) {
|
|
159
|
+
return (
|
|
160
|
+
<div className={cn('px-2 py-1.5 text-sm font-semibold', className)}>
|
|
161
|
+
{children}
|
|
162
|
+
</div>
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function DropdownMenuGroup({ children }) {
|
|
167
|
+
return <div>{children}</div>;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// ─── Submenu ─────────────────────────────────────────────────────────────────
|
|
171
|
+
const SubContext = createContext({ open: false, setOpen: () => {} });
|
|
172
|
+
|
|
173
|
+
export function DropdownMenuSub({ children }) {
|
|
174
|
+
const [open, setOpen] = useState(false);
|
|
175
|
+
return (
|
|
176
|
+
<SubContext.Provider value={{ open, setOpen }}>
|
|
177
|
+
<div className="relative">{children}</div>
|
|
178
|
+
</SubContext.Provider>
|
|
179
|
+
);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function DropdownMenuSubTrigger({ children, className }) {
|
|
183
|
+
const { open, setOpen } = useContext(SubContext);
|
|
184
|
+
return (
|
|
185
|
+
<div
|
|
186
|
+
role="menuitem"
|
|
187
|
+
aria-haspopup="menu"
|
|
188
|
+
aria-expanded={open}
|
|
189
|
+
className={cn(
|
|
190
|
+
'relative flex cursor-pointer select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors hover:bg-background focus:bg-background justify-between',
|
|
191
|
+
className
|
|
192
|
+
)}
|
|
193
|
+
onClick={(e) => { e.stopPropagation(); setOpen((v) => !v); }}
|
|
194
|
+
>
|
|
195
|
+
<span className="flex items-center gap-2">{children}</span>
|
|
196
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2} strokeLinecap="round" strokeLinejoin="round"><polyline points="9 18 15 12 9 6" /></svg>
|
|
197
|
+
</div>
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function DropdownMenuSubContent({ children, className }) {
|
|
202
|
+
const { open } = useContext(SubContext);
|
|
203
|
+
if (!open) return null;
|
|
204
|
+
return (
|
|
205
|
+
<div
|
|
206
|
+
role="menu"
|
|
207
|
+
className={cn(
|
|
208
|
+
'absolute left-full top-0 z-50 min-w-[8rem] overflow-hidden rounded-md border border-border bg-background/80 backdrop-blur-sm p-1 text-foreground shadow-lg ml-1',
|
|
209
|
+
className
|
|
210
|
+
)}
|
|
211
|
+
>
|
|
212
|
+
{children}
|
|
213
|
+
</div>
|
|
214
|
+
);
|
|
215
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect, useRef } from "react";
|
|
4
|
+
import { createPortal } from "react-dom";
|
|
5
|
+
function RenameDialog({ open, onSave, onCancel, title = "Rename chat", currentValue = "" }) {
|
|
6
|
+
const [value, setValue] = useState(currentValue);
|
|
7
|
+
const inputRef = useRef(null);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
if (open) {
|
|
10
|
+
setValue(currentValue);
|
|
11
|
+
setTimeout(() => {
|
|
12
|
+
if (inputRef.current) {
|
|
13
|
+
inputRef.current.focus();
|
|
14
|
+
inputRef.current.select();
|
|
15
|
+
}
|
|
16
|
+
}, 0);
|
|
17
|
+
}
|
|
18
|
+
}, [open, currentValue]);
|
|
19
|
+
useEffect(() => {
|
|
20
|
+
if (!open) return;
|
|
21
|
+
const handleEsc = (e) => {
|
|
22
|
+
if (e.key === "Escape") onCancel();
|
|
23
|
+
};
|
|
24
|
+
document.addEventListener("keydown", handleEsc);
|
|
25
|
+
return () => document.removeEventListener("keydown", handleEsc);
|
|
26
|
+
}, [open, onCancel]);
|
|
27
|
+
const handleSave = () => {
|
|
28
|
+
const trimmed = value.trim();
|
|
29
|
+
if (trimmed && trimmed !== currentValue) {
|
|
30
|
+
onSave(trimmed);
|
|
31
|
+
}
|
|
32
|
+
onCancel();
|
|
33
|
+
};
|
|
34
|
+
if (!open) return null;
|
|
35
|
+
return createPortal(
|
|
36
|
+
/* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 flex items-center justify-center", children: [
|
|
37
|
+
/* @__PURE__ */ jsx("div", { className: "fixed inset-0 bg-black/50", onClick: onCancel }),
|
|
38
|
+
/* @__PURE__ */ jsxs("div", { className: "relative z-50 w-full max-w-sm rounded-lg border border-border bg-background p-6 shadow-lg", onClick: (e) => e.stopPropagation(), children: [
|
|
39
|
+
/* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold", children: title }),
|
|
40
|
+
/* @__PURE__ */ jsx(
|
|
41
|
+
"input",
|
|
42
|
+
{
|
|
43
|
+
ref: inputRef,
|
|
44
|
+
type: "text",
|
|
45
|
+
value,
|
|
46
|
+
onChange: (e) => setValue(e.target.value),
|
|
47
|
+
onKeyDown: (e) => {
|
|
48
|
+
if (e.key === "Enter") handleSave();
|
|
49
|
+
},
|
|
50
|
+
className: "mt-3 w-full rounded-md border border-input bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
51
|
+
}
|
|
52
|
+
),
|
|
53
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-4 flex justify-end gap-2", children: [
|
|
54
|
+
/* @__PURE__ */ jsx(
|
|
55
|
+
"button",
|
|
56
|
+
{
|
|
57
|
+
onClick: onCancel,
|
|
58
|
+
className: "rounded-md px-3 py-1.5 text-sm font-medium border border-input bg-background hover:bg-muted",
|
|
59
|
+
children: "Cancel"
|
|
60
|
+
}
|
|
61
|
+
),
|
|
62
|
+
/* @__PURE__ */ jsx(
|
|
63
|
+
"button",
|
|
64
|
+
{
|
|
65
|
+
onClick: handleSave,
|
|
66
|
+
className: "rounded-md px-3 py-1.5 text-sm font-medium text-white bg-foreground hover:bg-foreground/90",
|
|
67
|
+
children: "Save"
|
|
68
|
+
}
|
|
69
|
+
)
|
|
70
|
+
] })
|
|
71
|
+
] })
|
|
72
|
+
] }),
|
|
73
|
+
document.body
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
export {
|
|
77
|
+
RenameDialog
|
|
78
|
+
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useRef } from 'react';
|
|
4
|
+
import { createPortal } from 'react-dom';
|
|
5
|
+
|
|
6
|
+
export function RenameDialog({ open, onSave, onCancel, title = 'Rename chat', currentValue = '' }) {
|
|
7
|
+
const [value, setValue] = useState(currentValue);
|
|
8
|
+
const inputRef = useRef(null);
|
|
9
|
+
|
|
10
|
+
useEffect(() => {
|
|
11
|
+
if (open) {
|
|
12
|
+
setValue(currentValue);
|
|
13
|
+
setTimeout(() => {
|
|
14
|
+
if (inputRef.current) {
|
|
15
|
+
inputRef.current.focus();
|
|
16
|
+
inputRef.current.select();
|
|
17
|
+
}
|
|
18
|
+
}, 0);
|
|
19
|
+
}
|
|
20
|
+
}, [open, currentValue]);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
if (!open) return;
|
|
24
|
+
const handleEsc = (e) => {
|
|
25
|
+
if (e.key === 'Escape') onCancel();
|
|
26
|
+
};
|
|
27
|
+
document.addEventListener('keydown', handleEsc);
|
|
28
|
+
return () => document.removeEventListener('keydown', handleEsc);
|
|
29
|
+
}, [open, onCancel]);
|
|
30
|
+
|
|
31
|
+
const handleSave = () => {
|
|
32
|
+
const trimmed = value.trim();
|
|
33
|
+
if (trimmed && trimmed !== currentValue) {
|
|
34
|
+
onSave(trimmed);
|
|
35
|
+
}
|
|
36
|
+
onCancel();
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
if (!open) return null;
|
|
40
|
+
|
|
41
|
+
return createPortal(
|
|
42
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
43
|
+
<div className="fixed inset-0 bg-black/50" onClick={onCancel} />
|
|
44
|
+
<div className="relative z-50 w-full max-w-sm rounded-lg border border-border bg-background p-6 shadow-lg" onClick={(e) => e.stopPropagation()}>
|
|
45
|
+
<h3 className="text-lg font-semibold">{title}</h3>
|
|
46
|
+
<input
|
|
47
|
+
ref={inputRef}
|
|
48
|
+
type="text"
|
|
49
|
+
value={value}
|
|
50
|
+
onChange={(e) => setValue(e.target.value)}
|
|
51
|
+
onKeyDown={(e) => {
|
|
52
|
+
if (e.key === 'Enter') handleSave();
|
|
53
|
+
}}
|
|
54
|
+
className="mt-3 w-full rounded-md border border-input bg-background px-3 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
|
|
55
|
+
/>
|
|
56
|
+
<div className="mt-4 flex justify-end gap-2">
|
|
57
|
+
<button
|
|
58
|
+
onClick={onCancel}
|
|
59
|
+
className="rounded-md px-3 py-1.5 text-sm font-medium border border-input bg-background hover:bg-muted"
|
|
60
|
+
>
|
|
61
|
+
Cancel
|
|
62
|
+
</button>
|
|
63
|
+
<button
|
|
64
|
+
onClick={handleSave}
|
|
65
|
+
className="rounded-md px-3 py-1.5 text-sm font-medium text-white bg-foreground hover:bg-foreground/90"
|
|
66
|
+
>
|
|
67
|
+
Save
|
|
68
|
+
</button>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
</div>,
|
|
72
|
+
document.body
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from "../../utils.js";
|
|
4
|
+
function ScrollArea({ children, className, ...props }) {
|
|
5
|
+
return /* @__PURE__ */ jsx("div", { className: cn("relative overflow-hidden", className), ...props, children: /* @__PURE__ */ jsx("div", { className: "h-full w-full overflow-y-auto scrollbar-thin", children }) });
|
|
6
|
+
}
|
|
7
|
+
function ScrollBar() {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
export {
|
|
11
|
+
ScrollArea,
|
|
12
|
+
ScrollBar
|
|
13
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../utils.js';
|
|
4
|
+
|
|
5
|
+
export function ScrollArea({ children, className, ...props }) {
|
|
6
|
+
return (
|
|
7
|
+
<div className={cn('relative overflow-hidden', className)} {...props}>
|
|
8
|
+
<div className="h-full w-full overflow-y-auto scrollbar-thin">
|
|
9
|
+
{children}
|
|
10
|
+
</div>
|
|
11
|
+
</div>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function ScrollBar() {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { cn } from "../../utils.js";
|
|
4
|
+
function Separator({ className, orientation = "horizontal", ...props }) {
|
|
5
|
+
return /* @__PURE__ */ jsx(
|
|
6
|
+
"div",
|
|
7
|
+
{
|
|
8
|
+
role: "separator",
|
|
9
|
+
"aria-orientation": orientation,
|
|
10
|
+
className: cn(
|
|
11
|
+
"shrink-0 bg-border",
|
|
12
|
+
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
|
13
|
+
className
|
|
14
|
+
),
|
|
15
|
+
...props
|
|
16
|
+
}
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
Separator
|
|
21
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { cn } from '../../utils.js';
|
|
4
|
+
|
|
5
|
+
export function Separator({ className, orientation = 'horizontal', ...props }) {
|
|
6
|
+
return (
|
|
7
|
+
<div
|
|
8
|
+
role="separator"
|
|
9
|
+
aria-orientation={orientation}
|
|
10
|
+
className={cn(
|
|
11
|
+
'shrink-0 bg-border',
|
|
12
|
+
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
|
|
13
|
+
className
|
|
14
|
+
)}
|
|
15
|
+
{...props}
|
|
16
|
+
/>
|
|
17
|
+
);
|
|
18
|
+
}
|