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
package/lib/chat/api.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { auth } from '../auth/index.js';
|
|
2
|
+
import { chatStream } from '../ai/index.js';
|
|
3
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* POST handler for /stream/chat — streaming chat with session auth.
|
|
7
|
+
* Dedicated route handler separate from the catch-all api/index.js.
|
|
8
|
+
*/
|
|
9
|
+
export async function POST(request) {
|
|
10
|
+
const session = await auth();
|
|
11
|
+
if (!session?.user?.id) {
|
|
12
|
+
return Response.json({ error: 'Unauthorized' }, { status: 401 });
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const body = await request.json();
|
|
16
|
+
const { messages, chatId: rawChatId, trigger, codeMode, repo, branch } = body;
|
|
17
|
+
|
|
18
|
+
if (!messages?.length) {
|
|
19
|
+
return Response.json({ error: 'No messages' }, { status: 400 });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Get the last user message — AI SDK v5 sends UIMessage[] with parts
|
|
23
|
+
const lastUserMessage = [...messages].reverse().find((m) => m.role === 'user');
|
|
24
|
+
if (!lastUserMessage) {
|
|
25
|
+
return Response.json({ error: 'No user message' }, { status: 400 });
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Extract text from message parts (AI SDK v5+) or fall back to content
|
|
29
|
+
let userText =
|
|
30
|
+
lastUserMessage.parts
|
|
31
|
+
?.filter((p) => p.type === 'text')
|
|
32
|
+
.map((p) => p.text)
|
|
33
|
+
.join('\n') ||
|
|
34
|
+
lastUserMessage.content ||
|
|
35
|
+
'';
|
|
36
|
+
|
|
37
|
+
// Extract file parts from message
|
|
38
|
+
const fileParts = lastUserMessage.parts?.filter((p) => p.type === 'file') || [];
|
|
39
|
+
const attachments = [];
|
|
40
|
+
|
|
41
|
+
for (const part of fileParts) {
|
|
42
|
+
const { mediaType, url } = part;
|
|
43
|
+
if (!mediaType || !url) continue;
|
|
44
|
+
|
|
45
|
+
if (mediaType.startsWith('image/') || mediaType === 'application/pdf') {
|
|
46
|
+
// Images and PDFs → pass as visual attachments for the LLM
|
|
47
|
+
attachments.push({ category: 'image', mimeType: mediaType, dataUrl: url });
|
|
48
|
+
} else if (mediaType.startsWith('text/') || mediaType === 'application/json') {
|
|
49
|
+
// Text files → decode base64 data URL and inline into message text
|
|
50
|
+
try {
|
|
51
|
+
const base64Data = url.split(',')[1];
|
|
52
|
+
const textContent = Buffer.from(base64Data, 'base64').toString('utf-8');
|
|
53
|
+
const fileName = part.name || 'file';
|
|
54
|
+
userText += `\n\nFile: ${fileName}\n\`\`\`\n${textContent}\n\`\`\``;
|
|
55
|
+
} catch (e) {
|
|
56
|
+
console.error('Failed to decode text file:', e);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (!userText.trim() && attachments.length === 0) {
|
|
62
|
+
return Response.json({ error: 'Empty message' }, { status: 400 });
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Map web channel to thread_id — AI layer handles DB persistence
|
|
66
|
+
const threadId = rawChatId || uuidv4();
|
|
67
|
+
const { createUIMessageStream, createUIMessageStreamResponse } = await import('ai');
|
|
68
|
+
|
|
69
|
+
const stream = createUIMessageStream({
|
|
70
|
+
onError: (error) => {
|
|
71
|
+
console.error('Chat stream error:', error);
|
|
72
|
+
return error?.message || 'An error occurred while processing your message.';
|
|
73
|
+
},
|
|
74
|
+
execute: async ({ writer }) => {
|
|
75
|
+
// chatStream handles: save user msg, invoke agent, save assistant msg, auto-title
|
|
76
|
+
const skipUserPersist = trigger === 'regenerate-message';
|
|
77
|
+
const streamOptions = {
|
|
78
|
+
userId: session.user.id,
|
|
79
|
+
skipUserPersist,
|
|
80
|
+
};
|
|
81
|
+
if (codeMode && repo && branch) {
|
|
82
|
+
streamOptions.repo = repo;
|
|
83
|
+
streamOptions.branch = branch;
|
|
84
|
+
}
|
|
85
|
+
const chunks = chatStream(threadId, userText, attachments, streamOptions);
|
|
86
|
+
|
|
87
|
+
// Signal start of assistant message
|
|
88
|
+
writer.write({ type: 'start' });
|
|
89
|
+
|
|
90
|
+
let textStarted = false;
|
|
91
|
+
let textId = uuidv4();
|
|
92
|
+
|
|
93
|
+
for await (const chunk of chunks) {
|
|
94
|
+
if (chunk.type === 'text') {
|
|
95
|
+
if (!textStarted) {
|
|
96
|
+
textId = uuidv4();
|
|
97
|
+
writer.write({ type: 'text-start', id: textId });
|
|
98
|
+
textStarted = true;
|
|
99
|
+
}
|
|
100
|
+
writer.write({ type: 'text-delta', id: textId, delta: chunk.text });
|
|
101
|
+
|
|
102
|
+
} else if (chunk.type === 'tool-call') {
|
|
103
|
+
// Close any open text block before tool events
|
|
104
|
+
if (textStarted) {
|
|
105
|
+
writer.write({ type: 'text-end', id: textId });
|
|
106
|
+
textStarted = false;
|
|
107
|
+
}
|
|
108
|
+
writer.write({
|
|
109
|
+
type: 'tool-input-start',
|
|
110
|
+
toolCallId: chunk.toolCallId,
|
|
111
|
+
toolName: chunk.toolName,
|
|
112
|
+
});
|
|
113
|
+
writer.write({
|
|
114
|
+
type: 'tool-input-available',
|
|
115
|
+
toolCallId: chunk.toolCallId,
|
|
116
|
+
toolName: chunk.toolName,
|
|
117
|
+
input: chunk.args,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
} else if (chunk.type === 'tool-result') {
|
|
121
|
+
writer.write({
|
|
122
|
+
type: 'tool-output-available',
|
|
123
|
+
toolCallId: chunk.toolCallId,
|
|
124
|
+
output: chunk.result,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Close final text block if still open
|
|
130
|
+
if (textStarted) {
|
|
131
|
+
writer.write({ type: 'text-end', id: textId });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Signal end of assistant message
|
|
135
|
+
writer.write({ type: 'finish' });
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
return createUIMessageStreamResponse({ stream });
|
|
140
|
+
}
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useState, useEffect } from "react";
|
|
4
|
+
import { CirclePlusIcon, PanelLeftIcon, MessageIcon, BellIcon, SwarmIcon, ArrowUpCircleIcon, LifeBuoyIcon, GitPullRequestIcon } from "./icons.js";
|
|
5
|
+
import { getUnreadNotificationCount, getPullRequestCount, getAppVersion } from "../actions.js";
|
|
6
|
+
import { SidebarHistory } from "./sidebar-history.js";
|
|
7
|
+
import { SidebarUserNav } from "./sidebar-user-nav.js";
|
|
8
|
+
import { UpgradeDialog } from "./upgrade-dialog.js";
|
|
9
|
+
import {
|
|
10
|
+
Sidebar,
|
|
11
|
+
SidebarContent,
|
|
12
|
+
SidebarFooter,
|
|
13
|
+
SidebarHeader,
|
|
14
|
+
SidebarMenu,
|
|
15
|
+
SidebarMenuItem,
|
|
16
|
+
SidebarMenuButton,
|
|
17
|
+
useSidebar
|
|
18
|
+
} from "./ui/sidebar.js";
|
|
19
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip.js";
|
|
20
|
+
import { useChatNav } from "./chat-nav-context.js";
|
|
21
|
+
function AppSidebar({ user }) {
|
|
22
|
+
const { navigateToChat } = useChatNav();
|
|
23
|
+
const { state, open, setOpenMobile, toggleSidebar } = useSidebar();
|
|
24
|
+
const collapsed = state === "collapsed";
|
|
25
|
+
const [unreadCount, setUnreadCount] = useState(0);
|
|
26
|
+
const [prCount, setPrCount] = useState(0);
|
|
27
|
+
const [version, setVersion] = useState("");
|
|
28
|
+
const [updateAvailable, setUpdateAvailable] = useState(null);
|
|
29
|
+
const [changelog, setChangelog] = useState(null);
|
|
30
|
+
const [upgradeOpen, setUpgradeOpen] = useState(false);
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
function fetchCounts() {
|
|
33
|
+
getUnreadNotificationCount().then((count) => setUnreadCount(count)).catch(() => {
|
|
34
|
+
});
|
|
35
|
+
getPullRequestCount().then((count) => setPrCount(count)).catch(() => {
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
fetchCounts();
|
|
39
|
+
const interval = setInterval(fetchCounts, 10 * 60 * 1e3);
|
|
40
|
+
return () => clearInterval(interval);
|
|
41
|
+
}, []);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
getAppVersion().then(({ version: version2, updateAvailable: updateAvailable2, changelog: changelog2 }) => {
|
|
44
|
+
setVersion(version2);
|
|
45
|
+
setUpdateAvailable(updateAvailable2);
|
|
46
|
+
setChangelog(changelog2);
|
|
47
|
+
}).catch(() => {
|
|
48
|
+
});
|
|
49
|
+
}, []);
|
|
50
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
51
|
+
/* @__PURE__ */ jsxs(Sidebar, { children: [
|
|
52
|
+
/* @__PURE__ */ jsxs(SidebarHeader, { children: [
|
|
53
|
+
/* @__PURE__ */ jsxs("div", { className: collapsed ? "flex justify-center" : "flex items-center justify-between", children: [
|
|
54
|
+
!collapsed && /* @__PURE__ */ jsxs("span", { className: "px-2 font-semibold text-lg", children: [
|
|
55
|
+
"GigaClaw",
|
|
56
|
+
version && /* @__PURE__ */ jsxs("span", { className: "text-[11px] font-normal text-muted-foreground", children: [
|
|
57
|
+
" v",
|
|
58
|
+
version
|
|
59
|
+
] })
|
|
60
|
+
] }),
|
|
61
|
+
/* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
62
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx(
|
|
63
|
+
"button",
|
|
64
|
+
{
|
|
65
|
+
className: "inline-flex shrink-0 items-center justify-center rounded-md p-2 text-muted-foreground hover:bg-background hover:text-foreground",
|
|
66
|
+
onClick: toggleSidebar,
|
|
67
|
+
children: /* @__PURE__ */ jsx(PanelLeftIcon, { size: 16 })
|
|
68
|
+
}
|
|
69
|
+
) }),
|
|
70
|
+
/* @__PURE__ */ jsx(TooltipContent, { side: collapsed ? "right" : "bottom", children: collapsed ? "Open sidebar" : "Close sidebar" })
|
|
71
|
+
] })
|
|
72
|
+
] }),
|
|
73
|
+
/* @__PURE__ */ jsxs(SidebarMenu, { children: [
|
|
74
|
+
/* @__PURE__ */ jsx(SidebarMenuItem, { className: "mb-2", children: /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
75
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
76
|
+
SidebarMenuButton,
|
|
77
|
+
{
|
|
78
|
+
href: "/",
|
|
79
|
+
className: collapsed ? "justify-center" : "",
|
|
80
|
+
onClick: (e) => {
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
navigateToChat(null);
|
|
83
|
+
setOpenMobile(false);
|
|
84
|
+
},
|
|
85
|
+
children: [
|
|
86
|
+
/* @__PURE__ */ jsx(CirclePlusIcon, { size: 16 }),
|
|
87
|
+
!collapsed && /* @__PURE__ */ jsx("span", { children: "New chat" })
|
|
88
|
+
]
|
|
89
|
+
}
|
|
90
|
+
) }),
|
|
91
|
+
collapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "New chat" })
|
|
92
|
+
] }) }),
|
|
93
|
+
/* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
94
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
95
|
+
SidebarMenuButton,
|
|
96
|
+
{
|
|
97
|
+
href: "/chats",
|
|
98
|
+
className: collapsed ? "justify-center" : "",
|
|
99
|
+
children: [
|
|
100
|
+
/* @__PURE__ */ jsx(MessageIcon, { size: 16 }),
|
|
101
|
+
!collapsed && /* @__PURE__ */ jsx("span", { children: "Chats" })
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
) }),
|
|
105
|
+
collapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Chats" })
|
|
106
|
+
] }) }),
|
|
107
|
+
/* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
108
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
109
|
+
SidebarMenuButton,
|
|
110
|
+
{
|
|
111
|
+
href: "/swarm",
|
|
112
|
+
className: collapsed ? "justify-center" : "",
|
|
113
|
+
children: [
|
|
114
|
+
/* @__PURE__ */ jsx(SwarmIcon, { size: 16 }),
|
|
115
|
+
!collapsed && /* @__PURE__ */ jsx("span", { children: "Swarm" })
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
) }),
|
|
119
|
+
collapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Swarm" })
|
|
120
|
+
] }) }),
|
|
121
|
+
/* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
122
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
123
|
+
SidebarMenuButton,
|
|
124
|
+
{
|
|
125
|
+
href: "/notifications",
|
|
126
|
+
className: collapsed ? "justify-center" : "",
|
|
127
|
+
children: [
|
|
128
|
+
/* @__PURE__ */ jsx(BellIcon, { size: 16 }),
|
|
129
|
+
!collapsed && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
130
|
+
"Notifications",
|
|
131
|
+
unreadCount > 0 && /* @__PURE__ */ jsx("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", children: unreadCount })
|
|
132
|
+
] }),
|
|
133
|
+
collapsed && unreadCount > 0 && /* @__PURE__ */ jsx("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", children: unreadCount })
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
) }),
|
|
137
|
+
collapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Notifications" })
|
|
138
|
+
] }) }),
|
|
139
|
+
/* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
140
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
141
|
+
SidebarMenuButton,
|
|
142
|
+
{
|
|
143
|
+
href: "/pull-requests",
|
|
144
|
+
className: collapsed ? "justify-center" : "",
|
|
145
|
+
children: [
|
|
146
|
+
/* @__PURE__ */ jsx(GitPullRequestIcon, { size: 16 }),
|
|
147
|
+
!collapsed && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
148
|
+
"Pending Changes",
|
|
149
|
+
prCount > 0 && /* @__PURE__ */ jsx("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", children: prCount })
|
|
150
|
+
] }),
|
|
151
|
+
collapsed && prCount > 0 && /* @__PURE__ */ jsx("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", children: prCount })
|
|
152
|
+
]
|
|
153
|
+
}
|
|
154
|
+
) }),
|
|
155
|
+
collapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Pending Changes" })
|
|
156
|
+
] }) }),
|
|
157
|
+
updateAvailable && /* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
158
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
159
|
+
SidebarMenuButton,
|
|
160
|
+
{
|
|
161
|
+
className: collapsed ? "justify-center" : "",
|
|
162
|
+
onClick: () => setUpgradeOpen(true),
|
|
163
|
+
children: [
|
|
164
|
+
/* @__PURE__ */ jsxs("span", { className: "relative", children: [
|
|
165
|
+
/* @__PURE__ */ jsx(ArrowUpCircleIcon, { size: 16 }),
|
|
166
|
+
collapsed && /* @__PURE__ */ jsx("span", { className: "absolute -top-1 -right-1 inline-block h-2 w-2 rounded-full bg-emerald-500" })
|
|
167
|
+
] }),
|
|
168
|
+
!collapsed && /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
|
|
169
|
+
"Upgrade",
|
|
170
|
+
/* @__PURE__ */ jsxs("span", { className: "inline-flex items-center justify-center rounded-full bg-emerald-500 px-1.5 py-0.5 text-[10px] font-medium leading-none text-white", children: [
|
|
171
|
+
"v",
|
|
172
|
+
updateAvailable
|
|
173
|
+
] })
|
|
174
|
+
] })
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
) }),
|
|
178
|
+
collapsed && /* @__PURE__ */ jsxs(TooltipContent, { side: "right", children: [
|
|
179
|
+
"Upgrade to v",
|
|
180
|
+
updateAvailable
|
|
181
|
+
] })
|
|
182
|
+
] }) }),
|
|
183
|
+
/* @__PURE__ */ jsx(SidebarMenuItem, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [
|
|
184
|
+
/* @__PURE__ */ jsx(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(
|
|
185
|
+
SidebarMenuButton,
|
|
186
|
+
{
|
|
187
|
+
href: "https://gigaclaw.gignaati.com",
|
|
188
|
+
target: "_blank",
|
|
189
|
+
rel: "noopener noreferrer",
|
|
190
|
+
className: collapsed ? "justify-center" : "",
|
|
191
|
+
children: [
|
|
192
|
+
/* @__PURE__ */ jsx(LifeBuoyIcon, { size: 16 }),
|
|
193
|
+
!collapsed && /* @__PURE__ */ jsx("span", { children: "Support" })
|
|
194
|
+
]
|
|
195
|
+
}
|
|
196
|
+
) }),
|
|
197
|
+
collapsed && /* @__PURE__ */ jsx(TooltipContent, { side: "right", children: "Support" })
|
|
198
|
+
] }) })
|
|
199
|
+
] })
|
|
200
|
+
] }),
|
|
201
|
+
!collapsed && /* @__PURE__ */ jsxs(SidebarContent, { children: [
|
|
202
|
+
/* @__PURE__ */ jsx("div", { className: "mx-4 mb-1 border-t border-border" }),
|
|
203
|
+
/* @__PURE__ */ jsx(SidebarHistory, {})
|
|
204
|
+
] }),
|
|
205
|
+
collapsed && /* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
206
|
+
/* @__PURE__ */ jsx(SidebarFooter, { children: user && /* @__PURE__ */ jsx(SidebarUserNav, { user, collapsed }) })
|
|
207
|
+
] }),
|
|
208
|
+
/* @__PURE__ */ jsx(UpgradeDialog, { open: upgradeOpen, onClose: () => setUpgradeOpen(false), version, updateAvailable, changelog })
|
|
209
|
+
] });
|
|
210
|
+
}
|
|
211
|
+
export {
|
|
212
|
+
AppSidebar
|
|
213
|
+
};
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react';
|
|
4
|
+
import { CirclePlusIcon, PanelLeftIcon, MessageIcon, BellIcon, SwarmIcon, ArrowUpCircleIcon, LifeBuoyIcon, GitPullRequestIcon } from './icons.js';
|
|
5
|
+
import { getUnreadNotificationCount, getPullRequestCount, getAppVersion } from '../actions.js';
|
|
6
|
+
import { SidebarHistory } from './sidebar-history.js';
|
|
7
|
+
import { SidebarUserNav } from './sidebar-user-nav.js';
|
|
8
|
+
import { UpgradeDialog } from './upgrade-dialog.js';
|
|
9
|
+
import {
|
|
10
|
+
Sidebar,
|
|
11
|
+
SidebarContent,
|
|
12
|
+
SidebarFooter,
|
|
13
|
+
SidebarHeader,
|
|
14
|
+
SidebarMenu,
|
|
15
|
+
SidebarMenuItem,
|
|
16
|
+
SidebarMenuButton,
|
|
17
|
+
useSidebar,
|
|
18
|
+
} from './ui/sidebar.js';
|
|
19
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from './ui/tooltip.js';
|
|
20
|
+
import { useChatNav } from './chat-nav-context.js';
|
|
21
|
+
|
|
22
|
+
export function AppSidebar({ user }) {
|
|
23
|
+
const { navigateToChat } = useChatNav();
|
|
24
|
+
const { state, open, setOpenMobile, toggleSidebar } = useSidebar();
|
|
25
|
+
const collapsed = state === 'collapsed';
|
|
26
|
+
const [unreadCount, setUnreadCount] = useState(0);
|
|
27
|
+
const [prCount, setPrCount] = useState(0);
|
|
28
|
+
const [version, setVersion] = useState('');
|
|
29
|
+
const [updateAvailable, setUpdateAvailable] = useState(null);
|
|
30
|
+
const [changelog, setChangelog] = useState(null);
|
|
31
|
+
const [upgradeOpen, setUpgradeOpen] = useState(false);
|
|
32
|
+
|
|
33
|
+
// Fetch badge counts (notifications + PRs) — run immediately, then every 10 minutes
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
function fetchCounts() {
|
|
36
|
+
getUnreadNotificationCount()
|
|
37
|
+
.then((count) => setUnreadCount(count))
|
|
38
|
+
.catch(() => {});
|
|
39
|
+
getPullRequestCount()
|
|
40
|
+
.then((count) => setPrCount(count))
|
|
41
|
+
.catch(() => {});
|
|
42
|
+
}
|
|
43
|
+
fetchCounts();
|
|
44
|
+
const interval = setInterval(fetchCounts, 10 * 60 * 1000);
|
|
45
|
+
return () => clearInterval(interval);
|
|
46
|
+
}, []);
|
|
47
|
+
|
|
48
|
+
// Version check — one-time on mount
|
|
49
|
+
useEffect(() => {
|
|
50
|
+
getAppVersion()
|
|
51
|
+
.then(({ version, updateAvailable, changelog }) => {
|
|
52
|
+
setVersion(version);
|
|
53
|
+
setUpdateAvailable(updateAvailable);
|
|
54
|
+
setChangelog(changelog);
|
|
55
|
+
})
|
|
56
|
+
.catch(() => {});
|
|
57
|
+
}, []);
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<>
|
|
61
|
+
<Sidebar>
|
|
62
|
+
<SidebarHeader>
|
|
63
|
+
{/* Top row: brand name + toggle icon (open) or just toggle icon (collapsed) */}
|
|
64
|
+
<div className={collapsed ? 'flex justify-center' : 'flex items-center justify-between'}>
|
|
65
|
+
{!collapsed && (
|
|
66
|
+
<span className="px-2 font-semibold text-lg">GigaClaw{version && <span className="text-[11px] font-normal text-muted-foreground"> v{version}</span>}</span>
|
|
67
|
+
)}
|
|
68
|
+
<Tooltip>
|
|
69
|
+
<TooltipTrigger asChild>
|
|
70
|
+
<button
|
|
71
|
+
className="inline-flex shrink-0 items-center justify-center rounded-md p-2 text-muted-foreground hover:bg-background hover:text-foreground"
|
|
72
|
+
onClick={toggleSidebar}
|
|
73
|
+
>
|
|
74
|
+
<PanelLeftIcon size={16} />
|
|
75
|
+
</button>
|
|
76
|
+
</TooltipTrigger>
|
|
77
|
+
<TooltipContent side={collapsed ? 'right' : 'bottom'}>
|
|
78
|
+
{collapsed ? 'Open sidebar' : 'Close sidebar'}
|
|
79
|
+
</TooltipContent>
|
|
80
|
+
</Tooltip>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<SidebarMenu>
|
|
84
|
+
{/* New chat */}
|
|
85
|
+
<SidebarMenuItem className="mb-2">
|
|
86
|
+
<Tooltip>
|
|
87
|
+
<TooltipTrigger asChild>
|
|
88
|
+
<SidebarMenuButton
|
|
89
|
+
href="/"
|
|
90
|
+
className={collapsed ? 'justify-center' : ''}
|
|
91
|
+
onClick={(e) => {
|
|
92
|
+
e.preventDefault();
|
|
93
|
+
navigateToChat(null);
|
|
94
|
+
setOpenMobile(false);
|
|
95
|
+
}}
|
|
96
|
+
>
|
|
97
|
+
<CirclePlusIcon size={16} />
|
|
98
|
+
{!collapsed && <span>New chat</span>}
|
|
99
|
+
</SidebarMenuButton>
|
|
100
|
+
</TooltipTrigger>
|
|
101
|
+
{collapsed && (
|
|
102
|
+
<TooltipContent side="right">New chat</TooltipContent>
|
|
103
|
+
)}
|
|
104
|
+
</Tooltip>
|
|
105
|
+
</SidebarMenuItem>
|
|
106
|
+
|
|
107
|
+
{/* Chats history */}
|
|
108
|
+
<SidebarMenuItem>
|
|
109
|
+
<Tooltip>
|
|
110
|
+
<TooltipTrigger asChild>
|
|
111
|
+
<SidebarMenuButton
|
|
112
|
+
href="/chats"
|
|
113
|
+
className={collapsed ? 'justify-center' : ''}
|
|
114
|
+
>
|
|
115
|
+
<MessageIcon size={16} />
|
|
116
|
+
{!collapsed && <span>Chats</span>}
|
|
117
|
+
</SidebarMenuButton>
|
|
118
|
+
</TooltipTrigger>
|
|
119
|
+
{collapsed && (
|
|
120
|
+
<TooltipContent side="right">Chats</TooltipContent>
|
|
121
|
+
)}
|
|
122
|
+
</Tooltip>
|
|
123
|
+
</SidebarMenuItem>
|
|
124
|
+
|
|
125
|
+
{/* Swarm */}
|
|
126
|
+
<SidebarMenuItem>
|
|
127
|
+
<Tooltip>
|
|
128
|
+
<TooltipTrigger asChild>
|
|
129
|
+
<SidebarMenuButton
|
|
130
|
+
href="/swarm"
|
|
131
|
+
className={collapsed ? 'justify-center' : ''}
|
|
132
|
+
>
|
|
133
|
+
<SwarmIcon size={16} />
|
|
134
|
+
{!collapsed && <span>Swarm</span>}
|
|
135
|
+
</SidebarMenuButton>
|
|
136
|
+
</TooltipTrigger>
|
|
137
|
+
{collapsed && (
|
|
138
|
+
<TooltipContent side="right">Swarm</TooltipContent>
|
|
139
|
+
)}
|
|
140
|
+
</Tooltip>
|
|
141
|
+
</SidebarMenuItem>
|
|
142
|
+
|
|
143
|
+
{/* Notifications */}
|
|
144
|
+
<SidebarMenuItem>
|
|
145
|
+
<Tooltip>
|
|
146
|
+
<TooltipTrigger asChild>
|
|
147
|
+
<SidebarMenuButton
|
|
148
|
+
href="/notifications"
|
|
149
|
+
className={collapsed ? 'justify-center' : ''}
|
|
150
|
+
>
|
|
151
|
+
<BellIcon size={16} />
|
|
152
|
+
{!collapsed && (
|
|
153
|
+
<span className="flex items-center gap-2">
|
|
154
|
+
Notifications
|
|
155
|
+
{unreadCount > 0 && (
|
|
156
|
+
<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">
|
|
157
|
+
{unreadCount}
|
|
158
|
+
</span>
|
|
159
|
+
)}
|
|
160
|
+
</span>
|
|
161
|
+
)}
|
|
162
|
+
{collapsed && unreadCount > 0 && (
|
|
163
|
+
<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">
|
|
164
|
+
{unreadCount}
|
|
165
|
+
</span>
|
|
166
|
+
)}
|
|
167
|
+
</SidebarMenuButton>
|
|
168
|
+
</TooltipTrigger>
|
|
169
|
+
{collapsed && (
|
|
170
|
+
<TooltipContent side="right">Notifications</TooltipContent>
|
|
171
|
+
)}
|
|
172
|
+
</Tooltip>
|
|
173
|
+
</SidebarMenuItem>
|
|
174
|
+
|
|
175
|
+
{/* Pending Changes */}
|
|
176
|
+
<SidebarMenuItem>
|
|
177
|
+
<Tooltip>
|
|
178
|
+
<TooltipTrigger asChild>
|
|
179
|
+
<SidebarMenuButton
|
|
180
|
+
href="/pull-requests"
|
|
181
|
+
className={collapsed ? 'justify-center' : ''}
|
|
182
|
+
>
|
|
183
|
+
<GitPullRequestIcon size={16} />
|
|
184
|
+
{!collapsed && (
|
|
185
|
+
<span className="flex items-center gap-2">
|
|
186
|
+
Pending Changes
|
|
187
|
+
{prCount > 0 && (
|
|
188
|
+
<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">
|
|
189
|
+
{prCount}
|
|
190
|
+
</span>
|
|
191
|
+
)}
|
|
192
|
+
</span>
|
|
193
|
+
)}
|
|
194
|
+
{collapsed && prCount > 0 && (
|
|
195
|
+
<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">
|
|
196
|
+
{prCount}
|
|
197
|
+
</span>
|
|
198
|
+
)}
|
|
199
|
+
</SidebarMenuButton>
|
|
200
|
+
</TooltipTrigger>
|
|
201
|
+
{collapsed && (
|
|
202
|
+
<TooltipContent side="right">Pending Changes</TooltipContent>
|
|
203
|
+
)}
|
|
204
|
+
</Tooltip>
|
|
205
|
+
</SidebarMenuItem>
|
|
206
|
+
|
|
207
|
+
{/* Upgrade (only when update is available) */}
|
|
208
|
+
{updateAvailable && (
|
|
209
|
+
<SidebarMenuItem>
|
|
210
|
+
<Tooltip>
|
|
211
|
+
<TooltipTrigger asChild>
|
|
212
|
+
<SidebarMenuButton
|
|
213
|
+
className={collapsed ? 'justify-center' : ''}
|
|
214
|
+
onClick={() => setUpgradeOpen(true)}
|
|
215
|
+
>
|
|
216
|
+
<span className="relative">
|
|
217
|
+
<ArrowUpCircleIcon size={16} />
|
|
218
|
+
{collapsed && (
|
|
219
|
+
<span className="absolute -top-1 -right-1 inline-block h-2 w-2 rounded-full bg-emerald-500" />
|
|
220
|
+
)}
|
|
221
|
+
</span>
|
|
222
|
+
{!collapsed && (
|
|
223
|
+
<span className="flex items-center gap-2">
|
|
224
|
+
Upgrade
|
|
225
|
+
<span className="inline-flex items-center justify-center rounded-full bg-emerald-500 px-1.5 py-0.5 text-[10px] font-medium leading-none text-white">
|
|
226
|
+
v{updateAvailable}
|
|
227
|
+
</span>
|
|
228
|
+
</span>
|
|
229
|
+
)}
|
|
230
|
+
</SidebarMenuButton>
|
|
231
|
+
</TooltipTrigger>
|
|
232
|
+
{collapsed && (
|
|
233
|
+
<TooltipContent side="right">Upgrade to v{updateAvailable}</TooltipContent>
|
|
234
|
+
)}
|
|
235
|
+
</Tooltip>
|
|
236
|
+
</SidebarMenuItem>
|
|
237
|
+
)}
|
|
238
|
+
|
|
239
|
+
{/* Support */}
|
|
240
|
+
<SidebarMenuItem>
|
|
241
|
+
<Tooltip>
|
|
242
|
+
<TooltipTrigger asChild>
|
|
243
|
+
<SidebarMenuButton
|
|
244
|
+
href="https://gigaclaw.gignaati.com"
|
|
245
|
+
target="_blank"
|
|
246
|
+
rel="noopener noreferrer"
|
|
247
|
+
className={collapsed ? 'justify-center' : ''}
|
|
248
|
+
>
|
|
249
|
+
<LifeBuoyIcon size={16} />
|
|
250
|
+
{!collapsed && <span>Support</span>}
|
|
251
|
+
</SidebarMenuButton>
|
|
252
|
+
</TooltipTrigger>
|
|
253
|
+
{collapsed && (
|
|
254
|
+
<TooltipContent side="right">Support</TooltipContent>
|
|
255
|
+
)}
|
|
256
|
+
</Tooltip>
|
|
257
|
+
</SidebarMenuItem>
|
|
258
|
+
|
|
259
|
+
</SidebarMenu>
|
|
260
|
+
</SidebarHeader>
|
|
261
|
+
|
|
262
|
+
{!collapsed && (
|
|
263
|
+
<SidebarContent>
|
|
264
|
+
<div className="mx-4 mb-1 border-t border-border" />
|
|
265
|
+
<SidebarHistory />
|
|
266
|
+
</SidebarContent>
|
|
267
|
+
)}
|
|
268
|
+
|
|
269
|
+
{/* Spacer when collapsed to push footer down */}
|
|
270
|
+
{collapsed && <div className="flex-1" />}
|
|
271
|
+
|
|
272
|
+
<SidebarFooter>
|
|
273
|
+
{user && <SidebarUserNav user={user} collapsed={collapsed} />}
|
|
274
|
+
</SidebarFooter>
|
|
275
|
+
</Sidebar>
|
|
276
|
+
<UpgradeDialog open={upgradeOpen} onClose={() => setUpgradeOpen(false)} version={version} updateAvailable={updateAvailable} changelog={changelog} />
|
|
277
|
+
</>
|
|
278
|
+
);
|
|
279
|
+
}
|