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.
Files changed (249) hide show
  1. package/LICENSE +26 -0
  2. package/README.md +237 -0
  3. package/api/CLAUDE.md +19 -0
  4. package/api/index.js +265 -0
  5. package/bin/cli.js +823 -0
  6. package/bin/local.sh +85 -0
  7. package/bin/postinstall.js +63 -0
  8. package/config/index.js +26 -0
  9. package/config/instrumentation.js +62 -0
  10. package/drizzle/0000_initial.sql +52 -0
  11. package/drizzle/0001_nostalgic_sersi.sql +11 -0
  12. package/drizzle/0002_black_daimon_hellstrom.sql +19 -0
  13. package/drizzle/0003_rename_code_workspaces.sql +5 -0
  14. package/drizzle/meta/0000_snapshot.json +321 -0
  15. package/drizzle/meta/0001_snapshot.json +390 -0
  16. package/drizzle/meta/0002_snapshot.json +411 -0
  17. package/drizzle/meta/0003_snapshot.json +419 -0
  18. package/drizzle/meta/_journal.json +34 -0
  19. package/lib/actions.js +44 -0
  20. package/lib/ai/agent.js +86 -0
  21. package/lib/ai/index.js +342 -0
  22. package/lib/ai/model.js +180 -0
  23. package/lib/ai/tools.js +269 -0
  24. package/lib/ai/web-search.js +42 -0
  25. package/lib/auth/actions.js +28 -0
  26. package/lib/auth/config.js +27 -0
  27. package/lib/auth/edge-config.js +27 -0
  28. package/lib/auth/index.js +27 -0
  29. package/lib/auth/middleware.js +62 -0
  30. package/lib/channels/base.js +56 -0
  31. package/lib/channels/index.js +15 -0
  32. package/lib/channels/telegram.js +148 -0
  33. package/lib/chat/actions.js +579 -0
  34. package/lib/chat/api.js +140 -0
  35. package/lib/chat/components/app-sidebar.js +213 -0
  36. package/lib/chat/components/app-sidebar.jsx +279 -0
  37. package/lib/chat/components/chat-header.js +192 -0
  38. package/lib/chat/components/chat-header.jsx +223 -0
  39. package/lib/chat/components/chat-input.js +236 -0
  40. package/lib/chat/components/chat-input.jsx +249 -0
  41. package/lib/chat/components/chat-nav-context.js +11 -0
  42. package/lib/chat/components/chat-nav-context.jsx +11 -0
  43. package/lib/chat/components/chat-page.js +99 -0
  44. package/lib/chat/components/chat-page.jsx +121 -0
  45. package/lib/chat/components/chat.js +153 -0
  46. package/lib/chat/components/chat.jsx +199 -0
  47. package/lib/chat/components/chats-page.js +367 -0
  48. package/lib/chat/components/chats-page.jsx +394 -0
  49. package/lib/chat/components/code-mode-toggle.js +132 -0
  50. package/lib/chat/components/code-mode-toggle.jsx +163 -0
  51. package/lib/chat/components/crons-page.js +172 -0
  52. package/lib/chat/components/crons-page.jsx +244 -0
  53. package/lib/chat/components/greeting.js +11 -0
  54. package/lib/chat/components/greeting.jsx +16 -0
  55. package/lib/chat/components/icons.js +805 -0
  56. package/lib/chat/components/icons.jsx +751 -0
  57. package/lib/chat/components/index.js +20 -0
  58. package/lib/chat/components/message.js +363 -0
  59. package/lib/chat/components/message.jsx +422 -0
  60. package/lib/chat/components/messages.js +65 -0
  61. package/lib/chat/components/messages.jsx +74 -0
  62. package/lib/chat/components/notifications-page.js +56 -0
  63. package/lib/chat/components/notifications-page.jsx +87 -0
  64. package/lib/chat/components/page-layout.js +21 -0
  65. package/lib/chat/components/page-layout.jsx +28 -0
  66. package/lib/chat/components/pull-requests-page.js +103 -0
  67. package/lib/chat/components/pull-requests-page.jsx +113 -0
  68. package/lib/chat/components/settings-layout.js +39 -0
  69. package/lib/chat/components/settings-layout.jsx +53 -0
  70. package/lib/chat/components/settings-secrets-page.js +216 -0
  71. package/lib/chat/components/settings-secrets-page.jsx +264 -0
  72. package/lib/chat/components/sidebar-history-item.js +138 -0
  73. package/lib/chat/components/sidebar-history-item.jsx +119 -0
  74. package/lib/chat/components/sidebar-history.js +167 -0
  75. package/lib/chat/components/sidebar-history.jsx +220 -0
  76. package/lib/chat/components/sidebar-user-nav.js +61 -0
  77. package/lib/chat/components/sidebar-user-nav.jsx +77 -0
  78. package/lib/chat/components/swarm-page.js +157 -0
  79. package/lib/chat/components/swarm-page.jsx +210 -0
  80. package/lib/chat/components/tool-call.js +89 -0
  81. package/lib/chat/components/tool-call.jsx +107 -0
  82. package/lib/chat/components/triggers-page.js +153 -0
  83. package/lib/chat/components/triggers-page.jsx +221 -0
  84. package/lib/chat/components/ui/combobox.js +98 -0
  85. package/lib/chat/components/ui/combobox.jsx +114 -0
  86. package/lib/chat/components/ui/confirm-dialog.js +53 -0
  87. package/lib/chat/components/ui/confirm-dialog.jsx +57 -0
  88. package/lib/chat/components/ui/dropdown-menu.js +194 -0
  89. package/lib/chat/components/ui/dropdown-menu.jsx +215 -0
  90. package/lib/chat/components/ui/rename-dialog.js +78 -0
  91. package/lib/chat/components/ui/rename-dialog.jsx +74 -0
  92. package/lib/chat/components/ui/scroll-area.js +13 -0
  93. package/lib/chat/components/ui/scroll-area.jsx +17 -0
  94. package/lib/chat/components/ui/separator.js +21 -0
  95. package/lib/chat/components/ui/separator.jsx +18 -0
  96. package/lib/chat/components/ui/sheet.js +75 -0
  97. package/lib/chat/components/ui/sheet.jsx +95 -0
  98. package/lib/chat/components/ui/sidebar.js +228 -0
  99. package/lib/chat/components/ui/sidebar.jsx +246 -0
  100. package/lib/chat/components/ui/tooltip.js +56 -0
  101. package/lib/chat/components/ui/tooltip.jsx +66 -0
  102. package/lib/chat/components/upgrade-dialog.js +151 -0
  103. package/lib/chat/components/upgrade-dialog.jsx +170 -0
  104. package/lib/chat/utils.js +11 -0
  105. package/lib/code/actions.js +153 -0
  106. package/lib/code/code-page.js +22 -0
  107. package/lib/code/code-page.jsx +25 -0
  108. package/lib/code/index.js +1 -0
  109. package/lib/code/terminal-view.js +201 -0
  110. package/lib/code/terminal-view.jsx +224 -0
  111. package/lib/code/ws-proxy.js +80 -0
  112. package/lib/cron.js +246 -0
  113. package/lib/db/api-keys.js +163 -0
  114. package/lib/db/chats.js +168 -0
  115. package/lib/db/code-workspaces.js +110 -0
  116. package/lib/db/index.js +52 -0
  117. package/lib/db/notifications.js +99 -0
  118. package/lib/db/schema.js +66 -0
  119. package/lib/db/update-check.js +96 -0
  120. package/lib/db/users.js +89 -0
  121. package/lib/paths.js +42 -0
  122. package/lib/tools/create-job.js +97 -0
  123. package/lib/tools/docker.js +146 -0
  124. package/lib/tools/github.js +271 -0
  125. package/lib/tools/openai.js +35 -0
  126. package/lib/tools/telegram.js +292 -0
  127. package/lib/triggers.js +104 -0
  128. package/lib/utils/render-md.js +111 -0
  129. package/package.json +118 -0
  130. package/setup/lib/auth.mjs +81 -0
  131. package/setup/lib/env.mjs +21 -0
  132. package/setup/lib/fs-utils.mjs +20 -0
  133. package/setup/lib/github.mjs +149 -0
  134. package/setup/lib/prerequisites.mjs +155 -0
  135. package/setup/lib/prompts.mjs +267 -0
  136. package/setup/lib/providers.mjs +105 -0
  137. package/setup/lib/sync.mjs +125 -0
  138. package/setup/lib/targets.mjs +45 -0
  139. package/setup/lib/telegram-verify.mjs +63 -0
  140. package/setup/lib/telegram.mjs +76 -0
  141. package/setup/setup-cloud.mjs +833 -0
  142. package/setup/setup-local.mjs +377 -0
  143. package/setup/setup-telegram.mjs +265 -0
  144. package/setup/setup.mjs +87 -0
  145. package/templates/.dockerignore +5 -0
  146. package/templates/.env.example +104 -0
  147. package/templates/.github/workflows/auto-merge.yml +117 -0
  148. package/templates/.github/workflows/notify-job-failed.yml +64 -0
  149. package/templates/.github/workflows/notify-pr-complete.yml +119 -0
  150. package/templates/.github/workflows/rebuild-event-handler.yml +121 -0
  151. package/templates/.github/workflows/run-job.yml +89 -0
  152. package/templates/.github/workflows/upgrade-event-handler.yml +62 -0
  153. package/templates/.gitignore.template +45 -0
  154. package/templates/.pi/extensions/env-sanitizer/index.ts +48 -0
  155. package/templates/.pi/extensions/env-sanitizer/package.json +5 -0
  156. package/templates/CLAUDE.md +29 -0
  157. package/templates/CLAUDE.md.template +308 -0
  158. package/templates/app/api/[...gigaclaw]/route.js +1 -0
  159. package/templates/app/api/auth/[...nextauth]/route.js +1 -0
  160. package/templates/app/chat/[chatId]/page.js +9 -0
  161. package/templates/app/chats/page.js +7 -0
  162. package/templates/app/code/[codeWorkspaceId]/page.js +9 -0
  163. package/templates/app/components/ascii-logo.jsx +12 -0
  164. package/templates/app/components/login-form.jsx +92 -0
  165. package/templates/app/components/setup-form.jsx +82 -0
  166. package/templates/app/components/theme-provider.jsx +11 -0
  167. package/templates/app/components/theme-toggle.jsx +38 -0
  168. package/templates/app/components/ui/button.jsx +21 -0
  169. package/templates/app/components/ui/card.jsx +23 -0
  170. package/templates/app/components/ui/input.jsx +10 -0
  171. package/templates/app/components/ui/label.jsx +10 -0
  172. package/templates/app/crons/page.js +5 -0
  173. package/templates/app/globals.css +90 -0
  174. package/templates/app/layout.js +33 -0
  175. package/templates/app/login/page.js +15 -0
  176. package/templates/app/notifications/page.js +7 -0
  177. package/templates/app/page.js +7 -0
  178. package/templates/app/pull-requests/page.js +7 -0
  179. package/templates/app/settings/crons/page.js +5 -0
  180. package/templates/app/settings/layout.js +7 -0
  181. package/templates/app/settings/page.js +5 -0
  182. package/templates/app/settings/secrets/page.js +5 -0
  183. package/templates/app/settings/triggers/page.js +5 -0
  184. package/templates/app/stream/chat/route.js +1 -0
  185. package/templates/app/swarm/page.js +7 -0
  186. package/templates/app/triggers/page.js +5 -0
  187. package/templates/config/CODE_PLANNING.md +14 -0
  188. package/templates/config/CRONS.json +56 -0
  189. package/templates/config/HEARTBEAT.md +3 -0
  190. package/templates/config/JOB_AGENT.md +30 -0
  191. package/templates/config/JOB_PLANNING.md +240 -0
  192. package/templates/config/JOB_SUMMARY.md +130 -0
  193. package/templates/config/SKILL_BUILDING_GUIDE.md +96 -0
  194. package/templates/config/SOUL.md +48 -0
  195. package/templates/config/TRIGGERS.json +58 -0
  196. package/templates/config/WEB_SEARCH_AVAILABLE.md +5 -0
  197. package/templates/config/WEB_SEARCH_UNAVAILABLE.md +3 -0
  198. package/templates/docker/claude-code-job/Dockerfile +34 -0
  199. package/templates/docker/claude-code-job/entrypoint.sh +149 -0
  200. package/templates/docker/claude-code-workspace/.tmux.conf +5 -0
  201. package/templates/docker/claude-code-workspace/Dockerfile +61 -0
  202. package/templates/docker/claude-code-workspace/entrypoint.sh +51 -0
  203. package/templates/docker/event-handler/Dockerfile +20 -0
  204. package/templates/docker/event-handler/ecosystem.config.cjs +7 -0
  205. package/templates/docker/pi-coding-agent-job/Dockerfile +51 -0
  206. package/templates/docker/pi-coding-agent-job/entrypoint.sh +164 -0
  207. package/templates/docker-compose.local.yml +78 -0
  208. package/templates/docker-compose.yml +64 -0
  209. package/templates/instrumentation.js +6 -0
  210. package/templates/middleware.js +23 -0
  211. package/templates/next.config.mjs +3 -0
  212. package/templates/postcss.config.mjs +5 -0
  213. package/templates/public/favicon.ico +0 -0
  214. package/templates/server.js +25 -0
  215. package/templates/skills/LICENSE +21 -0
  216. package/templates/skills/README.md +119 -0
  217. package/templates/skills/brave-search/SKILL.md +79 -0
  218. package/templates/skills/brave-search/content.js +86 -0
  219. package/templates/skills/brave-search/package-lock.json +621 -0
  220. package/templates/skills/brave-search/package.json +14 -0
  221. package/templates/skills/brave-search/search.js +199 -0
  222. package/templates/skills/browser-tools/SKILL.md +196 -0
  223. package/templates/skills/browser-tools/browser-content.js +103 -0
  224. package/templates/skills/browser-tools/browser-cookies.js +35 -0
  225. package/templates/skills/browser-tools/browser-eval.js +53 -0
  226. package/templates/skills/browser-tools/browser-hn-scraper.js +108 -0
  227. package/templates/skills/browser-tools/browser-nav.js +44 -0
  228. package/templates/skills/browser-tools/browser-pick.js +162 -0
  229. package/templates/skills/browser-tools/browser-screenshot.js +34 -0
  230. package/templates/skills/browser-tools/browser-start.js +87 -0
  231. package/templates/skills/browser-tools/package-lock.json +2556 -0
  232. package/templates/skills/browser-tools/package.json +19 -0
  233. package/templates/skills/google-docs/SKILL.md +23 -0
  234. package/templates/skills/google-docs/create.sh +69 -0
  235. package/templates/skills/google-drive/SKILL.md +47 -0
  236. package/templates/skills/google-drive/delete.sh +47 -0
  237. package/templates/skills/google-drive/download.sh +50 -0
  238. package/templates/skills/google-drive/list.sh +41 -0
  239. package/templates/skills/google-drive/upload.sh +76 -0
  240. package/templates/skills/kie-ai/SKILL.md +38 -0
  241. package/templates/skills/kie-ai/generate-image.sh +77 -0
  242. package/templates/skills/kie-ai/generate-video.sh +69 -0
  243. package/templates/skills/llm-secrets/SKILL.md +34 -0
  244. package/templates/skills/llm-secrets/llm-secrets.js +33 -0
  245. package/templates/skills/modify-self/SKILL.md +12 -0
  246. package/templates/skills/youtube-transcript/SKILL.md +41 -0
  247. package/templates/skills/youtube-transcript/package-lock.json +24 -0
  248. package/templates/skills/youtube-transcript/package.json +8 -0
  249. package/templates/skills/youtube-transcript/transcript.js +84 -0
