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,87 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { Streamdown } from 'streamdown';
|
|
5
|
+
import { PageLayout } from './page-layout.js';
|
|
6
|
+
import { BellIcon } from './icons.js';
|
|
7
|
+
import { linkSafety } from './message.js';
|
|
8
|
+
import { getNotifications, markNotificationsRead } from '../actions.js';
|
|
9
|
+
|
|
10
|
+
function timeAgo(ts) {
|
|
11
|
+
const seconds = Math.floor((Date.now() - ts) / 1000);
|
|
12
|
+
if (seconds < 60) return 'just now';
|
|
13
|
+
const minutes = Math.floor(seconds / 60);
|
|
14
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
15
|
+
const hours = Math.floor(minutes / 60);
|
|
16
|
+
if (hours < 24) return `${hours}h ago`;
|
|
17
|
+
const days = Math.floor(hours / 24);
|
|
18
|
+
if (days < 30) return `${days}d ago`;
|
|
19
|
+
const months = Math.floor(days / 30);
|
|
20
|
+
return `${months}mo ago`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function NotificationsPage({ session }) {
|
|
24
|
+
const [notifications, setNotifications] = useState([]);
|
|
25
|
+
const [loading, setLoading] = useState(true);
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
async function load() {
|
|
29
|
+
try {
|
|
30
|
+
const result = await getNotifications();
|
|
31
|
+
setNotifications(result);
|
|
32
|
+
// Mark all as read on view
|
|
33
|
+
await markNotificationsRead();
|
|
34
|
+
} catch (err) {
|
|
35
|
+
console.error('Failed to load notifications:', err);
|
|
36
|
+
} finally {
|
|
37
|
+
setLoading(false);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
load();
|
|
41
|
+
}, []);
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<PageLayout session={session}>
|
|
45
|
+
{/* Header */}
|
|
46
|
+
<div className="flex items-center justify-between mb-6">
|
|
47
|
+
<h1 className="text-2xl font-semibold">Notifications</h1>
|
|
48
|
+
</div>
|
|
49
|
+
|
|
50
|
+
{/* Count */}
|
|
51
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
52
|
+
{notifications.length} {notifications.length === 1 ? 'notification' : 'notifications'}
|
|
53
|
+
</p>
|
|
54
|
+
|
|
55
|
+
{/* Notification list */}
|
|
56
|
+
{loading ? (
|
|
57
|
+
<div className="flex flex-col gap-3">
|
|
58
|
+
{[...Array(5)].map((_, i) => (
|
|
59
|
+
<div key={i} className="h-14 animate-pulse rounded-md bg-border/50" />
|
|
60
|
+
))}
|
|
61
|
+
</div>
|
|
62
|
+
) : notifications.length === 0 ? (
|
|
63
|
+
<p className="text-sm text-muted-foreground py-8 text-center">
|
|
64
|
+
No notifications yet.
|
|
65
|
+
</p>
|
|
66
|
+
) : (
|
|
67
|
+
<div className="flex flex-col gap-3">
|
|
68
|
+
{notifications.map((n) => (
|
|
69
|
+
<div key={n.id} className="flex items-start gap-3 p-4 border border-border rounded-lg">
|
|
70
|
+
<div className="mt-0.5 shrink-0 text-muted-foreground">
|
|
71
|
+
<BellIcon size={16} />
|
|
72
|
+
</div>
|
|
73
|
+
<div className="flex-1 min-w-0">
|
|
74
|
+
<div className="text-sm prose-sm">
|
|
75
|
+
<Streamdown mode="static" linkSafety={linkSafety}>{n.notification}</Streamdown>
|
|
76
|
+
</div>
|
|
77
|
+
<span className="text-xs text-muted-foreground">
|
|
78
|
+
{timeAgo(n.createdAt)}
|
|
79
|
+
</span>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
))}
|
|
83
|
+
</div>
|
|
84
|
+
)}
|
|
85
|
+
</PageLayout>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { AppSidebar } from "./app-sidebar.js";
|
|
4
|
+
import { SidebarProvider, SidebarInset } from "./ui/sidebar.js";
|
|
5
|
+
import { ChatNavProvider } from "./chat-nav-context.js";
|
|
6
|
+
function defaultNavigateToChat(id) {
|
|
7
|
+
if (id) {
|
|
8
|
+
window.location.href = `/chat/${id}`;
|
|
9
|
+
} else {
|
|
10
|
+
window.location.href = "/";
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
function PageLayout({ session, children, contentClassName }) {
|
|
14
|
+
return /* @__PURE__ */ jsx(ChatNavProvider, { value: { activeChatId: null, navigateToChat: defaultNavigateToChat }, children: /* @__PURE__ */ jsxs(SidebarProvider, { children: [
|
|
15
|
+
/* @__PURE__ */ jsx(AppSidebar, { user: session.user }),
|
|
16
|
+
/* @__PURE__ */ jsx(SidebarInset, { children: /* @__PURE__ */ jsx("div", { className: contentClassName || "flex flex-col h-full max-w-4xl mx-auto w-full px-4 py-6", children }) })
|
|
17
|
+
] }) });
|
|
18
|
+
}
|
|
19
|
+
export {
|
|
20
|
+
PageLayout
|
|
21
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { AppSidebar } from './app-sidebar.js';
|
|
4
|
+
import { SidebarProvider, SidebarInset } from './ui/sidebar.js';
|
|
5
|
+
import { ChatNavProvider } from './chat-nav-context.js';
|
|
6
|
+
|
|
7
|
+
function defaultNavigateToChat(id) {
|
|
8
|
+
if (id) {
|
|
9
|
+
window.location.href = `/chat/${id}`;
|
|
10
|
+
} else {
|
|
11
|
+
window.location.href = '/';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function PageLayout({ session, children, contentClassName }) {
|
|
16
|
+
return (
|
|
17
|
+
<ChatNavProvider value={{ activeChatId: null, navigateToChat: defaultNavigateToChat }}>
|
|
18
|
+
<SidebarProvider>
|
|
19
|
+
<AppSidebar user={session.user} />
|
|
20
|
+
<SidebarInset>
|
|
21
|
+
<div className={contentClassName || "flex flex-col h-full max-w-4xl mx-auto w-full px-4 py-6"}>
|
|
22
|
+
{children}
|
|
23
|
+
</div>
|
|
24
|
+
</SidebarInset>
|
|
25
|
+
</SidebarProvider>
|
|
26
|
+
</ChatNavProvider>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect, useCallback } from "react";
|
|
4
|
+
import { PageLayout } from "./page-layout.js";
|
|
5
|
+
import { GitPullRequestIcon, SpinnerIcon, RefreshIcon } from "./icons.js";
|
|
6
|
+
import { getPullRequests } from "../actions.js";
|
|
7
|
+
function timeAgo(ts) {
|
|
8
|
+
const seconds = Math.floor((Date.now() - new Date(ts).getTime()) / 1e3);
|
|
9
|
+
if (seconds < 60) return "just now";
|
|
10
|
+
const minutes = Math.floor(seconds / 60);
|
|
11
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
12
|
+
const hours = Math.floor(minutes / 60);
|
|
13
|
+
if (hours < 24) return `${hours}h ago`;
|
|
14
|
+
const days = Math.floor(hours / 24);
|
|
15
|
+
if (days < 30) return `${days}d ago`;
|
|
16
|
+
const months = Math.floor(days / 30);
|
|
17
|
+
return `${months}mo ago`;
|
|
18
|
+
}
|
|
19
|
+
function PullRequestsPage({ session }) {
|
|
20
|
+
const [pullRequests, setPullRequests] = useState([]);
|
|
21
|
+
const [loading, setLoading] = useState(true);
|
|
22
|
+
const [refreshing, setRefreshing] = useState(false);
|
|
23
|
+
const fetchPRs = useCallback(async () => {
|
|
24
|
+
try {
|
|
25
|
+
const result = await getPullRequests();
|
|
26
|
+
setPullRequests(result);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
console.error("Failed to load pull requests:", err);
|
|
29
|
+
} finally {
|
|
30
|
+
setLoading(false);
|
|
31
|
+
setRefreshing(false);
|
|
32
|
+
}
|
|
33
|
+
}, []);
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
fetchPRs();
|
|
36
|
+
}, [fetchPRs]);
|
|
37
|
+
useEffect(() => {
|
|
38
|
+
const interval = setInterval(() => fetchPRs(), 6e4);
|
|
39
|
+
return () => clearInterval(interval);
|
|
40
|
+
}, [fetchPRs]);
|
|
41
|
+
return /* @__PURE__ */ jsxs(PageLayout, { session, children: [
|
|
42
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-6", children: [
|
|
43
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: "Pull Requests" }),
|
|
44
|
+
!loading && /* @__PURE__ */ jsx(
|
|
45
|
+
"button",
|
|
46
|
+
{
|
|
47
|
+
onClick: () => {
|
|
48
|
+
setRefreshing(true);
|
|
49
|
+
fetchPRs();
|
|
50
|
+
},
|
|
51
|
+
disabled: refreshing,
|
|
52
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border border-border bg-background text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50 disabled:pointer-events-none",
|
|
53
|
+
children: refreshing ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
54
|
+
/* @__PURE__ */ jsx(SpinnerIcon, { size: 14 }),
|
|
55
|
+
" Refreshing..."
|
|
56
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
57
|
+
/* @__PURE__ */ jsx(RefreshIcon, { size: 14 }),
|
|
58
|
+
" Refresh"
|
|
59
|
+
] })
|
|
60
|
+
}
|
|
61
|
+
)
|
|
62
|
+
] }),
|
|
63
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground mb-4", children: [
|
|
64
|
+
pullRequests.length,
|
|
65
|
+
" open ",
|
|
66
|
+
pullRequests.length === 1 ? "pull request" : "pull requests"
|
|
67
|
+
] }),
|
|
68
|
+
loading ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: [...Array(5)].map((_, i) => /* @__PURE__ */ jsx("div", { className: "h-14 animate-pulse rounded-md bg-border/50" }, i)) }) : pullRequests.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground py-8 text-center", children: "No open pull requests." }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: pullRequests.map((pr) => /* @__PURE__ */ jsxs(
|
|
69
|
+
"a",
|
|
70
|
+
{
|
|
71
|
+
href: pr.html_url,
|
|
72
|
+
target: "_blank",
|
|
73
|
+
rel: "noopener noreferrer",
|
|
74
|
+
className: "flex items-start gap-3 p-4 border border-border rounded-lg hover:bg-accent transition-colors no-underline text-inherit",
|
|
75
|
+
children: [
|
|
76
|
+
/* @__PURE__ */ jsx("div", { className: "mt-0.5 shrink-0 text-muted-foreground", children: /* @__PURE__ */ jsx(GitPullRequestIcon, { size: 16 }) }),
|
|
77
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 min-w-0", children: [
|
|
78
|
+
/* @__PURE__ */ jsxs("div", { className: "text-sm font-medium", children: [
|
|
79
|
+
"#",
|
|
80
|
+
pr.number,
|
|
81
|
+
" ",
|
|
82
|
+
pr.title
|
|
83
|
+
] }),
|
|
84
|
+
/* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground mt-1", children: [
|
|
85
|
+
pr.head_branch,
|
|
86
|
+
" \u2192 ",
|
|
87
|
+
pr.base_branch,
|
|
88
|
+
" \xB7 opened by ",
|
|
89
|
+
pr.user,
|
|
90
|
+
" \xB7 ",
|
|
91
|
+
timeAgo(pr.created_at)
|
|
92
|
+
] })
|
|
93
|
+
] }),
|
|
94
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs text-blue-500 shrink-0 mt-1", children: "View \u2192" })
|
|
95
|
+
]
|
|
96
|
+
},
|
|
97
|
+
pr.id
|
|
98
|
+
)) })
|
|
99
|
+
] });
|
|
100
|
+
}
|
|
101
|
+
export {
|
|
102
|
+
PullRequestsPage
|
|
103
|
+
};
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
4
|
+
import { PageLayout } from './page-layout.js';
|
|
5
|
+
import { GitPullRequestIcon, SpinnerIcon, RefreshIcon } from './icons.js';
|
|
6
|
+
import { getPullRequests } from '../actions.js';
|
|
7
|
+
|
|
8
|
+
function timeAgo(ts) {
|
|
9
|
+
const seconds = Math.floor((Date.now() - new Date(ts).getTime()) / 1000);
|
|
10
|
+
if (seconds < 60) return 'just now';
|
|
11
|
+
const minutes = Math.floor(seconds / 60);
|
|
12
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
13
|
+
const hours = Math.floor(minutes / 60);
|
|
14
|
+
if (hours < 24) return `${hours}h ago`;
|
|
15
|
+
const days = Math.floor(hours / 24);
|
|
16
|
+
if (days < 30) return `${days}d ago`;
|
|
17
|
+
const months = Math.floor(days / 30);
|
|
18
|
+
return `${months}mo ago`;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function PullRequestsPage({ session }) {
|
|
22
|
+
const [pullRequests, setPullRequests] = useState([]);
|
|
23
|
+
const [loading, setLoading] = useState(true);
|
|
24
|
+
const [refreshing, setRefreshing] = useState(false);
|
|
25
|
+
|
|
26
|
+
const fetchPRs = useCallback(async () => {
|
|
27
|
+
try {
|
|
28
|
+
const result = await getPullRequests();
|
|
29
|
+
setPullRequests(result);
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error('Failed to load pull requests:', err);
|
|
32
|
+
} finally {
|
|
33
|
+
setLoading(false);
|
|
34
|
+
setRefreshing(false);
|
|
35
|
+
}
|
|
36
|
+
}, []);
|
|
37
|
+
|
|
38
|
+
// Initial load
|
|
39
|
+
useEffect(() => { fetchPRs(); }, [fetchPRs]);
|
|
40
|
+
|
|
41
|
+
// Auto-refresh every 60 seconds
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
const interval = setInterval(() => fetchPRs(), 60000);
|
|
44
|
+
return () => clearInterval(interval);
|
|
45
|
+
}, [fetchPRs]);
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<PageLayout session={session}>
|
|
49
|
+
{/* Header */}
|
|
50
|
+
<div className="flex items-center justify-between mb-6">
|
|
51
|
+
<h1 className="text-2xl font-semibold">Pull Requests</h1>
|
|
52
|
+
{!loading && (
|
|
53
|
+
<button
|
|
54
|
+
onClick={() => { setRefreshing(true); fetchPRs(); }}
|
|
55
|
+
disabled={refreshing}
|
|
56
|
+
className="inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border border-border bg-background text-muted-foreground hover:bg-accent hover:text-foreground disabled:opacity-50 disabled:pointer-events-none"
|
|
57
|
+
>
|
|
58
|
+
{refreshing ? (
|
|
59
|
+
<><SpinnerIcon size={14} /> Refreshing...</>
|
|
60
|
+
) : (
|
|
61
|
+
<><RefreshIcon size={14} /> Refresh</>
|
|
62
|
+
)}
|
|
63
|
+
</button>
|
|
64
|
+
)}
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
{/* Count */}
|
|
68
|
+
<p className="text-sm text-muted-foreground mb-4">
|
|
69
|
+
{pullRequests.length} open {pullRequests.length === 1 ? 'pull request' : 'pull requests'}
|
|
70
|
+
</p>
|
|
71
|
+
|
|
72
|
+
{/* PR list */}
|
|
73
|
+
{loading ? (
|
|
74
|
+
<div className="flex flex-col gap-3">
|
|
75
|
+
{[...Array(5)].map((_, i) => (
|
|
76
|
+
<div key={i} className="h-14 animate-pulse rounded-md bg-border/50" />
|
|
77
|
+
))}
|
|
78
|
+
</div>
|
|
79
|
+
) : pullRequests.length === 0 ? (
|
|
80
|
+
<p className="text-sm text-muted-foreground py-8 text-center">
|
|
81
|
+
No open pull requests.
|
|
82
|
+
</p>
|
|
83
|
+
) : (
|
|
84
|
+
<div className="flex flex-col gap-3">
|
|
85
|
+
{pullRequests.map((pr) => (
|
|
86
|
+
<a
|
|
87
|
+
key={pr.id}
|
|
88
|
+
href={pr.html_url}
|
|
89
|
+
target="_blank"
|
|
90
|
+
rel="noopener noreferrer"
|
|
91
|
+
className="flex items-start gap-3 p-4 border border-border rounded-lg hover:bg-accent transition-colors no-underline text-inherit"
|
|
92
|
+
>
|
|
93
|
+
<div className="mt-0.5 shrink-0 text-muted-foreground">
|
|
94
|
+
<GitPullRequestIcon size={16} />
|
|
95
|
+
</div>
|
|
96
|
+
<div className="flex-1 min-w-0">
|
|
97
|
+
<div className="text-sm font-medium">
|
|
98
|
+
#{pr.number} {pr.title}
|
|
99
|
+
</div>
|
|
100
|
+
<div className="text-xs text-muted-foreground mt-1">
|
|
101
|
+
{pr.head_branch} → {pr.base_branch} · opened by {pr.user} · {timeAgo(pr.created_at)}
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
<span className="text-xs text-blue-500 shrink-0 mt-1">
|
|
105
|
+
View →
|
|
106
|
+
</span>
|
|
107
|
+
</a>
|
|
108
|
+
))}
|
|
109
|
+
</div>
|
|
110
|
+
)}
|
|
111
|
+
</PageLayout>
|
|
112
|
+
);
|
|
113
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { PageLayout } from "./page-layout.js";
|
|
5
|
+
import { ClockIcon, ZapIcon, KeyIcon } from "./icons.js";
|
|
6
|
+
const TABS = [
|
|
7
|
+
{ id: "crons", label: "Crons", href: "/settings/crons", icon: ClockIcon },
|
|
8
|
+
{ id: "triggers", label: "Triggers", href: "/settings/triggers", icon: ZapIcon },
|
|
9
|
+
{ id: "secrets", label: "Secrets", href: "/settings/secrets", icon: KeyIcon }
|
|
10
|
+
];
|
|
11
|
+
function SettingsLayout({ session, children }) {
|
|
12
|
+
const [activePath, setActivePath] = useState("");
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
setActivePath(window.location.pathname);
|
|
15
|
+
}, []);
|
|
16
|
+
return /* @__PURE__ */ jsxs(PageLayout, { session, children: [
|
|
17
|
+
/* @__PURE__ */ jsx("div", { className: "mb-6", children: /* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold", children: "Settings" }) }),
|
|
18
|
+
/* @__PURE__ */ jsx("div", { className: "flex gap-1 border-b border-border mb-6", children: TABS.map((tab) => {
|
|
19
|
+
const isActive = activePath === tab.href || activePath.startsWith(tab.href + "/");
|
|
20
|
+
const Icon = tab.icon;
|
|
21
|
+
return /* @__PURE__ */ jsxs(
|
|
22
|
+
"a",
|
|
23
|
+
{
|
|
24
|
+
href: tab.href,
|
|
25
|
+
className: `inline-flex items-center gap-2 px-3 py-2 text-sm font-medium border-b-2 transition-colors ${isActive ? "border-foreground text-foreground" : "border-transparent text-muted-foreground hover:text-foreground hover:border-border"}`,
|
|
26
|
+
children: [
|
|
27
|
+
/* @__PURE__ */ jsx(Icon, { size: 14 }),
|
|
28
|
+
tab.label
|
|
29
|
+
]
|
|
30
|
+
},
|
|
31
|
+
tab.id
|
|
32
|
+
);
|
|
33
|
+
}) }),
|
|
34
|
+
children
|
|
35
|
+
] });
|
|
36
|
+
}
|
|
37
|
+
export {
|
|
38
|
+
SettingsLayout
|
|
39
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { PageLayout } from './page-layout.js';
|
|
5
|
+
import { ClockIcon, ZapIcon, KeyIcon } from './icons.js';
|
|
6
|
+
|
|
7
|
+
const TABS = [
|
|
8
|
+
{ id: 'crons', label: 'Crons', href: '/settings/crons', icon: ClockIcon },
|
|
9
|
+
{ id: 'triggers', label: 'Triggers', href: '/settings/triggers', icon: ZapIcon },
|
|
10
|
+
{ id: 'secrets', label: 'Secrets', href: '/settings/secrets', icon: KeyIcon },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export function SettingsLayout({ session, children }) {
|
|
14
|
+
const [activePath, setActivePath] = useState('');
|
|
15
|
+
|
|
16
|
+
useEffect(() => {
|
|
17
|
+
setActivePath(window.location.pathname);
|
|
18
|
+
}, []);
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<PageLayout session={session}>
|
|
22
|
+
{/* Header */}
|
|
23
|
+
<div className="mb-6">
|
|
24
|
+
<h1 className="text-2xl font-semibold">Settings</h1>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
{/* Tab navigation */}
|
|
28
|
+
<div className="flex gap-1 border-b border-border mb-6">
|
|
29
|
+
{TABS.map((tab) => {
|
|
30
|
+
const isActive = activePath === tab.href || activePath.startsWith(tab.href + '/');
|
|
31
|
+
const Icon = tab.icon;
|
|
32
|
+
return (
|
|
33
|
+
<a
|
|
34
|
+
key={tab.id}
|
|
35
|
+
href={tab.href}
|
|
36
|
+
className={`inline-flex items-center gap-2 px-3 py-2 text-sm font-medium border-b-2 transition-colors ${
|
|
37
|
+
isActive
|
|
38
|
+
? 'border-foreground text-foreground'
|
|
39
|
+
: 'border-transparent text-muted-foreground hover:text-foreground hover:border-border'
|
|
40
|
+
}`}
|
|
41
|
+
>
|
|
42
|
+
<Icon size={14} />
|
|
43
|
+
{tab.label}
|
|
44
|
+
</a>
|
|
45
|
+
);
|
|
46
|
+
})}
|
|
47
|
+
</div>
|
|
48
|
+
|
|
49
|
+
{/* Tab content */}
|
|
50
|
+
{children}
|
|
51
|
+
</PageLayout>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { KeyIcon, CopyIcon, CheckIcon, TrashIcon, RefreshIcon } from "./icons.js";
|
|
5
|
+
import { createNewApiKey, getApiKeys, deleteApiKey } from "../actions.js";
|
|
6
|
+
function timeAgo(ts) {
|
|
7
|
+
if (!ts) return "Never";
|
|
8
|
+
const seconds = Math.floor((Date.now() - ts) / 1e3);
|
|
9
|
+
if (seconds < 60) return "just now";
|
|
10
|
+
const minutes = Math.floor(seconds / 60);
|
|
11
|
+
if (minutes < 60) return `${minutes}m ago`;
|
|
12
|
+
const hours = Math.floor(minutes / 60);
|
|
13
|
+
if (hours < 24) return `${hours}h ago`;
|
|
14
|
+
const days = Math.floor(hours / 24);
|
|
15
|
+
if (days < 30) return `${days}d ago`;
|
|
16
|
+
const months = Math.floor(days / 30);
|
|
17
|
+
return `${months}mo ago`;
|
|
18
|
+
}
|
|
19
|
+
function formatDate(ts) {
|
|
20
|
+
if (!ts) return "\u2014";
|
|
21
|
+
return new Date(ts).toLocaleDateString(void 0, {
|
|
22
|
+
year: "numeric",
|
|
23
|
+
month: "short",
|
|
24
|
+
day: "numeric"
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
function CopyButton({ text }) {
|
|
28
|
+
const [copied, setCopied] = useState(false);
|
|
29
|
+
const handleCopy = async () => {
|
|
30
|
+
try {
|
|
31
|
+
await navigator.clipboard.writeText(text);
|
|
32
|
+
setCopied(true);
|
|
33
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
34
|
+
} catch {
|
|
35
|
+
const textarea = document.createElement("textarea");
|
|
36
|
+
textarea.value = text;
|
|
37
|
+
document.body.appendChild(textarea);
|
|
38
|
+
textarea.select();
|
|
39
|
+
document.execCommand("copy");
|
|
40
|
+
document.body.removeChild(textarea);
|
|
41
|
+
setCopied(true);
|
|
42
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
return /* @__PURE__ */ jsxs(
|
|
46
|
+
"button",
|
|
47
|
+
{
|
|
48
|
+
onClick: handleCopy,
|
|
49
|
+
className: "inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border border-border bg-background text-muted-foreground hover:bg-accent hover:text-foreground",
|
|
50
|
+
children: [
|
|
51
|
+
copied ? /* @__PURE__ */ jsx(CheckIcon, { size: 14 }) : /* @__PURE__ */ jsx(CopyIcon, { size: 14 }),
|
|
52
|
+
copied ? "Copied" : "Copy"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
function Section({ title, description, children }) {
|
|
58
|
+
return /* @__PURE__ */ jsxs("div", { className: "pb-8 mb-8 border-b border-border last:border-b-0 last:pb-0 last:mb-0", children: [
|
|
59
|
+
/* @__PURE__ */ jsx("h2", { className: "text-base font-medium mb-1", children: title }),
|
|
60
|
+
description && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-4", children: description }),
|
|
61
|
+
children
|
|
62
|
+
] });
|
|
63
|
+
}
|
|
64
|
+
function ApiKeySection() {
|
|
65
|
+
const [currentKey, setCurrentKey] = useState(null);
|
|
66
|
+
const [loading, setLoading] = useState(true);
|
|
67
|
+
const [creating, setCreating] = useState(false);
|
|
68
|
+
const [newKey, setNewKey] = useState(null);
|
|
69
|
+
const [confirmDelete, setConfirmDelete] = useState(false);
|
|
70
|
+
const [confirmRegenerate, setConfirmRegenerate] = useState(false);
|
|
71
|
+
const [error, setError] = useState(null);
|
|
72
|
+
const loadKey = async () => {
|
|
73
|
+
try {
|
|
74
|
+
const result = await getApiKeys();
|
|
75
|
+
setCurrentKey(result);
|
|
76
|
+
} catch {
|
|
77
|
+
} finally {
|
|
78
|
+
setLoading(false);
|
|
79
|
+
}
|
|
80
|
+
};
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
loadKey();
|
|
83
|
+
}, []);
|
|
84
|
+
const handleCreate = async () => {
|
|
85
|
+
if (creating) return;
|
|
86
|
+
setCreating(true);
|
|
87
|
+
setError(null);
|
|
88
|
+
setConfirmRegenerate(false);
|
|
89
|
+
try {
|
|
90
|
+
const result = await createNewApiKey();
|
|
91
|
+
if (result.error) {
|
|
92
|
+
setError(result.error);
|
|
93
|
+
} else {
|
|
94
|
+
setNewKey(result.key);
|
|
95
|
+
await loadKey();
|
|
96
|
+
}
|
|
97
|
+
} catch {
|
|
98
|
+
setError("Failed to create API key");
|
|
99
|
+
} finally {
|
|
100
|
+
setCreating(false);
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const handleDelete = async () => {
|
|
104
|
+
if (!confirmDelete) {
|
|
105
|
+
setConfirmDelete(true);
|
|
106
|
+
setTimeout(() => setConfirmDelete(false), 3e3);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
await deleteApiKey();
|
|
111
|
+
setCurrentKey(null);
|
|
112
|
+
setNewKey(null);
|
|
113
|
+
setConfirmDelete(false);
|
|
114
|
+
} catch {
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const handleRegenerate = () => {
|
|
118
|
+
if (!confirmRegenerate) {
|
|
119
|
+
setConfirmRegenerate(true);
|
|
120
|
+
setTimeout(() => setConfirmRegenerate(false), 3e3);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
handleCreate();
|
|
124
|
+
};
|
|
125
|
+
if (loading) {
|
|
126
|
+
return /* @__PURE__ */ jsx("div", { className: "h-14 animate-pulse rounded-md bg-border/50" });
|
|
127
|
+
}
|
|
128
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
129
|
+
error && /* @__PURE__ */ jsx("p", { className: "text-sm text-destructive mb-4", children: error }),
|
|
130
|
+
newKey && /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-green-500/30 bg-green-500/5 p-4 mb-4", children: [
|
|
131
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3 mb-2", children: [
|
|
132
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-green-600 dark:text-green-400", children: "API key created \u2014 copy it now. You won't be able to see it again." }),
|
|
133
|
+
/* @__PURE__ */ jsx(
|
|
134
|
+
"button",
|
|
135
|
+
{
|
|
136
|
+
onClick: () => setNewKey(null),
|
|
137
|
+
className: "text-xs text-muted-foreground hover:text-foreground shrink-0",
|
|
138
|
+
children: "Dismiss"
|
|
139
|
+
}
|
|
140
|
+
)
|
|
141
|
+
] }),
|
|
142
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
143
|
+
/* @__PURE__ */ jsx("code", { className: "flex-1 rounded-md bg-muted px-3 py-2 text-xs font-mono break-all select-all", children: newKey }),
|
|
144
|
+
/* @__PURE__ */ jsx(CopyButton, { text: newKey })
|
|
145
|
+
] })
|
|
146
|
+
] }),
|
|
147
|
+
currentKey ? /* @__PURE__ */ jsx("div", { className: "rounded-lg border bg-card p-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
148
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
149
|
+
/* @__PURE__ */ jsx("div", { className: "shrink-0 rounded-md bg-muted p-2", children: /* @__PURE__ */ jsx(KeyIcon, { size: 16 }) }),
|
|
150
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
151
|
+
/* @__PURE__ */ jsxs("code", { className: "text-sm font-mono", children: [
|
|
152
|
+
currentKey.keyPrefix,
|
|
153
|
+
"..."
|
|
154
|
+
] }),
|
|
155
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs text-muted-foreground mt-0.5", children: [
|
|
156
|
+
"Created ",
|
|
157
|
+
formatDate(currentKey.createdAt),
|
|
158
|
+
currentKey.lastUsedAt && /* @__PURE__ */ jsxs("span", { className: "ml-2", children: [
|
|
159
|
+
"\xB7 Last used ",
|
|
160
|
+
timeAgo(currentKey.lastUsedAt)
|
|
161
|
+
] })
|
|
162
|
+
] })
|
|
163
|
+
] })
|
|
164
|
+
] }),
|
|
165
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
166
|
+
/* @__PURE__ */ jsxs(
|
|
167
|
+
"button",
|
|
168
|
+
{
|
|
169
|
+
onClick: handleRegenerate,
|
|
170
|
+
disabled: creating,
|
|
171
|
+
className: `inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border ${confirmRegenerate ? "border-yellow-500 text-yellow-600 hover:bg-yellow-500/10" : "border-border text-muted-foreground hover:bg-accent hover:text-foreground"} disabled:opacity-50`,
|
|
172
|
+
children: [
|
|
173
|
+
/* @__PURE__ */ jsx(RefreshIcon, { size: 12 }),
|
|
174
|
+
creating ? "Generating..." : confirmRegenerate ? "Confirm regenerate" : "Regenerate"
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
),
|
|
178
|
+
/* @__PURE__ */ jsxs(
|
|
179
|
+
"button",
|
|
180
|
+
{
|
|
181
|
+
onClick: handleDelete,
|
|
182
|
+
className: `inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium border ${confirmDelete ? "border-destructive text-destructive hover:bg-destructive/10" : "border-border text-muted-foreground hover:text-destructive hover:border-destructive/50"}`,
|
|
183
|
+
children: [
|
|
184
|
+
/* @__PURE__ */ jsx(TrashIcon, { size: 12 }),
|
|
185
|
+
confirmDelete ? "Confirm delete" : "Delete"
|
|
186
|
+
]
|
|
187
|
+
}
|
|
188
|
+
)
|
|
189
|
+
] })
|
|
190
|
+
] }) }) : /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-dashed bg-card p-6 flex flex-col items-center text-center", children: [
|
|
191
|
+
/* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground mb-3", children: "No API key configured" }),
|
|
192
|
+
/* @__PURE__ */ jsx(
|
|
193
|
+
"button",
|
|
194
|
+
{
|
|
195
|
+
onClick: handleCreate,
|
|
196
|
+
disabled: creating,
|
|
197
|
+
className: "inline-flex items-center gap-2 rounded-md px-3 py-2 text-sm font-medium bg-foreground text-background hover:bg-foreground/90 disabled:opacity-50 disabled:pointer-events-none",
|
|
198
|
+
children: creating ? "Creating..." : "Create API key"
|
|
199
|
+
}
|
|
200
|
+
)
|
|
201
|
+
] })
|
|
202
|
+
] });
|
|
203
|
+
}
|
|
204
|
+
function SettingsSecretsPage() {
|
|
205
|
+
return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
|
|
206
|
+
Section,
|
|
207
|
+
{
|
|
208
|
+
title: "API Key",
|
|
209
|
+
description: "Authenticates external requests to /api endpoints. Pass via the x-api-key header.",
|
|
210
|
+
children: /* @__PURE__ */ jsx(ApiKeySection, {})
|
|
211
|
+
}
|
|
212
|
+
) });
|
|
213
|
+
}
|
|
214
|
+
export {
|
|
215
|
+
SettingsSecretsPage
|
|
216
|
+
};
|