thepopebot 1.1.2 → 1.2.3
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 +1 -1
- package/api/index.js +72 -165
- package/bin/cli.js +36 -6
- package/bin/local.sh +31 -0
- package/bin/postinstall.js +6 -2
- package/config/index.js +2 -11
- package/config/instrumentation.js +17 -5
- package/lib/actions.js +7 -6
- package/lib/ai/agent.js +36 -0
- package/lib/ai/index.js +274 -0
- package/lib/ai/model.js +67 -0
- package/lib/ai/tools.js +49 -0
- package/lib/auth/actions.js +28 -0
- package/lib/auth/config.js +45 -0
- package/lib/auth/index.js +27 -0
- package/lib/auth/middleware.js +30 -0
- package/lib/channels/base.js +56 -0
- package/lib/channels/index.js +15 -0
- package/lib/channels/telegram.js +146 -0
- package/lib/chat/actions.js +239 -0
- package/lib/chat/api.js +103 -0
- package/lib/chat/components/app-sidebar.js +161 -0
- package/lib/chat/components/app-sidebar.jsx +214 -0
- package/lib/chat/components/chat-header.js +9 -0
- package/lib/chat/components/chat-header.jsx +14 -0
- package/lib/chat/components/chat-input.js +230 -0
- package/lib/chat/components/chat-input.jsx +232 -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 +70 -0
- package/lib/chat/components/chat-page.jsx +89 -0
- package/lib/chat/components/chat.js +78 -0
- package/lib/chat/components/chat.jsx +91 -0
- package/lib/chat/components/chats-page.js +170 -0
- package/lib/chat/components/chats-page.jsx +203 -0
- package/lib/chat/components/crons-page.js +144 -0
- package/lib/chat/components/crons-page.jsx +204 -0
- package/lib/chat/components/greeting.js +11 -0
- package/lib/chat/components/greeting.jsx +14 -0
- package/lib/chat/components/icons.js +518 -0
- package/lib/chat/components/icons.jsx +482 -0
- package/lib/chat/components/index.js +19 -0
- package/lib/chat/components/message.js +66 -0
- package/lib/chat/components/message.jsx +92 -0
- package/lib/chat/components/messages.js +63 -0
- package/lib/chat/components/messages.jsx +72 -0
- package/lib/chat/components/notifications-page.js +54 -0
- package/lib/chat/components/notifications-page.jsx +83 -0
- package/lib/chat/components/page-layout.js +21 -0
- package/lib/chat/components/page-layout.jsx +28 -0
- package/lib/chat/components/settings-layout.js +37 -0
- package/lib/chat/components/settings-layout.jsx +51 -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 +54 -0
- package/lib/chat/components/sidebar-history-item.jsx +50 -0
- package/lib/chat/components/sidebar-history.js +92 -0
- package/lib/chat/components/sidebar-history.jsx +132 -0
- package/lib/chat/components/sidebar-user-nav.js +59 -0
- package/lib/chat/components/sidebar-user-nav.jsx +69 -0
- package/lib/chat/components/swarm-page.js +250 -0
- package/lib/chat/components/swarm-page.jsx +356 -0
- package/lib/chat/components/triggers-page.js +121 -0
- package/lib/chat/components/triggers-page.jsx +177 -0
- package/lib/chat/components/ui/dropdown-menu.js +98 -0
- package/lib/chat/components/ui/dropdown-menu.jsx +116 -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 +227 -0
- package/lib/chat/components/ui/sidebar.jsx +245 -0
- package/lib/chat/components/ui/tooltip.js +56 -0
- package/lib/chat/components/ui/tooltip.jsx +66 -0
- package/lib/chat/utils.js +11 -0
- package/lib/cron.js +7 -8
- package/lib/db/api-keys.js +160 -0
- package/lib/db/chats.js +129 -0
- package/lib/db/index.js +106 -0
- package/lib/db/notifications.js +99 -0
- package/lib/db/schema.js +51 -0
- package/lib/db/users.js +89 -0
- package/lib/paths.js +23 -17
- package/lib/tools/create-job.js +3 -3
- package/lib/tools/github.js +145 -1
- package/lib/tools/openai.js +1 -1
- package/lib/tools/telegram.js +4 -3
- package/lib/triggers.js +6 -7
- package/lib/utils/render-md.js +6 -6
- package/package.json +43 -6
- package/setup/lib/auth.mjs +22 -9
- package/setup/lib/prerequisites.mjs +10 -3
- package/setup/lib/telegram-verify.mjs +3 -16
- package/setup/setup-telegram.mjs +31 -62
- package/setup/setup.mjs +58 -98
- package/templates/.dockerignore +5 -0
- package/templates/.env.example +18 -2
- package/templates/.github/workflows/auto-merge.yml +1 -1
- package/templates/.github/workflows/build-image.yml +6 -4
- package/templates/.github/workflows/notify-job-failed.yml +2 -2
- package/templates/.github/workflows/notify-pr-complete.yml +2 -2
- package/templates/.github/workflows/run-job.yml +24 -10
- package/templates/CLAUDE.md +5 -3
- package/templates/app/api/auth/[...nextauth]/route.js +1 -0
- package/templates/app/api/chat/route.js +1 -0
- package/templates/app/chat/[chatId]/page.js +8 -0
- package/templates/app/chats/page.js +7 -0
- package/templates/app/components/ascii-logo.jsx +10 -0
- package/templates/app/components/login-form.jsx +81 -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 +7 -0
- package/templates/app/globals.css +66 -0
- package/templates/app/layout.js +9 -2
- package/templates/app/login/page.js +15 -0
- package/templates/app/notifications/page.js +7 -0
- package/templates/app/page.js +6 -30
- 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/swarm/page.js +7 -0
- package/templates/app/triggers/page.js +7 -0
- package/templates/config/CRONS.json +2 -2
- package/templates/config/TRIGGERS.json +2 -2
- package/templates/docker/event_handler/Dockerfile +19 -0
- package/templates/docker/{entrypoint.sh → job/entrypoint.sh} +4 -4
- package/templates/docker/runner/Dockerfile +38 -0
- package/templates/docker/runner/entrypoint.sh +41 -0
- package/templates/docker-compose.yml +52 -0
- package/templates/instrumentation.js +6 -1
- package/templates/middleware.js +1 -0
- package/templates/postcss.config.mjs +5 -0
- package/lib/claude/conversation.js +0 -76
- package/lib/claude/index.js +0 -142
- package/lib/claude/tools.js +0 -54
- /package/templates/docker/{Dockerfile → job/Dockerfile} +0 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { SquarePenIcon, PanelLeftIcon, MessageIcon, BellIcon, SwarmIcon, ClockIcon, ZapIcon } from './icons.js';
|
|
5
|
+
import { getUnreadNotificationCount } from '../actions.js';
|
|
6
|
+
import { SidebarHistory } from './sidebar-history.js';
|
|
7
|
+
import { SidebarUserNav } from './sidebar-user-nav.js';
|
|
8
|
+
import {
|
|
9
|
+
Sidebar,
|
|
10
|
+
SidebarContent,
|
|
11
|
+
SidebarFooter,
|
|
12
|
+
SidebarHeader,
|
|
13
|
+
SidebarGroup,
|
|
14
|
+
SidebarGroupLabel,
|
|
15
|
+
SidebarMenu,
|
|
16
|
+
SidebarMenuItem,
|
|
17
|
+
SidebarMenuButton,
|
|
18
|
+
useSidebar,
|
|
19
|
+
} from './ui/sidebar.js';
|
|
20
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip.js';
|
|
21
|
+
import { useChatNav } from './chat-nav-context.js';
|
|
22
|
+
|
|
23
|
+
export function AppSidebar({ user }) {
|
|
24
|
+
const { navigateToChat } = useChatNav();
|
|
25
|
+
const { state, open, setOpenMobile, toggleSidebar } = useSidebar();
|
|
26
|
+
const collapsed = state === 'collapsed';
|
|
27
|
+
const [unreadCount, setUnreadCount] = useState(0);
|
|
28
|
+
|
|
29
|
+
useEffect(() => {
|
|
30
|
+
getUnreadNotificationCount()
|
|
31
|
+
.then((count) => setUnreadCount(count))
|
|
32
|
+
.catch(() => {});
|
|
33
|
+
}, []);
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<Sidebar>
|
|
37
|
+
<SidebarHeader>
|
|
38
|
+
{/* Top row: brand name + toggle icon (open) or just toggle icon (collapsed) */}
|
|
39
|
+
<div className={collapsed ? 'flex justify-center' : 'flex items-center justify-between'}>
|
|
40
|
+
{!collapsed && (
|
|
41
|
+
<span className="px-2 font-semibold text-lg">The Pope Bot</span>
|
|
42
|
+
)}
|
|
43
|
+
<Tooltip>
|
|
44
|
+
<TooltipTrigger asChild>
|
|
45
|
+
<button
|
|
46
|
+
className="inline-flex shrink-0 items-center justify-center rounded-md p-2 text-muted-foreground hover:bg-background hover:text-foreground"
|
|
47
|
+
onClick={toggleSidebar}
|
|
48
|
+
>
|
|
49
|
+
<PanelLeftIcon size={16} />
|
|
50
|
+
</button>
|
|
51
|
+
</TooltipTrigger>
|
|
52
|
+
<TooltipContent side={collapsed ? 'right' : 'bottom'}>
|
|
53
|
+
{collapsed ? 'Open sidebar' : 'Close sidebar'}
|
|
54
|
+
</TooltipContent>
|
|
55
|
+
</Tooltip>
|
|
56
|
+
</div>
|
|
57
|
+
|
|
58
|
+
<SidebarMenu>
|
|
59
|
+
{/* New chat */}
|
|
60
|
+
<SidebarMenuItem>
|
|
61
|
+
<Tooltip>
|
|
62
|
+
<TooltipTrigger asChild>
|
|
63
|
+
<SidebarMenuButton
|
|
64
|
+
className={collapsed ? 'justify-center' : ''}
|
|
65
|
+
onClick={() => {
|
|
66
|
+
navigateToChat(null);
|
|
67
|
+
setOpenMobile(false);
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
<SquarePenIcon size={16} />
|
|
71
|
+
{!collapsed && <span>New chat</span>}
|
|
72
|
+
</SidebarMenuButton>
|
|
73
|
+
</TooltipTrigger>
|
|
74
|
+
{collapsed && (
|
|
75
|
+
<TooltipContent side="right">New chat</TooltipContent>
|
|
76
|
+
)}
|
|
77
|
+
</Tooltip>
|
|
78
|
+
</SidebarMenuItem>
|
|
79
|
+
|
|
80
|
+
{/* Chats history */}
|
|
81
|
+
<SidebarMenuItem>
|
|
82
|
+
<Tooltip>
|
|
83
|
+
<TooltipTrigger asChild>
|
|
84
|
+
<SidebarMenuButton
|
|
85
|
+
className={collapsed ? 'justify-center' : ''}
|
|
86
|
+
onClick={() => {
|
|
87
|
+
window.location.href = '/chats';
|
|
88
|
+
}}
|
|
89
|
+
>
|
|
90
|
+
<MessageIcon size={16} />
|
|
91
|
+
{!collapsed && <span>Chats</span>}
|
|
92
|
+
</SidebarMenuButton>
|
|
93
|
+
</TooltipTrigger>
|
|
94
|
+
{collapsed && (
|
|
95
|
+
<TooltipContent side="right">Chats</TooltipContent>
|
|
96
|
+
)}
|
|
97
|
+
</Tooltip>
|
|
98
|
+
</SidebarMenuItem>
|
|
99
|
+
|
|
100
|
+
{/* Notifications */}
|
|
101
|
+
<SidebarMenuItem>
|
|
102
|
+
<Tooltip>
|
|
103
|
+
<TooltipTrigger asChild>
|
|
104
|
+
<SidebarMenuButton
|
|
105
|
+
className={collapsed ? 'justify-center' : ''}
|
|
106
|
+
onClick={() => {
|
|
107
|
+
window.location.href = '/notifications';
|
|
108
|
+
}}
|
|
109
|
+
>
|
|
110
|
+
<BellIcon size={16} />
|
|
111
|
+
{!collapsed && (
|
|
112
|
+
<span className="flex items-center gap-2">
|
|
113
|
+
Notifications
|
|
114
|
+
{unreadCount > 0 && (
|
|
115
|
+
<span className="inline-flex items-center justify-center rounded-full bg-destructive px-1.5 py-0.5 text-[10px] font-medium leading-none text-destructive-foreground">
|
|
116
|
+
{unreadCount}
|
|
117
|
+
</span>
|
|
118
|
+
)}
|
|
119
|
+
</span>
|
|
120
|
+
)}
|
|
121
|
+
{collapsed && unreadCount > 0 && (
|
|
122
|
+
<span className="absolute -top-1 -right-1 inline-flex h-4 w-4 items-center justify-center rounded-full bg-destructive text-[10px] font-medium text-destructive-foreground">
|
|
123
|
+
{unreadCount}
|
|
124
|
+
</span>
|
|
125
|
+
)}
|
|
126
|
+
</SidebarMenuButton>
|
|
127
|
+
</TooltipTrigger>
|
|
128
|
+
{collapsed && (
|
|
129
|
+
<TooltipContent side="right">Notifications</TooltipContent>
|
|
130
|
+
)}
|
|
131
|
+
</Tooltip>
|
|
132
|
+
</SidebarMenuItem>
|
|
133
|
+
|
|
134
|
+
{/* Swarm */}
|
|
135
|
+
<SidebarMenuItem>
|
|
136
|
+
<Tooltip>
|
|
137
|
+
<TooltipTrigger asChild>
|
|
138
|
+
<SidebarMenuButton
|
|
139
|
+
className={collapsed ? 'justify-center' : ''}
|
|
140
|
+
onClick={() => {
|
|
141
|
+
window.location.href = '/swarm';
|
|
142
|
+
}}
|
|
143
|
+
>
|
|
144
|
+
<SwarmIcon size={16} />
|
|
145
|
+
{!collapsed && <span>Swarm</span>}
|
|
146
|
+
</SidebarMenuButton>
|
|
147
|
+
</TooltipTrigger>
|
|
148
|
+
{collapsed && (
|
|
149
|
+
<TooltipContent side="right">Swarm</TooltipContent>
|
|
150
|
+
)}
|
|
151
|
+
</Tooltip>
|
|
152
|
+
</SidebarMenuItem>
|
|
153
|
+
|
|
154
|
+
{/* Cron Jobs */}
|
|
155
|
+
<SidebarMenuItem>
|
|
156
|
+
<Tooltip>
|
|
157
|
+
<TooltipTrigger asChild>
|
|
158
|
+
<SidebarMenuButton
|
|
159
|
+
className={collapsed ? 'justify-center' : ''}
|
|
160
|
+
onClick={() => {
|
|
161
|
+
window.location.href = '/crons';
|
|
162
|
+
}}
|
|
163
|
+
>
|
|
164
|
+
<ClockIcon size={16} />
|
|
165
|
+
{!collapsed && <span>Cron Jobs</span>}
|
|
166
|
+
</SidebarMenuButton>
|
|
167
|
+
</TooltipTrigger>
|
|
168
|
+
{collapsed && (
|
|
169
|
+
<TooltipContent side="right">Cron Jobs</TooltipContent>
|
|
170
|
+
)}
|
|
171
|
+
</Tooltip>
|
|
172
|
+
</SidebarMenuItem>
|
|
173
|
+
|
|
174
|
+
{/* Triggers */}
|
|
175
|
+
<SidebarMenuItem>
|
|
176
|
+
<Tooltip>
|
|
177
|
+
<TooltipTrigger asChild>
|
|
178
|
+
<SidebarMenuButton
|
|
179
|
+
className={collapsed ? 'justify-center' : ''}
|
|
180
|
+
onClick={() => {
|
|
181
|
+
window.location.href = '/triggers';
|
|
182
|
+
}}
|
|
183
|
+
>
|
|
184
|
+
<ZapIcon size={16} />
|
|
185
|
+
{!collapsed && <span>Triggers</span>}
|
|
186
|
+
</SidebarMenuButton>
|
|
187
|
+
</TooltipTrigger>
|
|
188
|
+
{collapsed && (
|
|
189
|
+
<TooltipContent side="right">Triggers</TooltipContent>
|
|
190
|
+
)}
|
|
191
|
+
</Tooltip>
|
|
192
|
+
</SidebarMenuItem>
|
|
193
|
+
|
|
194
|
+
</SidebarMenu>
|
|
195
|
+
</SidebarHeader>
|
|
196
|
+
|
|
197
|
+
{!collapsed && (
|
|
198
|
+
<SidebarContent>
|
|
199
|
+
<SidebarGroup className="pt-0">
|
|
200
|
+
<SidebarGroupLabel>Chats</SidebarGroupLabel>
|
|
201
|
+
</SidebarGroup>
|
|
202
|
+
<SidebarHistory />
|
|
203
|
+
</SidebarContent>
|
|
204
|
+
)}
|
|
205
|
+
|
|
206
|
+
{/* Spacer when collapsed to push footer down */}
|
|
207
|
+
{collapsed && <div className="flex-1" />}
|
|
208
|
+
|
|
209
|
+
<SidebarFooter>
|
|
210
|
+
{user && <SidebarUserNav user={user} collapsed={collapsed} />}
|
|
211
|
+
</SidebarFooter>
|
|
212
|
+
</Sidebar>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from "react/jsx-runtime";
|
|
3
|
+
import { SidebarTrigger } from "./ui/sidebar.js";
|
|
4
|
+
function ChatHeader({ chatId }) {
|
|
5
|
+
return /* @__PURE__ */ jsx("header", { className: "sticky top-0 flex items-center gap-2 bg-background px-2 py-1.5 md:px-2 z-10", children: /* @__PURE__ */ jsx("div", { className: "md:hidden", children: /* @__PURE__ */ jsx(SidebarTrigger, {}) }) });
|
|
6
|
+
}
|
|
7
|
+
export {
|
|
8
|
+
ChatHeader
|
|
9
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { SidebarTrigger } from './ui/sidebar.js';
|
|
4
|
+
|
|
5
|
+
export function ChatHeader({ chatId }) {
|
|
6
|
+
return (
|
|
7
|
+
<header className="sticky top-0 flex items-center gap-2 bg-background px-2 py-1.5 md:px-2 z-10">
|
|
8
|
+
{/* Mobile-only: open sidebar sheet */}
|
|
9
|
+
<div className="md:hidden">
|
|
10
|
+
<SidebarTrigger />
|
|
11
|
+
</div>
|
|
12
|
+
</header>
|
|
13
|
+
);
|
|
14
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
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 }) {
|
|
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 (!input.trim() && files.length === 0 || isStreaming) return;
|
|
97
|
+
onSubmit();
|
|
98
|
+
};
|
|
99
|
+
const handleKeyDown = (e) => {
|
|
100
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
101
|
+
e.preventDefault();
|
|
102
|
+
handleSubmit();
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
const handleDragOver = (e) => {
|
|
106
|
+
e.preventDefault();
|
|
107
|
+
setIsDragging(true);
|
|
108
|
+
};
|
|
109
|
+
const handleDragLeave = (e) => {
|
|
110
|
+
e.preventDefault();
|
|
111
|
+
setIsDragging(false);
|
|
112
|
+
};
|
|
113
|
+
const handleDrop = (e) => {
|
|
114
|
+
e.preventDefault();
|
|
115
|
+
setIsDragging(false);
|
|
116
|
+
if (e.dataTransfer?.files?.length) {
|
|
117
|
+
handleFiles(e.dataTransfer.files);
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
const canSend = input.trim() || files.length > 0;
|
|
121
|
+
return /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-4xl px-2 pb-4 md:px-4", children: /* @__PURE__ */ jsx("form", { onSubmit: handleSubmit, className: "relative", children: /* @__PURE__ */ jsxs(
|
|
122
|
+
"div",
|
|
123
|
+
{
|
|
124
|
+
className: cn(
|
|
125
|
+
"flex flex-col rounded-xl border bg-muted p-2 transition-colors",
|
|
126
|
+
isDragging ? "border-primary bg-primary/5" : "border-border"
|
|
127
|
+
),
|
|
128
|
+
onDragOver: handleDragOver,
|
|
129
|
+
onDragLeave: handleDragLeave,
|
|
130
|
+
onDrop: handleDrop,
|
|
131
|
+
children: [
|
|
132
|
+
files.length > 0 && /* @__PURE__ */ jsx("div", { className: "mb-2 flex gap-2 overflow-x-auto px-1 py-1", children: files.map((f, i) => {
|
|
133
|
+
const isImage = f.file.type.startsWith("image/");
|
|
134
|
+
return /* @__PURE__ */ jsxs("div", { className: "group relative flex-shrink-0", children: [
|
|
135
|
+
isImage ? /* @__PURE__ */ jsx(
|
|
136
|
+
"img",
|
|
137
|
+
{
|
|
138
|
+
src: f.previewUrl,
|
|
139
|
+
alt: f.file.name,
|
|
140
|
+
className: "h-16 w-16 rounded-lg object-cover"
|
|
141
|
+
}
|
|
142
|
+
) : /* @__PURE__ */ jsxs("div", { className: "flex h-16 items-center gap-1.5 rounded-lg bg-foreground/10 px-3", children: [
|
|
143
|
+
/* @__PURE__ */ jsx(FileTextIcon, { size: 14 }),
|
|
144
|
+
/* @__PURE__ */ jsx("span", { className: "max-w-[100px] truncate text-xs", children: f.file.name })
|
|
145
|
+
] }),
|
|
146
|
+
/* @__PURE__ */ jsx(
|
|
147
|
+
"button",
|
|
148
|
+
{
|
|
149
|
+
type: "button",
|
|
150
|
+
onClick: () => removeFile(i),
|
|
151
|
+
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",
|
|
152
|
+
"aria-label": `Remove ${f.file.name}`,
|
|
153
|
+
children: /* @__PURE__ */ jsx(XIcon, { size: 10 })
|
|
154
|
+
}
|
|
155
|
+
)
|
|
156
|
+
] }, i);
|
|
157
|
+
}) }),
|
|
158
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-end gap-2", children: [
|
|
159
|
+
/* @__PURE__ */ jsx(
|
|
160
|
+
"button",
|
|
161
|
+
{
|
|
162
|
+
type: "button",
|
|
163
|
+
onClick: () => fileInputRef.current?.click(),
|
|
164
|
+
className: "inline-flex items-center justify-center rounded-lg p-2 text-muted-foreground hover:text-foreground",
|
|
165
|
+
"aria-label": "Attach files",
|
|
166
|
+
disabled: isStreaming,
|
|
167
|
+
children: /* @__PURE__ */ jsx(PaperclipIcon, { size: 16 })
|
|
168
|
+
}
|
|
169
|
+
),
|
|
170
|
+
/* @__PURE__ */ jsx(
|
|
171
|
+
"input",
|
|
172
|
+
{
|
|
173
|
+
ref: fileInputRef,
|
|
174
|
+
type: "file",
|
|
175
|
+
multiple: true,
|
|
176
|
+
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",
|
|
177
|
+
className: "hidden",
|
|
178
|
+
onChange: (e) => {
|
|
179
|
+
if (e.target.files?.length) handleFiles(e.target.files);
|
|
180
|
+
e.target.value = "";
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
),
|
|
184
|
+
/* @__PURE__ */ jsx(
|
|
185
|
+
"textarea",
|
|
186
|
+
{
|
|
187
|
+
ref: textareaRef,
|
|
188
|
+
value: input,
|
|
189
|
+
onChange: (e) => setInput(e.target.value),
|
|
190
|
+
onKeyDown: handleKeyDown,
|
|
191
|
+
placeholder: "Send a message...",
|
|
192
|
+
rows: 1,
|
|
193
|
+
className: cn(
|
|
194
|
+
"flex-1 resize-none bg-transparent px-2 py-1.5 text-sm text-foreground",
|
|
195
|
+
"placeholder:text-muted-foreground focus:outline-none",
|
|
196
|
+
"max-h-[200px]"
|
|
197
|
+
),
|
|
198
|
+
disabled: isStreaming
|
|
199
|
+
}
|
|
200
|
+
),
|
|
201
|
+
isStreaming ? /* @__PURE__ */ jsx(
|
|
202
|
+
"button",
|
|
203
|
+
{
|
|
204
|
+
type: "button",
|
|
205
|
+
onClick: stop,
|
|
206
|
+
className: "inline-flex items-center justify-center rounded-lg bg-foreground p-2 text-background hover:opacity-80",
|
|
207
|
+
"aria-label": "Stop generating",
|
|
208
|
+
children: /* @__PURE__ */ jsx(StopIcon, { size: 16 })
|
|
209
|
+
}
|
|
210
|
+
) : /* @__PURE__ */ jsx(
|
|
211
|
+
"button",
|
|
212
|
+
{
|
|
213
|
+
type: "submit",
|
|
214
|
+
disabled: !canSend,
|
|
215
|
+
className: cn(
|
|
216
|
+
"inline-flex items-center justify-center rounded-lg p-2",
|
|
217
|
+
canSend ? "bg-foreground text-background hover:opacity-80" : "bg-muted-foreground/20 text-muted-foreground cursor-not-allowed"
|
|
218
|
+
),
|
|
219
|
+
"aria-label": "Send message",
|
|
220
|
+
children: /* @__PURE__ */ jsx(SendIcon, { size: 16 })
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
] })
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
) }) });
|
|
227
|
+
}
|
|
228
|
+
export {
|
|
229
|
+
ChatInput
|
|
230
|
+
};
|