@@ -0,0 +1,192 @@
1
+ "use client";
2
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
+ import { useState, useEffect, useCallback, useRef } from "react";
4
+ import { SidebarTrigger } from "./ui/sidebar.js";
5
+ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent } from "./ui/dropdown-menu.js";
6
+ import { ConfirmDialog } from "./ui/confirm-dialog.js";
7
+ import { RenameDialog } from "./ui/rename-dialog.jsx";
8
+ import { ChevronDownIcon, StarIcon, StarFilledIcon, PencilIcon, TrashIcon, ExportIcon } from "./icons.js";
9
+ import { getChatMeta, getChatMetaByWorkspace, renameChat, deleteChat, starChat, exportChat } from "../actions.js";
10
+ import { useChatNav } from "./chat-nav-context.js";
11
+ function ChatHeader({ chatId: chatIdProp, workspaceId }) {
12
+ const [title, setTitle] = useState(null);
13
+ const [starred, setStarred] = useState(0);
14
+ const [resolvedChatId, setResolvedChatId] = useState(chatIdProp || null);
15
+ const [isEditing, setIsEditing] = useState(false);
16
+ const [editValue, setEditValue] = useState("");
17
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
18
+ const [showRenameDialog, setShowRenameDialog] = useState(false);
19
+ const [isExporting, setIsExporting] = useState(false);
20
+ const inputRef = useRef(null);
21
+ const nav = useChatNav();
22
+ const chatId = resolvedChatId;
23
+ const showControls = chatId && title && title !== "New Chat";
24
+ const fetchMeta = useCallback(() => {
25
+ if (workspaceId) {
26
+ getChatMetaByWorkspace(workspaceId).then((meta) => {
27
+ if (meta?.title && meta.title !== "New Chat") {
28
+ setTitle(meta.title);
29
+ setStarred(meta.starred || 0);
30
+ setResolvedChatId(meta.chatId);
31
+ }
32
+ }).catch(() => {
33
+ });
34
+ return;
35
+ }
36
+ if (!chatIdProp) return;
37
+ getChatMeta(chatIdProp).then((meta) => {
38
+ if (meta?.title && meta.title !== "New Chat") {
39
+ setTitle(meta.title);
40
+ setStarred(meta.starred || 0);
41
+ }
42
+ }).catch(() => {
43
+ });
44
+ }, [chatIdProp, workspaceId]);
45
+ useEffect(() => {
46
+ fetchMeta();
47
+ const handler = () => fetchMeta();
48
+ window.addEventListener("chatsupdated", handler);
49
+ return () => window.removeEventListener("chatsupdated", handler);
50
+ }, [fetchMeta]);
51
+ useEffect(() => {
52
+ if (isEditing && inputRef.current) {
53
+ inputRef.current.focus();
54
+ inputRef.current.select();
55
+ }
56
+ }, [isEditing]);
57
+ const enterEditMode = () => {
58
+ setEditValue(title || "");
59
+ setIsEditing(true);
60
+ };
61
+ const saveEdit = async () => {
62
+ setIsEditing(false);
63
+ const trimmed = editValue.trim();
64
+ if (!trimmed || trimmed === title) return;
65
+ setTitle(trimmed);
66
+ await renameChat(chatId, trimmed);
67
+ window.dispatchEvent(new Event("chatsupdated"));
68
+ };
69
+ const cancelEdit = () => {
70
+ setIsEditing(false);
71
+ };
72
+ const handleRenameFromDialog = async (newTitle) => {
73
+ setTitle(newTitle);
74
+ await renameChat(chatId, newTitle);
75
+ window.dispatchEvent(new Event("chatsupdated"));
76
+ };
77
+ const handleStar = async () => {
78
+ const newStarred = starred ? 0 : 1;
79
+ setStarred(newStarred);
80
+ await starChat(chatId);
81
+ window.dispatchEvent(new Event("chatsupdated"));
82
+ };
83
+ const handleExport = async (format) => {
84
+ if (!chatId || isExporting) return;
85
+ setIsExporting(true);
86
+ try {
87
+ const { filename, content, mimeType } = await exportChat(chatId, format);
88
+ const blob = new Blob([content], { type: mimeType });
89
+ const url = URL.createObjectURL(blob);
90
+ const a = document.createElement("a");
91
+ a.href = url;
92
+ a.download = filename;
93
+ document.body.appendChild(a);
94
+ a.click();
95
+ document.body.removeChild(a);
96
+ URL.revokeObjectURL(url);
97
+ } catch (err) {
98
+ console.error("Export failed:", err);
99
+ } finally {
100
+ setIsExporting(false);
101
+ }
102
+ };
103
+ const handleDelete = async () => {
104
+ setShowDeleteConfirm(false);
105
+ await deleteChat(chatId);
106
+ window.dispatchEvent(new Event("chatsupdated"));
107
+ nav?.navigateToChat?.(null);
108
+ };
109
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
110
+ /* @__PURE__ */ jsxs("header", { className: "sticky top-0 flex items-center gap-2 bg-background px-2 py-1.5 md:px-2 z-10", children: [
111
+ /* @__PURE__ */ jsx("div", { className: "md:hidden", children: /* @__PURE__ */ jsx(SidebarTrigger, {}) }),
112
+ isEditing ? /* @__PURE__ */ jsx(
113
+ "input",
114
+ {
115
+ ref: inputRef,
116
+ type: "text",
117
+ value: editValue,
118
+ onChange: (e) => setEditValue(e.target.value),
119
+ onKeyDown: (e) => {
120
+ if (e.key === "Enter") saveEdit();
121
+ if (e.key === "Escape") cancelEdit();
122
+ },
123
+ onBlur: saveEdit,
124
+ className: "text-base font-medium text-foreground bg-background rounded-md border border-ring px-2 py-0.5 outline-none ring-2 ring-ring/30"
125
+ }
126
+ ) : showControls ? /* @__PURE__ */ jsxs("div", { className: "group/title flex items-center gap-0.5 rounded-md px-1.5 py-0.5 hover:bg-muted transition-colors", children: [
127
+ /* @__PURE__ */ jsx(
128
+ "h1",
129
+ {
130
+ className: "text-base font-medium text-muted-foreground truncate cursor-pointer",
131
+ onClick: enterEditMode,
132
+ children: title
133
+ }
134
+ ),
135
+ /* @__PURE__ */ jsxs(DropdownMenu, { children: [
136
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsx("button", { className: "flex items-center justify-center h-6 w-6 rounded text-muted-foreground shrink-0", "aria-label": "Chat options", children: /* @__PURE__ */ jsx(ChevronDownIcon, { size: 14 }) }) }),
137
+ /* @__PURE__ */ jsxs(DropdownMenuContent, { align: "start", children: [
138
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: handleStar, children: [
139
+ starred ? /* @__PURE__ */ jsx(StarFilledIcon, { size: 14 }) : /* @__PURE__ */ jsx(StarIcon, { size: 14 }),
140
+ /* @__PURE__ */ jsx("span", { children: starred ? "Unstar" : "Star" })
141
+ ] }),
142
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: () => setShowRenameDialog(true), children: [
143
+ /* @__PURE__ */ jsx(PencilIcon, { size: 14 }),
144
+ /* @__PURE__ */ jsx("span", { children: "Rename" })
145
+ ] }),
146
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
147
+ /* @__PURE__ */ jsxs(DropdownMenuSub, { children: [
148
+ /* @__PURE__ */ jsxs(DropdownMenuSubTrigger, { children: [
149
+ /* @__PURE__ */ jsx(ExportIcon, { size: 14 }),
150
+ /* @__PURE__ */ jsx("span", { children: isExporting ? "Exporting\u2026" : "Export" })
151
+ ] }),
152
+ /* @__PURE__ */ jsxs(DropdownMenuSubContent, { children: [
153
+ /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => handleExport("md"), children: /* @__PURE__ */ jsx("span", { children: "Markdown (.md)" }) }),
154
+ /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => handleExport("txt"), children: /* @__PURE__ */ jsx("span", { children: "Plain Text (.txt)" }) }),
155
+ /* @__PURE__ */ jsx(DropdownMenuItem, { onClick: () => handleExport("json"), children: /* @__PURE__ */ jsx("span", { children: "JSON (.json)" }) })
156
+ ] })
157
+ ] }),
158
+ /* @__PURE__ */ jsx(DropdownMenuSeparator, {}),
159
+ /* @__PURE__ */ jsxs(DropdownMenuItem, { onClick: () => setShowDeleteConfirm(true), children: [
160
+ /* @__PURE__ */ jsx(TrashIcon, { size: 14 }),
161
+ /* @__PURE__ */ jsx("span", { className: "text-destructive", children: "Delete" })
162
+ ] })
163
+ ] })
164
+ ] })
165
+ ] }) : /* @__PURE__ */ jsx("h1", { className: "text-base font-medium text-muted-foreground truncate", children: title || "\xA0" })
166
+ ] }),
167
+ /* @__PURE__ */ jsx(
168
+ RenameDialog,
169
+ {
170
+ open: showRenameDialog,
171
+ onSave: handleRenameFromDialog,
172
+ onCancel: () => setShowRenameDialog(false),
173
+ title: "Rename chat",
174
+ currentValue: title || ""
175
+ }
176
+ ),
177
+ /* @__PURE__ */ jsx(
178
+ ConfirmDialog,
179
+ {
180
+ open: showDeleteConfirm,
181
+ onConfirm: handleDelete,
182
+ onCancel: () => setShowDeleteConfirm(false),
183
+ title: "Delete chat?",
184
+ description: "This will permanently delete this chat and all its messages.",
185
+ confirmLabel: "Delete"
186
+ }
187
+ )
188
+ ] });
189
+ }
190
+ export {
191
+ ChatHeader
192
+ };
@@ -0,0 +1,223 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect, useCallback, useRef } from 'react';
4
+ import { SidebarTrigger } from './ui/sidebar.js';
5
+ import { DropdownMenu, DropdownMenuTrigger, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent } from './ui/dropdown-menu.js';
6
+ import { ConfirmDialog } from './ui/confirm-dialog.js';
7
+ import { RenameDialog } from './ui/rename-dialog.jsx';
8
+ import { ChevronDownIcon, StarIcon, StarFilledIcon, PencilIcon, TrashIcon, ExportIcon } from './icons.js';
9
+ import { getChatMeta, getChatMetaByWorkspace, renameChat, deleteChat, starChat, exportChat } from '../actions.js';
10
+ import { useChatNav } from './chat-nav-context.js';
11
+
12
+ export function ChatHeader({ chatId: chatIdProp, workspaceId }) {
13
+ const [title, setTitle] = useState(null);
14
+ const [starred, setStarred] = useState(0);
15
+ const [resolvedChatId, setResolvedChatId] = useState(chatIdProp || null);
16
+ const [isEditing, setIsEditing] = useState(false);
17
+ const [editValue, setEditValue] = useState('');
18
+ const [showDeleteConfirm, setShowDeleteConfirm] = useState(false);
19
+ const [showRenameDialog, setShowRenameDialog] = useState(false);
20
+ const [isExporting, setIsExporting] = useState(false);
21
+ const inputRef = useRef(null);
22
+ const nav = useChatNav();
23
+
24
+ // The actual chatId to use for actions (either passed directly or resolved from workspace)
25
+ const chatId = resolvedChatId;
26
+
27
+ // Whether to show the dropdown and inline-edit features
28
+ const showControls = chatId && title && title !== 'New Chat';
29
+
30
+ const fetchMeta = useCallback(() => {
31
+ if (workspaceId) {
32
+ getChatMetaByWorkspace(workspaceId)
33
+ .then((meta) => {
34
+ if (meta?.title && meta.title !== 'New Chat') {
35
+ setTitle(meta.title);
36
+ setStarred(meta.starred || 0);
37
+ setResolvedChatId(meta.chatId);
38
+ }
39
+ })
40
+ .catch(() => {});
41
+ return;
42
+ }
43
+ if (!chatIdProp) return;
44
+ getChatMeta(chatIdProp)
45
+ .then((meta) => {
46
+ if (meta?.title && meta.title !== 'New Chat') {
47
+ setTitle(meta.title);
48
+ setStarred(meta.starred || 0);
49
+ }
50
+ })
51
+ .catch(() => {});
52
+ }, [chatIdProp, workspaceId]);
53
+
54
+ useEffect(() => {
55
+ fetchMeta();
56
+ const handler = () => fetchMeta();
57
+ window.addEventListener('chatsupdated', handler);
58
+ return () => window.removeEventListener('chatsupdated', handler);
59
+ }, [fetchMeta]);
60
+
61
+ // Auto-focus and select all when entering inline edit mode
62
+ useEffect(() => {
63
+ if (isEditing && inputRef.current) {
64
+ inputRef.current.focus();
65
+ inputRef.current.select();
66
+ }
67
+ }, [isEditing]);
68
+
69
+ const enterEditMode = () => {
70
+ setEditValue(title || '');
71
+ setIsEditing(true);
72
+ };
73
+
74
+ const saveEdit = async () => {
75
+ setIsEditing(false);
76
+ const trimmed = editValue.trim();
77
+ if (!trimmed || trimmed === title) return;
78
+ setTitle(trimmed);
79
+ await renameChat(chatId, trimmed);
80
+ window.dispatchEvent(new Event('chatsupdated'));
81
+ };
82
+
83
+ const cancelEdit = () => {
84
+ setIsEditing(false);
85
+ };
86
+
87
+ const handleRenameFromDialog = async (newTitle) => {
88
+ setTitle(newTitle);
89
+ await renameChat(chatId, newTitle);
90
+ window.dispatchEvent(new Event('chatsupdated'));
91
+ };
92
+
93
+ const handleStar = async () => {
94
+ const newStarred = starred ? 0 : 1;
95
+ setStarred(newStarred);
96
+ await starChat(chatId);
97
+ window.dispatchEvent(new Event('chatsupdated'));
98
+ };
99
+
100
+ const handleExport = async (format) => {
101
+ if (!chatId || isExporting) return;
102
+ setIsExporting(true);
103
+ try {
104
+ const { filename, content, mimeType } = await exportChat(chatId, format);
105
+ const blob = new Blob([content], { type: mimeType });
106
+ const url = URL.createObjectURL(blob);
107
+ const a = document.createElement('a');
108
+ a.href = url;
109
+ a.download = filename;
110
+ document.body.appendChild(a);
111
+ a.click();
112
+ document.body.removeChild(a);
113
+ URL.revokeObjectURL(url);
114
+ } catch (err) {
115
+ console.error('Export failed:', err);
116
+ } finally {
117
+ setIsExporting(false);
118
+ }
119
+ };
120
+
121
+ const handleDelete = async () => {
122
+ setShowDeleteConfirm(false);
123
+ await deleteChat(chatId);
124
+ window.dispatchEvent(new Event('chatsupdated'));
125
+ nav?.navigateToChat?.(null);
126
+ };
127
+
128
+ return (
129
+ <>
130
+ <header className="sticky top-0 flex items-center gap-2 bg-background px-2 py-1.5 md:px-2 z-10">
131
+ {/* Mobile-only: open sidebar sheet */}
132
+ <div className="md:hidden">
133
+ <SidebarTrigger />
134
+ </div>
135
+
136
+ {isEditing ? (
137
+ <input
138
+ ref={inputRef}
139
+ type="text"
140
+ value={editValue}
141
+ onChange={(e) => setEditValue(e.target.value)}
142
+ onKeyDown={(e) => {
143
+ if (e.key === 'Enter') saveEdit();
144
+ if (e.key === 'Escape') cancelEdit();
145
+ }}
146
+ onBlur={saveEdit}
147
+ className="text-base font-medium text-foreground bg-background rounded-md border border-ring px-2 py-0.5 outline-none ring-2 ring-ring/30"
148
+ />
149
+ ) : showControls ? (
150
+ <div className="group/title flex items-center gap-0.5 rounded-md px-1.5 py-0.5 hover:bg-muted transition-colors">
151
+ <h1
152
+ className="text-base font-medium text-muted-foreground truncate cursor-pointer"
153
+ onClick={enterEditMode}
154
+ >
155
+ {title}
156
+ </h1>
157
+ <DropdownMenu>
158
+ <DropdownMenuTrigger asChild>
159
+ <button className="flex items-center justify-center h-6 w-6 rounded text-muted-foreground shrink-0" aria-label="Chat options">
160
+ <ChevronDownIcon size={14} />
161
+ </button>
162
+ </DropdownMenuTrigger>
163
+ <DropdownMenuContent align="start">
164
+ <DropdownMenuItem onClick={handleStar}>
165
+ {starred ? <StarFilledIcon size={14} /> : <StarIcon size={14} />}
166
+ <span>{starred ? 'Unstar' : 'Star'}</span>
167
+ </DropdownMenuItem>
168
+ <DropdownMenuItem onClick={() => setShowRenameDialog(true)}>
169
+ <PencilIcon size={14} />
170
+ <span>Rename</span>
171
+ </DropdownMenuItem>
172
+ <DropdownMenuSeparator />
173
+ <DropdownMenuSub>
174
+ <DropdownMenuSubTrigger>
175
+ <ExportIcon size={14} />
176
+ <span>{isExporting ? 'Exporting…' : 'Export'}</span>
177
+ </DropdownMenuSubTrigger>
178
+ <DropdownMenuSubContent>
179
+ <DropdownMenuItem onClick={() => handleExport('md')}>
180
+ <span>Markdown (.md)</span>
181
+ </DropdownMenuItem>
182
+ <DropdownMenuItem onClick={() => handleExport('txt')}>
183
+ <span>Plain Text (.txt)</span>
184
+ </DropdownMenuItem>
185
+ <DropdownMenuItem onClick={() => handleExport('json')}>
186
+ <span>JSON (.json)</span>
187
+ </DropdownMenuItem>
188
+ </DropdownMenuSubContent>
189
+ </DropdownMenuSub>
190
+ <DropdownMenuSeparator />
191
+ <DropdownMenuItem onClick={() => setShowDeleteConfirm(true)}>
192
+ <TrashIcon size={14} />
193
+ <span className="text-destructive">Delete</span>
194
+ </DropdownMenuItem>
195
+ </DropdownMenuContent>
196
+ </DropdownMenu>
197
+ </div>
198
+ ) : (
199
+ <h1 className="text-base font-medium text-muted-foreground truncate">
200
+ {title || '\u00A0'}
201
+ </h1>
202
+ )}
203
+ </header>
204
+
205
+ <RenameDialog
206
+ open={showRenameDialog}
207
+ onSave={handleRenameFromDialog}
208
+ onCancel={() => setShowRenameDialog(false)}
209
+ title="Rename chat"
210
+ currentValue={title || ''}
211
+ />
212
+
213
+ <ConfirmDialog
214
+ open={showDeleteConfirm}
215
+ onConfirm={handleDelete}
216
+ onCancel={() => setShowDeleteConfirm(false)}
217
+ title="Delete chat?"
218
+ description="This will permanently delete this chat and all its messages."
219
+ confirmLabel="Delete"
220
+ />
221
+ </>
222
+ );
223
+ }
@@ -0,0 +1,236 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { useRef, useEffect, useCallback, useState } from "react";
4
+ import { SendIcon, StopIcon, PaperclipIcon, XIcon, FileTextIcon } from "./icons.js";
5
+ import { cn } from "../utils.js";
6
+ const ACCEPTED_TYPES = [
7
+ "image/jpeg",
8
+ "image/png",
9
+ "image/gif",
10
+ "image/webp",
11
+ "application/pdf",
12
+ "text/plain",
13
+ "text/markdown",
14
+ "text/csv",
15
+ "text/html",
16
+ "text/css",
17
+ "text/javascript",
18
+ "text/x-python",
19
+ "text/x-typescript",
20
+ "application/json"
21
+ ];
22
+ const MAX_FILES = 5;
23
+ function isAcceptedType(file) {
24
+ if (ACCEPTED_TYPES.includes(file.type)) return true;
25
+ const ext = file.name?.split(".").pop()?.toLowerCase();
26
+ const textExts = ["txt", "md", "csv", "json", "js", "ts", "jsx", "tsx", "py", "html", "css", "yml", "yaml", "xml", "sh", "bash", "rb", "go", "rs", "java", "c", "cpp", "h", "hpp"];
27
+ return textExts.includes(ext);
28
+ }
29
+ function getEffectiveType(file) {
30
+ if (ACCEPTED_TYPES.includes(file.type) && file.type !== "") return file.type;
31
+ const ext = file.name?.split(".").pop()?.toLowerCase();
32
+ const extMap = {
33
+ txt: "text/plain",
34
+ md: "text/markdown",
35
+ csv: "text/csv",
36
+ json: "application/json",
37
+ js: "text/javascript",
38
+ ts: "text/x-typescript",
39
+ jsx: "text/javascript",
40
+ tsx: "text/x-typescript",
41
+ py: "text/x-python",
42
+ html: "text/html",
43
+ css: "text/css",
44
+ yml: "text/plain",
45
+ yaml: "text/plain",
46
+ xml: "text/plain",
47
+ sh: "text/plain",
48
+ bash: "text/plain",
49
+ rb: "text/plain",
50
+ go: "text/plain",
51
+ rs: "text/plain",
52
+ java: "text/plain",
53
+ c: "text/plain",
54
+ cpp: "text/plain",
55
+ h: "text/plain",
56
+ hpp: "text/plain"
57
+ };
58
+ return extMap[ext] || file.type || "text/plain";
59
+ }
60
+ function ChatInput({ input, setInput, onSubmit, status, stop, files, setFiles, disabled = false, placeholder = "Send a message...", canSendOverride }) {
61
+ const textareaRef = useRef(null);
62
+ const fileInputRef = useRef(null);
63
+ const [isDragging, setIsDragging] = useState(false);
64
+ const isStreaming = status === "streaming" || status === "submitted";
65
+ const adjustHeight = useCallback(() => {
66
+ const textarea = textareaRef.current;
67
+ if (!textarea) return;
68
+ textarea.style.height = "auto";
69
+ textarea.style.height = `${Math.min(textarea.scrollHeight, 200)}px`;
70
+ }, []);
71
+ useEffect(() => {
72
+ adjustHeight();
73
+ }, [input, adjustHeight]);
74
+ useEffect(() => {
75
+ textareaRef.current?.focus();
76
+ }, []);
77
+ const handleFiles = useCallback((fileList) => {
78
+ const newFiles = Array.from(fileList).filter(isAcceptedType);
79
+ if (newFiles.length === 0) return;
80
+ newFiles.forEach((file) => {
81
+ const reader = new FileReader();
82
+ reader.onload = () => {
83
+ setFiles((current) => {
84
+ if (current.length >= MAX_FILES) return current;
85
+ return [...current, { file, previewUrl: reader.result }];
86
+ });
87
+ };
88
+ reader.readAsDataURL(file);
89
+ });
90
+ }, [setFiles]);
91
+ const removeFile = (index) => {
92
+ setFiles((prev) => prev.filter((_, i) => i !== index));
93
+ };
94
+ const handleSubmit = (e) => {
95
+ if (e) e.preventDefault();
96
+ if (disabled || !input.trim() && files.length === 0 || isStreaming) return;
97
+ if (canSendOverride !== void 0 && !canSendOverride) return;
98
+ onSubmit();
99
+ };
100
+ const handleKeyDown = (e) => {
101
+ if (e.key === "Enter" && !e.shiftKey) {
102
+ e.preventDefault();
103
+ handleSubmit();
104
+ }
105
+ };
106
+ const handleDragOver = (e) => {
107
+ e.preventDefault();
108
+ setIsDragging(true);
109
+ };
110
+ const handleDragLeave = (e) => {
111
+ e.preventDefault();
112
+ setIsDragging(false);
113
+ };
114
+ const handleDrop = (e) => {
115
+ e.preventDefault();
116
+ setIsDragging(false);
117
+ if (e.dataTransfer?.files?.length) {
118
+ handleFiles(e.dataTransfer.files);
119
+ }
120
+ };
121
+ const canSend = canSendOverride !== void 0 ? canSendOverride && (input.trim() || files.length > 0) : input.trim() || files.length > 0;
122
+ if (disabled && !isStreaming) {
123
+ return /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-4xl px-4 pb-4 md:px-6", children: /* @__PURE__ */ jsx("div", { className: "flex flex-col rounded-xl border border-border bg-muted p-2", children: /* @__PURE__ */ jsx("div", { className: "flex items-center px-2 py-1.5", children: /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: placeholder }) }) }) });
124
+ }
125
+ return /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-4xl px-4 pb-4 md:px-6", children: /* @__PURE__ */ jsx("form", { onSubmit: handleSubmit, className: "relative", children: /* @__PURE__ */ jsxs(
126
+ "div",
127
+ {
128
+ className: cn(
129
+ "flex flex-col rounded-xl border bg-muted p-2 transition-colors",
130
+ isDragging ? "border-primary bg-primary/5" : "border-border"
131
+ ),
132
+ onDragOver: handleDragOver,
133
+ onDragLeave: handleDragLeave,
134
+ onDrop: handleDrop,
135
+ children: [
136
+ files.length > 0 && /* @__PURE__ */ jsx("div", { className: "mb-2 flex gap-2 overflow-x-auto px-1 py-1", children: files.map((f, i) => {
137
+ const isImage = f.file.type.startsWith("image/");
138
+ return /* @__PURE__ */ jsxs("div", { className: "group relative flex-shrink-0", children: [
139
+ isImage ? /* @__PURE__ */ jsx(
140
+ "img",
141
+ {
142
+ src: f.previewUrl,
143
+ alt: f.file.name,
144
+ className: "h-16 w-16 rounded-lg object-cover"
145
+ }
146
+ ) : /* @__PURE__ */ jsxs("div", { className: "flex h-16 items-center gap-1.5 rounded-lg bg-foreground/10 px-3", children: [
147
+ /* @__PURE__ */ jsx(FileTextIcon, { size: 14 }),
148
+ /* @__PURE__ */ jsx("span", { className: "max-w-[100px] truncate text-xs", children: f.file.name })
149
+ ] }),
150
+ /* @__PURE__ */ jsx(
151
+ "button",
152
+ {
153
+ type: "button",
154
+ onClick: () => removeFile(i),
155
+ className: "absolute -right-1.5 -top-1.5 hidden rounded-full bg-foreground p-0.5 text-background group-hover:flex items-center justify-center",
156
+ "aria-label": `Remove ${f.file.name}`,
157
+ children: /* @__PURE__ */ jsx(XIcon, { size: 10 })
158
+ }
159
+ )
160
+ ] }, i);
161
+ }) }),
162
+ /* @__PURE__ */ jsx(
163
+ "textarea",
164
+ {
165
+ ref: textareaRef,
166
+ value: input,
167
+ onChange: (e) => setInput(e.target.value),
168
+ onKeyDown: handleKeyDown,
169
+ placeholder,
170
+ rows: 1,
171
+ className: cn(
172
+ "w-full resize-none bg-transparent px-2 py-1.5 text-sm text-foreground",
173
+ "placeholder:text-muted-foreground focus:outline-none",
174
+ "max-h-[200px]"
175
+ ),
176
+ disabled: isStreaming
177
+ }
178
+ ),
179
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
180
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
181
+ /* @__PURE__ */ jsx(
182
+ "button",
183
+ {
184
+ type: "button",
185
+ onClick: () => fileInputRef.current?.click(),
186
+ className: "inline-flex items-center justify-center rounded-lg p-2 text-muted-foreground hover:text-foreground",
187
+ "aria-label": "Attach files",
188
+ disabled: isStreaming,
189
+ children: /* @__PURE__ */ jsx(PaperclipIcon, { size: 16 })
190
+ }
191
+ ),
192
+ /* @__PURE__ */ jsx(
193
+ "input",
194
+ {
195
+ ref: fileInputRef,
196
+ type: "file",
197
+ multiple: true,
198
+ accept: "image/*,application/pdf,text/*,application/json,.md,.csv,.json,.js,.ts,.jsx,.tsx,.py,.html,.css,.yml,.yaml,.xml,.sh,.rb,.go,.rs,.java,.c,.cpp,.h",
199
+ className: "hidden",
200
+ onChange: (e) => {
201
+ if (e.target.files?.length) handleFiles(e.target.files);
202
+ e.target.value = "";
203
+ }
204
+ }
205
+ )
206
+ ] }),
207
+ isStreaming ? /* @__PURE__ */ jsx(
208
+ "button",
209
+ {
210
+ type: "button",
211
+ onClick: stop,
212
+ className: "inline-flex items-center justify-center rounded-lg bg-foreground p-2 text-background hover:opacity-80",
213
+ "aria-label": "Stop generating",
214
+ children: /* @__PURE__ */ jsx(StopIcon, { size: 16 })
215
+ }
216
+ ) : /* @__PURE__ */ jsx(
217
+ "button",
218
+ {
219
+ type: "submit",
220
+ disabled: !canSend,
221
+ className: cn(
222
+ "inline-flex items-center justify-center rounded-lg p-2",
223
+ canSend ? "bg-foreground text-background hover:opacity-80" : "bg-muted-foreground/20 text-muted-foreground cursor-not-allowed"
224
+ ),
225
+ "aria-label": "Send message",
226
+ children: /* @__PURE__ */ jsx(SendIcon, { size: 16 })
227
+ }
228
+ )
229
+ ] })
230
+ ]
231
+ }
232
+ ) }) });
233
+ }
234
+ export {
235
+ ChatInput
236
+ };