@assistkick/create 1.10.0 → 1.12.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/dist/src/scaffolder.d.ts +12 -1
- package/dist/src/scaffolder.js +40 -3
- package/dist/src/scaffolder.js.map +1 -1
- package/package.json +1 -1
- package/templates/assistkick-product-system/package.json +1 -1
- package/templates/assistkick-product-system/packages/backend/package.json +1 -0
- package/templates/assistkick-product-system/packages/backend/src/mcp/permission_mcp_server.ts +196 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +31 -7
- package/templates/assistkick-product-system/packages/backend/src/routes/auth.ts +15 -12
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_files.test.ts +95 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_files.ts +97 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_permission.ts +94 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_sessions.ts +189 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_upload.test.ts +131 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_upload.ts +94 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +12 -3
- package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +2 -2
- package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +390 -22
- package/templates/assistkick-product-system/packages/backend/src/routes/git_branches.test.ts +306 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/git_connect.test.ts +133 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +66 -9
- package/templates/assistkick-product-system/packages/backend/src/routes/preview.ts +204 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/projects.test.ts +205 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +37 -9
- package/templates/assistkick-product-system/packages/backend/src/routes/skills.test.ts +139 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/skills.ts +95 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +5 -4
- package/templates/assistkick-product-system/packages/backend/src/routes/users.ts +4 -4
- package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +8 -8
- package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +5 -5
- package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +6 -6
- package/templates/assistkick-product-system/packages/backend/src/server.ts +107 -27
- package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +105 -203
- package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +76 -266
- package/templates/assistkick-product-system/packages/backend/src/services/chat_cli_bridge.test.ts +427 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_cli_bridge.ts +345 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_message_repository.test.ts +170 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_message_repository.ts +106 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_session_service.test.ts +217 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_session_service.ts +188 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_ws_handler.test.ts +1243 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_ws_handler.ts +894 -0
- package/templates/assistkick-product-system/packages/backend/src/services/coherence-review.ts +3 -3
- package/templates/assistkick-product-system/packages/backend/src/services/dev_command_detector.test.ts +85 -0
- package/templates/assistkick-product-system/packages/backend/src/services/dev_command_detector.ts +54 -0
- package/templates/assistkick-product-system/packages/backend/src/services/email_service.ts +13 -10
- package/templates/assistkick-product-system/packages/backend/src/services/init.ts +11 -3
- package/templates/assistkick-product-system/packages/backend/src/services/invitation_service.ts +1 -1
- package/templates/assistkick-product-system/packages/backend/src/services/password_reset_service.ts +1 -1
- package/templates/assistkick-product-system/packages/backend/src/services/permission_service.test.ts +243 -0
- package/templates/assistkick-product-system/packages/backend/src/services/permission_service.ts +259 -0
- package/templates/assistkick-product-system/packages/backend/src/services/preview_server_manager.test.ts +172 -0
- package/templates/assistkick-product-system/packages/backend/src/services/preview_server_manager.ts +225 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +29 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +17 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +255 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +300 -25
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +44 -0
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +62 -7
- package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +77 -6
- package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +129 -8
- package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +2 -1
- package/templates/assistkick-product-system/packages/backend/src/services/title_generator_service.test.ts +45 -0
- package/templates/assistkick-product-system/packages/backend/src/services/title_generator_service.ts +157 -0
- package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +4 -3
- package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +3 -3
- package/templates/assistkick-product-system/packages/frontend/package.json +5 -0
- package/templates/assistkick-product-system/packages/frontend/src/App.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +336 -5
- package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +192 -12
- package/templates/assistkick-product-system/packages/frontend/src/components/AttachmentPreviewList.tsx +98 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/AutocompleteDropdown.tsx +65 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatAttachButton.tsx +56 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatDropZone.tsx +80 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageBubble.tsx +155 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageContent.tsx +182 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageInput.tsx +233 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatSessionSidebar.tsx +218 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatStopButton.tsx +32 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatTodoSidebar.tsx +113 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatView.tsx +842 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/CommitMessageModal.tsx +82 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/DiagramOverlay.tsx +160 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +5 -5
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +9 -10
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +5 -5
- package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +112 -41
- package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +2 -2
- package/templates/assistkick-product-system/packages/frontend/src/components/HighlightedText.tsx +87 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ImageLightbox.tsx +192 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +2 -2
- package/templates/assistkick-product-system/packages/frontend/src/components/MentionPill.tsx +33 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/MermaidBlock.tsx +148 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/PermissionDialog.tsx +91 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/PermissionModeSelector.tsx +229 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +249 -83
- package/templates/assistkick-product-system/packages/frontend/src/components/QueuedMessageBubble.tsx +38 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +212 -117
- package/templates/assistkick-product-system/packages/frontend/src/components/SystemPromptAccordion.tsx +48 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/TaskIcon.tsx +11 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +25 -9
- package/templates/assistkick-product-system/packages/frontend/src/components/ToolDiffView.tsx +114 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ToolResultCard.tsx +87 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ToolUseCard.tsx +149 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +25 -8
- package/templates/assistkick-product-system/packages/frontend/src/components/UnifiedGitWidget.tsx +722 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +2 -1
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/ProgrammableNode.tsx +178 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +3 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +103 -9
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +26 -2
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +42 -1
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useDocumentTitle.ts +11 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +1 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/use_chat_stream.ts +826 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/use_file_tree_cache.ts +69 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/use_mention_autocomplete.ts +284 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/attachment_manager.test.ts +183 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/attachment_manager.ts +150 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/chat_message_helpers.test.ts +305 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/chat_message_helpers.ts +113 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/context_usage_helpers.test.ts +157 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/context_usage_helpers.ts +95 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/mermaid_helpers.test.ts +65 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/mermaid_helpers.ts +110 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/message_queue.ts +66 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/tool_use_summary.test.ts +124 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/tool_use_summary.ts +112 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/ChatRoute.tsx +8 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +0 -4
- package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/accept_invitation.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/forgot_password.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/login.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/register.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/reset_password.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useAttachmentStore.ts +66 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useChatSessionStore.ts +107 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useMessageQueueStore.ts +110 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/usePreviewStore.ts +78 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +7 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +6 -1
- package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +30 -357
- package/templates/assistkick-product-system/packages/frontend/src/utils/parse_node_markdown.test.ts +115 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/parse_node_markdown.ts +91 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/preview_utils.test.ts +30 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/preview_utils.ts +3 -0
- package/templates/assistkick-product-system/packages/shared/db/migrate.ts +82 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0000_outgoing_ultron.sql +277 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0015_magenta_jazinda.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0016_giant_xorn.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0017_sloppy_mentor.sql +6 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0018_vengeful_kabuki.sql +9 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0019_careful_sentinels.sql +8 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0020_clever_spot.sql +27 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0021_graceful_hex.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0022_short_kingpin.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0023_ambiguous_sharon_carter.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0024_fat_unus.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0000_snapshot.json +972 -22
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0015_snapshot.json +1552 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0016_snapshot.json +1560 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0017_snapshot.json +1598 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0018_snapshot.json +1657 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0019_snapshot.json +1709 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0020_snapshot.json +1733 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0021_snapshot.json +1740 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0022_snapshot.json +1755 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0023_snapshot.json +1762 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0024_snapshot.json +1769 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +2 -100
- package/templates/assistkick-product-system/packages/shared/db/schema.ts +40 -1
- package/templates/assistkick-product-system/packages/shared/lib/claude-service.test.ts +236 -0
- package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +46 -5
- package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +65 -39
- package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.test.ts +173 -0
- package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.ts +213 -0
- package/templates/assistkick-product-system/packages/shared/lib/validator.test.ts +70 -0
- package/templates/assistkick-product-system/packages/shared/lib/validator.ts +17 -1
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +803 -27
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +502 -68
- package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +4 -4
- package/templates/assistkick-product-system/packages/shared/package.json +2 -1
- package/templates/assistkick-product-system/packages/shared/test_fixtures/hanging_stream.mjs +46 -0
- package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +44 -0
- package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +7 -0
- package/templates/assistkick-product-system/packages/shared/tools/remove_node.ts +2 -1
- package/templates/assistkick-product-system/packages/shared/tools/resolve_question.ts +2 -1
- package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -1
- package/templates/assistkick-product-system/tests/message_queue.test.ts +178 -0
- package/templates/assistkick-product-system/tests/message_queue_per_session.test.ts +143 -0
- package/templates/skills/assistkick-bootstrap/SKILL.md +26 -26
- package/templates/skills/assistkick-code-reviewer/SKILL.md +45 -46
- package/templates/skills/assistkick-db-explorer/SKILL.md +13 -13
- package/templates/skills/assistkick-debugger/SKILL.md +23 -23
- package/templates/skills/assistkick-developer/SKILL.md +59 -63
- package/templates/skills/assistkick-interview/SKILL.md +26 -26
- package/templates/skills/assistkick-video-composition-agent/SKILL.md +231 -0
- package/templates/skills/assistkick-video-script-writer/SKILL.md +136 -0
package/templates/assistkick-product-system/packages/frontend/src/components/UnifiedGitWidget.tsx
ADDED
|
@@ -0,0 +1,722 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* UnifiedGitWidget — replaces BranchSwitcher with a full JetBrains-style git widget.
|
|
3
|
+
* Shows current branch + pull/push buttons with ahead/behind counts.
|
|
4
|
+
* Dropdown includes: branches, remotes, remote connection, and auth settings.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useState, useCallback, useEffect, useRef } from 'react';
|
|
8
|
+
import {
|
|
9
|
+
ChevronDown, GitBranch, Plus, Globe, ArrowDown, ArrowUp,
|
|
10
|
+
RefreshCw, Link, Key, Settings, Unlink, Trash2,
|
|
11
|
+
} from 'lucide-react';
|
|
12
|
+
import { apiClient } from '../api/client';
|
|
13
|
+
import { useProjectStore, type Project } from '../stores/useProjectStore';
|
|
14
|
+
|
|
15
|
+
type WidgetTab = 'branches' | 'remotes' | 'auth';
|
|
16
|
+
|
|
17
|
+
interface UnifiedGitWidgetProps {
|
|
18
|
+
projectId: string;
|
|
19
|
+
onBranchSwitch: () => void;
|
|
20
|
+
onDirtyCheckout: (branch: string) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const UnifiedGitWidget = ({ projectId, onBranchSwitch, onDirtyCheckout }: UnifiedGitWidgetProps) => {
|
|
24
|
+
const [current, setCurrent] = useState('');
|
|
25
|
+
const [localBranches, setLocalBranches] = useState<string[]>([]);
|
|
26
|
+
const [remoteBranches, setRemoteBranches] = useState<string[]>([]);
|
|
27
|
+
const [open, setOpen] = useState(false);
|
|
28
|
+
const [tab, setTab] = useState<WidgetTab>('branches');
|
|
29
|
+
const [creating, setCreating] = useState(false);
|
|
30
|
+
const [newBranchName, setNewBranchName] = useState('');
|
|
31
|
+
const [loading, setLoading] = useState(false);
|
|
32
|
+
const [error, setError] = useState<string | null>(null);
|
|
33
|
+
const [success, setSuccess] = useState<string | null>(null);
|
|
34
|
+
const [ahead, setAhead] = useState(0);
|
|
35
|
+
const [behind, setBehind] = useState(0);
|
|
36
|
+
const [hasRemote, setHasRemote] = useState(false);
|
|
37
|
+
const [pulling, setPulling] = useState(false);
|
|
38
|
+
const [pushing, setPushing] = useState(false);
|
|
39
|
+
const [fetching, setFetching] = useState(false);
|
|
40
|
+
|
|
41
|
+
// Remote connection state
|
|
42
|
+
const [remoteUrl, setRemoteUrl] = useState('');
|
|
43
|
+
const [remotes, setRemotes] = useState<Array<{ name: string; url: string }>>([]);
|
|
44
|
+
|
|
45
|
+
// Auth state
|
|
46
|
+
const [gitStatus, setGitStatus] = useState<any>(null);
|
|
47
|
+
const [sshPublicKey, setSshPublicKey] = useState<string | null>(null);
|
|
48
|
+
const [copied, setCopied] = useState(false);
|
|
49
|
+
const [sshCloneUrl, setSshCloneUrl] = useState('');
|
|
50
|
+
|
|
51
|
+
// Pull confirmation dialog
|
|
52
|
+
const [showPullConfirm, setShowPullConfirm] = useState(false);
|
|
53
|
+
|
|
54
|
+
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
55
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
56
|
+
const refetchProjects = useProjectStore((s) => s.refetchProjects);
|
|
57
|
+
|
|
58
|
+
const loadBranches = useCallback(async () => {
|
|
59
|
+
try {
|
|
60
|
+
const data = await apiClient.fetchGitBranches(projectId);
|
|
61
|
+
setCurrent(data.current);
|
|
62
|
+
setLocalBranches(data.local);
|
|
63
|
+
setRemoteBranches(data.remote);
|
|
64
|
+
} catch {
|
|
65
|
+
setCurrent('');
|
|
66
|
+
}
|
|
67
|
+
}, [projectId]);
|
|
68
|
+
|
|
69
|
+
const loadAheadBehind = useCallback(async () => {
|
|
70
|
+
try {
|
|
71
|
+
const data = await apiClient.getAheadBehind(projectId);
|
|
72
|
+
setAhead(data.ahead);
|
|
73
|
+
setBehind(data.behind);
|
|
74
|
+
} catch {
|
|
75
|
+
setAhead(0);
|
|
76
|
+
setBehind(0);
|
|
77
|
+
}
|
|
78
|
+
}, [projectId]);
|
|
79
|
+
|
|
80
|
+
const loadRemotes = useCallback(async () => {
|
|
81
|
+
try {
|
|
82
|
+
const data = await apiClient.listGitRemotes(projectId);
|
|
83
|
+
setRemotes(data.remotes || []);
|
|
84
|
+
setHasRemote((data.remotes || []).length > 0);
|
|
85
|
+
} catch {
|
|
86
|
+
setRemotes([]);
|
|
87
|
+
setHasRemote(false);
|
|
88
|
+
}
|
|
89
|
+
}, [projectId]);
|
|
90
|
+
|
|
91
|
+
const loadStatus = useCallback(async () => {
|
|
92
|
+
try {
|
|
93
|
+
const data = await apiClient.getGitStatus(projectId);
|
|
94
|
+
setGitStatus(data);
|
|
95
|
+
if (data.sshPublicKey) setSshPublicKey(data.sshPublicKey);
|
|
96
|
+
} catch {
|
|
97
|
+
// ignore
|
|
98
|
+
}
|
|
99
|
+
}, [projectId]);
|
|
100
|
+
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
loadBranches();
|
|
103
|
+
loadRemotes();
|
|
104
|
+
loadAheadBehind();
|
|
105
|
+
}, [loadBranches, loadRemotes, loadAheadBehind]);
|
|
106
|
+
|
|
107
|
+
// Close dropdown on outside click
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (!open) return;
|
|
110
|
+
const handleClick = (e: MouseEvent) => {
|
|
111
|
+
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
|
|
112
|
+
setOpen(false);
|
|
113
|
+
setCreating(false);
|
|
114
|
+
setError(null);
|
|
115
|
+
setSuccess(null);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
document.addEventListener('mousedown', handleClick);
|
|
119
|
+
return () => document.removeEventListener('mousedown', handleClick);
|
|
120
|
+
}, [open]);
|
|
121
|
+
|
|
122
|
+
// Focus new branch input when entering create mode
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
if (creating) {
|
|
125
|
+
setTimeout(() => inputRef.current?.focus(), 50);
|
|
126
|
+
}
|
|
127
|
+
}, [creating]);
|
|
128
|
+
|
|
129
|
+
const handleToggle = useCallback(() => {
|
|
130
|
+
setOpen(prev => !prev);
|
|
131
|
+
setCreating(false);
|
|
132
|
+
setError(null);
|
|
133
|
+
setSuccess(null);
|
|
134
|
+
}, []);
|
|
135
|
+
|
|
136
|
+
const handleSelectBranch = useCallback(async (branch: string) => {
|
|
137
|
+
if (branch === current) {
|
|
138
|
+
setOpen(false);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
setLoading(true);
|
|
142
|
+
setError(null);
|
|
143
|
+
try {
|
|
144
|
+
await apiClient.checkoutBranch(projectId, branch);
|
|
145
|
+
await loadBranches();
|
|
146
|
+
await loadAheadBehind();
|
|
147
|
+
setOpen(false);
|
|
148
|
+
onBranchSwitch();
|
|
149
|
+
} catch (err: any) {
|
|
150
|
+
if (err.dirty) {
|
|
151
|
+
setOpen(false);
|
|
152
|
+
onDirtyCheckout(branch);
|
|
153
|
+
} else {
|
|
154
|
+
setError(err.message);
|
|
155
|
+
}
|
|
156
|
+
} finally {
|
|
157
|
+
setLoading(false);
|
|
158
|
+
}
|
|
159
|
+
}, [projectId, current, loadBranches, loadAheadBehind, onBranchSwitch, onDirtyCheckout]);
|
|
160
|
+
|
|
161
|
+
const handleCreateBranch = useCallback(async (e: React.FormEvent) => {
|
|
162
|
+
e.preventDefault();
|
|
163
|
+
const name = newBranchName.trim();
|
|
164
|
+
if (!name) return;
|
|
165
|
+
setLoading(true);
|
|
166
|
+
setError(null);
|
|
167
|
+
try {
|
|
168
|
+
await apiClient.createGitBranch(projectId, name);
|
|
169
|
+
await loadBranches();
|
|
170
|
+
setNewBranchName('');
|
|
171
|
+
setCreating(false);
|
|
172
|
+
setOpen(false);
|
|
173
|
+
onBranchSwitch();
|
|
174
|
+
} catch (err: any) {
|
|
175
|
+
setError(err.message);
|
|
176
|
+
} finally {
|
|
177
|
+
setLoading(false);
|
|
178
|
+
}
|
|
179
|
+
}, [projectId, newBranchName, loadBranches, onBranchSwitch]);
|
|
180
|
+
|
|
181
|
+
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
182
|
+
if (e.key === 'Escape') {
|
|
183
|
+
if (creating) {
|
|
184
|
+
setCreating(false);
|
|
185
|
+
setNewBranchName('');
|
|
186
|
+
} else {
|
|
187
|
+
setOpen(false);
|
|
188
|
+
}
|
|
189
|
+
setError(null);
|
|
190
|
+
setSuccess(null);
|
|
191
|
+
}
|
|
192
|
+
}, [creating]);
|
|
193
|
+
|
|
194
|
+
const handleDeleteBranch = useCallback(async (branch: string) => {
|
|
195
|
+
if (branch === current) return;
|
|
196
|
+
setLoading(true);
|
|
197
|
+
setError(null);
|
|
198
|
+
try {
|
|
199
|
+
await apiClient.deleteGitBranch(projectId, branch);
|
|
200
|
+
await loadBranches();
|
|
201
|
+
setSuccess(`Deleted branch "${branch}"`);
|
|
202
|
+
} catch (err: any) {
|
|
203
|
+
setError(err.message);
|
|
204
|
+
} finally {
|
|
205
|
+
setLoading(false);
|
|
206
|
+
}
|
|
207
|
+
}, [projectId, current, loadBranches]);
|
|
208
|
+
|
|
209
|
+
// Pull/Push/Fetch handlers
|
|
210
|
+
const handleFetch = useCallback(async () => {
|
|
211
|
+
setFetching(true);
|
|
212
|
+
setError(null);
|
|
213
|
+
try {
|
|
214
|
+
const data = await apiClient.gitFetch(projectId);
|
|
215
|
+
setAhead(data.ahead);
|
|
216
|
+
setBehind(data.behind);
|
|
217
|
+
await loadBranches();
|
|
218
|
+
} catch (err: any) {
|
|
219
|
+
setError(err.message);
|
|
220
|
+
} finally {
|
|
221
|
+
setFetching(false);
|
|
222
|
+
}
|
|
223
|
+
}, [projectId, loadBranches]);
|
|
224
|
+
|
|
225
|
+
const handlePull = useCallback(async () => {
|
|
226
|
+
setError(null);
|
|
227
|
+
try {
|
|
228
|
+
// Check if local workspace has files beyond the initial empty commit
|
|
229
|
+
const { onlyInitialCommit } = await apiClient.getInitialCommitStatus(projectId);
|
|
230
|
+
if (!onlyInitialCommit) {
|
|
231
|
+
// Show confirmation dialog — pulling may overwrite local files
|
|
232
|
+
setShowPullConfirm(true);
|
|
233
|
+
return;
|
|
234
|
+
}
|
|
235
|
+
// Safe to pull directly (only initial commit exists)
|
|
236
|
+
setPulling(true);
|
|
237
|
+
const data = await apiClient.gitPull(projectId);
|
|
238
|
+
setAhead(data.ahead);
|
|
239
|
+
setBehind(data.behind);
|
|
240
|
+
await loadBranches();
|
|
241
|
+
onBranchSwitch();
|
|
242
|
+
} catch (err: any) {
|
|
243
|
+
setError(err.message);
|
|
244
|
+
} finally {
|
|
245
|
+
setPulling(false);
|
|
246
|
+
}
|
|
247
|
+
}, [projectId, loadBranches, onBranchSwitch]);
|
|
248
|
+
|
|
249
|
+
const handlePullConfirmed = useCallback(async () => {
|
|
250
|
+
setShowPullConfirm(false);
|
|
251
|
+
setPulling(true);
|
|
252
|
+
setError(null);
|
|
253
|
+
try {
|
|
254
|
+
const data = await apiClient.gitPull(projectId, true);
|
|
255
|
+
setAhead(data.ahead);
|
|
256
|
+
setBehind(data.behind);
|
|
257
|
+
await loadBranches();
|
|
258
|
+
onBranchSwitch();
|
|
259
|
+
} catch (err: any) {
|
|
260
|
+
setError(err.message);
|
|
261
|
+
} finally {
|
|
262
|
+
setPulling(false);
|
|
263
|
+
}
|
|
264
|
+
}, [projectId, loadBranches, onBranchSwitch]);
|
|
265
|
+
|
|
266
|
+
const handlePush = useCallback(async () => {
|
|
267
|
+
setPushing(true);
|
|
268
|
+
setError(null);
|
|
269
|
+
try {
|
|
270
|
+
const data = await apiClient.gitPush(projectId);
|
|
271
|
+
setAhead(data.ahead);
|
|
272
|
+
setBehind(data.behind);
|
|
273
|
+
} catch (err: any) {
|
|
274
|
+
setError(err.message);
|
|
275
|
+
} finally {
|
|
276
|
+
setPushing(false);
|
|
277
|
+
}
|
|
278
|
+
}, [projectId]);
|
|
279
|
+
|
|
280
|
+
// Remote connection handler
|
|
281
|
+
const handleAddRemote = useCallback(async () => {
|
|
282
|
+
const url = remoteUrl.trim();
|
|
283
|
+
if (!url) return;
|
|
284
|
+
setLoading(true);
|
|
285
|
+
setError(null);
|
|
286
|
+
try {
|
|
287
|
+
await apiClient.addGitRemote(projectId, url);
|
|
288
|
+
setRemoteUrl('');
|
|
289
|
+
await loadRemotes();
|
|
290
|
+
await refetchProjects();
|
|
291
|
+
setSuccess('Remote added successfully');
|
|
292
|
+
|
|
293
|
+
// Fetch after adding remote
|
|
294
|
+
const fetchData = await apiClient.gitFetch(projectId);
|
|
295
|
+
setAhead(fetchData.ahead);
|
|
296
|
+
setBehind(fetchData.behind);
|
|
297
|
+
await loadBranches();
|
|
298
|
+
} catch (err: any) {
|
|
299
|
+
setError(err.message);
|
|
300
|
+
} finally {
|
|
301
|
+
setLoading(false);
|
|
302
|
+
}
|
|
303
|
+
}, [projectId, remoteUrl, loadRemotes, loadBranches, refetchProjects]);
|
|
304
|
+
|
|
305
|
+
// SSH key management
|
|
306
|
+
const handleGenerateSshKey = useCallback(async () => {
|
|
307
|
+
setLoading(true);
|
|
308
|
+
setError(null);
|
|
309
|
+
setCopied(false);
|
|
310
|
+
try {
|
|
311
|
+
const data = await apiClient.generateSshKey(projectId);
|
|
312
|
+
setSshPublicKey(data.publicKey);
|
|
313
|
+
setSuccess(sshPublicKey ? 'SSH keypair regenerated' : 'SSH keypair generated');
|
|
314
|
+
await refetchProjects();
|
|
315
|
+
} catch (err: any) {
|
|
316
|
+
setError(err.message);
|
|
317
|
+
} finally {
|
|
318
|
+
setLoading(false);
|
|
319
|
+
}
|
|
320
|
+
}, [projectId, sshPublicKey, refetchProjects]);
|
|
321
|
+
|
|
322
|
+
const handleCopyPublicKey = useCallback(async () => {
|
|
323
|
+
if (!sshPublicKey) return;
|
|
324
|
+
try {
|
|
325
|
+
await navigator.clipboard.writeText(sshPublicKey);
|
|
326
|
+
setCopied(true);
|
|
327
|
+
setTimeout(() => setCopied(false), 2000);
|
|
328
|
+
} catch {
|
|
329
|
+
setError('Failed to copy to clipboard');
|
|
330
|
+
}
|
|
331
|
+
}, [sshPublicKey]);
|
|
332
|
+
|
|
333
|
+
const handleConnectSsh = useCallback(async () => {
|
|
334
|
+
const url = sshCloneUrl.trim();
|
|
335
|
+
if (!url) return;
|
|
336
|
+
setLoading(true);
|
|
337
|
+
setError(null);
|
|
338
|
+
try {
|
|
339
|
+
await apiClient.connectSshRepo(projectId, url);
|
|
340
|
+
setSuccess('Repository connected via SSH');
|
|
341
|
+
setSshCloneUrl('');
|
|
342
|
+
await refetchProjects();
|
|
343
|
+
await loadRemotes();
|
|
344
|
+
await loadStatus();
|
|
345
|
+
await loadBranches();
|
|
346
|
+
await loadAheadBehind();
|
|
347
|
+
} catch (err: any) {
|
|
348
|
+
setError(err.message);
|
|
349
|
+
} finally {
|
|
350
|
+
setLoading(false);
|
|
351
|
+
}
|
|
352
|
+
}, [projectId, sshCloneUrl, refetchProjects, loadRemotes, loadStatus, loadBranches, loadAheadBehind]);
|
|
353
|
+
|
|
354
|
+
const handleDisconnect = useCallback(async () => {
|
|
355
|
+
setLoading(true);
|
|
356
|
+
setError(null);
|
|
357
|
+
try {
|
|
358
|
+
await apiClient.disconnectGitRepo(projectId);
|
|
359
|
+
setSuccess('Repository disconnected');
|
|
360
|
+
setSshPublicKey(null);
|
|
361
|
+
setHasRemote(false);
|
|
362
|
+
setRemotes([]);
|
|
363
|
+
setAhead(0);
|
|
364
|
+
setBehind(0);
|
|
365
|
+
await refetchProjects();
|
|
366
|
+
await loadStatus();
|
|
367
|
+
} catch (err: any) {
|
|
368
|
+
setError(err.message);
|
|
369
|
+
} finally {
|
|
370
|
+
setLoading(false);
|
|
371
|
+
}
|
|
372
|
+
}, [projectId, refetchProjects, loadStatus]);
|
|
373
|
+
|
|
374
|
+
// Don't render if no git workspace
|
|
375
|
+
if (!current) return null;
|
|
376
|
+
|
|
377
|
+
// Filter out remote branches that already have a local equivalent
|
|
378
|
+
const filteredRemote = remoteBranches.filter(rb => {
|
|
379
|
+
const localName = rb.replace(/^[^/]+\//, '');
|
|
380
|
+
return !localBranches.includes(localName);
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
const tabCls = (active: boolean) =>
|
|
384
|
+
`bg-none border-none cursor-pointer font-mono text-[10px] px-2 py-1.5 border-b-2 ${
|
|
385
|
+
active ? 'text-accent border-b-accent' : 'text-content-secondary border-b-transparent hover:text-content'
|
|
386
|
+
}`;
|
|
387
|
+
|
|
388
|
+
return (
|
|
389
|
+
<div className="relative px-2 py-1.5 border-b border-edge" ref={dropdownRef} onKeyDown={handleKeyDown}>
|
|
390
|
+
{/* Main bar: branch button + action buttons */}
|
|
391
|
+
<div className="flex items-center gap-1">
|
|
392
|
+
{/* Branch dropdown trigger */}
|
|
393
|
+
<button
|
|
394
|
+
className="flex items-center gap-1.5 flex-1 min-w-0 py-1 px-2 border border-edge rounded bg-surface text-content font-mono text-xs cursor-pointer hover:border-content-muted"
|
|
395
|
+
onClick={handleToggle}
|
|
396
|
+
disabled={loading}
|
|
397
|
+
>
|
|
398
|
+
<GitBranch size={13} className="shrink-0 text-accent" />
|
|
399
|
+
<span className="flex-1 text-left overflow-hidden text-ellipsis whitespace-nowrap">{current}</span>
|
|
400
|
+
<ChevronDown size={12} className={`shrink-0 transition-transform duration-150 ${open ? 'rotate-180' : ''}`} />
|
|
401
|
+
</button>
|
|
402
|
+
|
|
403
|
+
{/* Pull/Push/Fetch action buttons — only when remote is connected */}
|
|
404
|
+
{hasRemote && (
|
|
405
|
+
<div className="flex items-center gap-0.5">
|
|
406
|
+
<button
|
|
407
|
+
className="flex items-center gap-0.5 p-1 border-none bg-transparent text-content-secondary cursor-pointer rounded hover:bg-tab-hover hover:text-content disabled:opacity-50 disabled:cursor-not-allowed"
|
|
408
|
+
onClick={handleFetch}
|
|
409
|
+
disabled={fetching}
|
|
410
|
+
title="Fetch"
|
|
411
|
+
>
|
|
412
|
+
<RefreshCw size={12} className={fetching ? 'animate-spin' : ''} />
|
|
413
|
+
</button>
|
|
414
|
+
<button
|
|
415
|
+
className="flex items-center gap-0.5 p-1 border-none bg-transparent text-content-secondary cursor-pointer rounded hover:bg-tab-hover hover:text-content disabled:opacity-50 disabled:cursor-not-allowed"
|
|
416
|
+
onClick={handlePull}
|
|
417
|
+
disabled={pulling}
|
|
418
|
+
title={`Pull${behind > 0 ? ` (${behind} behind)` : ''}`}
|
|
419
|
+
>
|
|
420
|
+
<ArrowDown size={12} />
|
|
421
|
+
{behind > 0 && <span className="text-[10px] font-mono text-accent">{behind}</span>}
|
|
422
|
+
</button>
|
|
423
|
+
<button
|
|
424
|
+
className="flex items-center gap-0.5 p-1 border-none bg-transparent text-content-secondary cursor-pointer rounded hover:bg-tab-hover hover:text-content disabled:opacity-50 disabled:cursor-not-allowed"
|
|
425
|
+
onClick={handlePush}
|
|
426
|
+
disabled={pushing}
|
|
427
|
+
title={`Push${ahead > 0 ? ` (${ahead} ahead)` : ''}`}
|
|
428
|
+
>
|
|
429
|
+
<ArrowUp size={12} />
|
|
430
|
+
{ahead > 0 && <span className="text-[10px] font-mono text-accent">{ahead}</span>}
|
|
431
|
+
</button>
|
|
432
|
+
</div>
|
|
433
|
+
)}
|
|
434
|
+
</div>
|
|
435
|
+
|
|
436
|
+
{/* Dropdown panel */}
|
|
437
|
+
{open && (
|
|
438
|
+
<div className="absolute top-full left-2 right-2 z-100 max-h-[400px] overflow-y-auto bg-surface-alt border border-edge rounded shadow-lg">
|
|
439
|
+
{/* Tab bar */}
|
|
440
|
+
<div className="flex border-b border-edge px-1">
|
|
441
|
+
<button className={tabCls(tab === 'branches')} onClick={() => { setTab('branches'); setError(null); setSuccess(null); }}>
|
|
442
|
+
Branches
|
|
443
|
+
</button>
|
|
444
|
+
<button className={tabCls(tab === 'remotes')} onClick={() => { setTab('remotes'); setError(null); setSuccess(null); loadRemotes(); }}>
|
|
445
|
+
Remotes
|
|
446
|
+
</button>
|
|
447
|
+
<button className={tabCls(tab === 'auth')} onClick={() => { setTab('auth'); setError(null); setSuccess(null); loadStatus(); }}>
|
|
448
|
+
Auth
|
|
449
|
+
</button>
|
|
450
|
+
</div>
|
|
451
|
+
|
|
452
|
+
{error && (
|
|
453
|
+
<div className="px-2.5 py-1.5 text-[11px] text-error bg-[rgba(255,107,107,0.08)] border-b border-edge">{error}</div>
|
|
454
|
+
)}
|
|
455
|
+
{success && (
|
|
456
|
+
<div className="px-2.5 py-1.5 text-[11px] text-[#69db7c] bg-[rgba(105,219,124,0.08)] border-b border-edge">{success}</div>
|
|
457
|
+
)}
|
|
458
|
+
|
|
459
|
+
{/* Branches tab */}
|
|
460
|
+
{tab === 'branches' && (
|
|
461
|
+
<div className="py-1">
|
|
462
|
+
{/* Local branches */}
|
|
463
|
+
{localBranches.length > 0 && (
|
|
464
|
+
<div>
|
|
465
|
+
<div className="px-2.5 pt-1.5 pb-0.5 text-[10px] uppercase tracking-wide text-content-muted">Local</div>
|
|
466
|
+
{localBranches.map(branch => (
|
|
467
|
+
<div key={branch} className="group flex items-center hover:bg-tab-hover">
|
|
468
|
+
<button
|
|
469
|
+
className={`flex items-center gap-1.5 flex-1 min-w-0 py-1 px-2.5 border-none bg-transparent text-content-secondary font-mono text-xs cursor-pointer text-left hover:text-content ${branch === current ? 'text-accent font-semibold' : ''}`}
|
|
470
|
+
onClick={() => handleSelectBranch(branch)}
|
|
471
|
+
disabled={loading}
|
|
472
|
+
>
|
|
473
|
+
<GitBranch size={12} className="shrink-0" />
|
|
474
|
+
<span className="truncate">{branch}</span>
|
|
475
|
+
</button>
|
|
476
|
+
{branch !== current && (
|
|
477
|
+
<button
|
|
478
|
+
className="p-1 mr-1 border-none bg-transparent text-content-muted cursor-pointer rounded opacity-0 group-hover:opacity-100 transition-opacity hover:text-error"
|
|
479
|
+
onClick={(e) => { e.stopPropagation(); handleDeleteBranch(branch); }}
|
|
480
|
+
disabled={loading}
|
|
481
|
+
title={`Delete branch "${branch}"`}
|
|
482
|
+
>
|
|
483
|
+
<Trash2 size={11} />
|
|
484
|
+
</button>
|
|
485
|
+
)}
|
|
486
|
+
</div>
|
|
487
|
+
))}
|
|
488
|
+
</div>
|
|
489
|
+
)}
|
|
490
|
+
|
|
491
|
+
{/* Remote branches */}
|
|
492
|
+
{filteredRemote.length > 0 && (
|
|
493
|
+
<div>
|
|
494
|
+
<div className="px-2.5 pt-1.5 pb-0.5 text-[10px] uppercase tracking-wide text-content-muted">Remote</div>
|
|
495
|
+
{filteredRemote.map(branch => (
|
|
496
|
+
<button
|
|
497
|
+
key={branch}
|
|
498
|
+
className="flex items-center gap-1.5 w-full py-1 px-2.5 border-none bg-transparent text-content-muted font-mono text-xs cursor-pointer text-left hover:bg-tab-hover hover:text-content"
|
|
499
|
+
onClick={() => handleSelectBranch(branch)}
|
|
500
|
+
disabled={loading}
|
|
501
|
+
>
|
|
502
|
+
<Globe size={12} className="shrink-0" />
|
|
503
|
+
<span>{branch}</span>
|
|
504
|
+
</button>
|
|
505
|
+
))}
|
|
506
|
+
</div>
|
|
507
|
+
)}
|
|
508
|
+
|
|
509
|
+
{/* Separator */}
|
|
510
|
+
<div className="h-px my-1 bg-edge" />
|
|
511
|
+
|
|
512
|
+
{/* Create new branch */}
|
|
513
|
+
{creating ? (
|
|
514
|
+
<form className="px-2 py-1" onSubmit={handleCreateBranch}>
|
|
515
|
+
<input
|
|
516
|
+
ref={inputRef}
|
|
517
|
+
className="w-full py-1 px-1.5 border border-accent rounded-sm bg-surface text-content font-mono text-xs outline-none"
|
|
518
|
+
value={newBranchName}
|
|
519
|
+
onChange={e => setNewBranchName(e.target.value)}
|
|
520
|
+
placeholder="Branch name..."
|
|
521
|
+
disabled={loading}
|
|
522
|
+
/>
|
|
523
|
+
</form>
|
|
524
|
+
) : (
|
|
525
|
+
<button
|
|
526
|
+
className="flex items-center gap-1.5 w-full py-1 px-2.5 border-none bg-transparent text-accent font-mono text-xs cursor-pointer text-left hover:bg-tab-hover"
|
|
527
|
+
onClick={() => setCreating(true)}
|
|
528
|
+
disabled={loading}
|
|
529
|
+
>
|
|
530
|
+
<Plus size={12} className="shrink-0" />
|
|
531
|
+
<span>New branch...</span>
|
|
532
|
+
</button>
|
|
533
|
+
)}
|
|
534
|
+
</div>
|
|
535
|
+
)}
|
|
536
|
+
|
|
537
|
+
{/* Remotes tab */}
|
|
538
|
+
{tab === 'remotes' && (
|
|
539
|
+
<div className="px-2.5 py-2">
|
|
540
|
+
{remotes.length > 0 ? (
|
|
541
|
+
<div className="mb-2">
|
|
542
|
+
{remotes.map(r => (
|
|
543
|
+
<div key={r.name} className="flex items-center gap-1.5 py-1 border-b border-edge last:border-0">
|
|
544
|
+
<Globe size={12} className="shrink-0 text-accent" />
|
|
545
|
+
<span className="text-xs font-mono text-content font-semibold">{r.name}</span>
|
|
546
|
+
<span className="text-[11px] font-mono text-content-secondary truncate flex-1">{r.url}</span>
|
|
547
|
+
</div>
|
|
548
|
+
))}
|
|
549
|
+
</div>
|
|
550
|
+
) : (
|
|
551
|
+
<p className="text-content-muted text-xs mb-2">No remotes configured.</p>
|
|
552
|
+
)}
|
|
553
|
+
|
|
554
|
+
<div className="border-t border-edge pt-2">
|
|
555
|
+
<label className="block text-content-secondary text-[10px] uppercase tracking-wider mb-1">
|
|
556
|
+
{remotes.length > 0 ? 'Add another remote' : 'Add remote origin'}
|
|
557
|
+
</label>
|
|
558
|
+
<div className="flex gap-1">
|
|
559
|
+
<input
|
|
560
|
+
className="flex-1 py-1 px-1.5 border border-edge rounded-sm bg-surface text-content font-mono text-xs outline-none focus:border-accent"
|
|
561
|
+
type="text"
|
|
562
|
+
placeholder="https://github.com/org/repo.git"
|
|
563
|
+
value={remoteUrl}
|
|
564
|
+
onChange={e => setRemoteUrl(e.target.value)}
|
|
565
|
+
onKeyDown={e => { if (e.key === 'Enter') handleAddRemote(); }}
|
|
566
|
+
disabled={loading}
|
|
567
|
+
/>
|
|
568
|
+
<button
|
|
569
|
+
className="px-2 py-1 bg-accent text-white border-none rounded-sm text-[11px] cursor-pointer disabled:opacity-50 disabled:cursor-not-allowed hover:opacity-90"
|
|
570
|
+
onClick={handleAddRemote}
|
|
571
|
+
disabled={loading || !remoteUrl.trim()}
|
|
572
|
+
>
|
|
573
|
+
<Link size={12} />
|
|
574
|
+
</button>
|
|
575
|
+
</div>
|
|
576
|
+
</div>
|
|
577
|
+
|
|
578
|
+
{hasRemote && (
|
|
579
|
+
<button
|
|
580
|
+
className="flex items-center gap-1 mt-2 px-0 py-1 bg-transparent border-none text-error text-[11px] cursor-pointer hover:underline"
|
|
581
|
+
onClick={handleDisconnect}
|
|
582
|
+
disabled={loading}
|
|
583
|
+
>
|
|
584
|
+
<Unlink size={11} />
|
|
585
|
+
Disconnect
|
|
586
|
+
</button>
|
|
587
|
+
)}
|
|
588
|
+
</div>
|
|
589
|
+
)}
|
|
590
|
+
|
|
591
|
+
{/* Auth tab */}
|
|
592
|
+
{tab === 'auth' && (
|
|
593
|
+
<div className="px-2.5 py-2">
|
|
594
|
+
{/* Current auth method */}
|
|
595
|
+
<div className="flex justify-between py-1 mb-2 border-b border-edge">
|
|
596
|
+
<span className="text-content-secondary text-[11px]">Auth Method:</span>
|
|
597
|
+
<span className="text-[11px] text-content">
|
|
598
|
+
{gitStatus?.gitAuthMethod === 'ssh_key' ? 'SSH Key' : gitStatus?.gitAuthMethod === 'github_app' ? 'GitHub App' : 'None'}
|
|
599
|
+
</span>
|
|
600
|
+
</div>
|
|
601
|
+
|
|
602
|
+
{/* SSH Key section */}
|
|
603
|
+
<div className="mb-2">
|
|
604
|
+
<div className="flex items-center gap-1 mb-1">
|
|
605
|
+
<Key size={11} className="text-content-secondary" />
|
|
606
|
+
<span className="text-[10px] uppercase tracking-wider text-content-secondary font-medium">SSH Deploy Key</span>
|
|
607
|
+
</div>
|
|
608
|
+
|
|
609
|
+
{!sshPublicKey ? (
|
|
610
|
+
<button
|
|
611
|
+
className="px-2 py-1 bg-accent text-white border-none rounded-sm text-[11px] cursor-pointer disabled:opacity-50 hover:opacity-90"
|
|
612
|
+
onClick={handleGenerateSshKey}
|
|
613
|
+
disabled={loading}
|
|
614
|
+
>
|
|
615
|
+
{loading ? 'Generating...' : 'Generate SSH Key'}
|
|
616
|
+
</button>
|
|
617
|
+
) : (
|
|
618
|
+
<>
|
|
619
|
+
<div className="relative mb-1">
|
|
620
|
+
<textarea
|
|
621
|
+
className="w-full py-1 px-1.5 bg-surface border border-edge rounded-sm text-content font-mono text-[10px] outline-none resize-none h-12"
|
|
622
|
+
readOnly
|
|
623
|
+
value={sshPublicKey}
|
|
624
|
+
/>
|
|
625
|
+
<button
|
|
626
|
+
className="absolute top-1 right-1 px-1.5 py-0.5 bg-surface-raised border border-edge rounded text-[9px] text-content-secondary hover:text-content cursor-pointer"
|
|
627
|
+
onClick={handleCopyPublicKey}
|
|
628
|
+
>
|
|
629
|
+
{copied ? 'Copied!' : 'Copy'}
|
|
630
|
+
</button>
|
|
631
|
+
</div>
|
|
632
|
+
|
|
633
|
+
<div className="p-1.5 bg-[rgba(255,200,50,0.08)] border border-[rgba(255,200,50,0.2)] rounded-sm text-[10px] text-content-secondary leading-relaxed mb-1">
|
|
634
|
+
Add as <strong className="text-content">deploy key</strong> in GitHub. Check <strong className="text-content">"Allow write access"</strong>.
|
|
635
|
+
</div>
|
|
636
|
+
|
|
637
|
+
{/* SSH clone URL input */}
|
|
638
|
+
<div className="mt-1.5 pt-1.5 border-t border-edge">
|
|
639
|
+
<label className="block text-content-secondary text-[10px] uppercase tracking-wider mb-0.5">SSH clone URL:</label>
|
|
640
|
+
<div className="flex gap-1">
|
|
641
|
+
<input
|
|
642
|
+
className="flex-1 py-1 px-1.5 border border-edge rounded-sm bg-surface text-content font-mono text-xs outline-none focus:border-accent"
|
|
643
|
+
type="text"
|
|
644
|
+
placeholder="git@github.com:org/repo.git"
|
|
645
|
+
value={sshCloneUrl}
|
|
646
|
+
onChange={e => setSshCloneUrl(e.target.value)}
|
|
647
|
+
onKeyDown={e => { if (e.key === 'Enter') handleConnectSsh(); }}
|
|
648
|
+
/>
|
|
649
|
+
<button
|
|
650
|
+
className="px-2 py-1 bg-accent text-white border-none rounded-sm text-[11px] cursor-pointer disabled:opacity-50 hover:opacity-90"
|
|
651
|
+
onClick={handleConnectSsh}
|
|
652
|
+
disabled={loading || !sshCloneUrl.trim()}
|
|
653
|
+
>
|
|
654
|
+
Connect
|
|
655
|
+
</button>
|
|
656
|
+
</div>
|
|
657
|
+
</div>
|
|
658
|
+
|
|
659
|
+
<button
|
|
660
|
+
className="mt-1.5 px-2 py-1 bg-surface-raised border border-edge rounded-sm text-[10px] text-content-secondary cursor-pointer hover:text-content"
|
|
661
|
+
onClick={handleGenerateSshKey}
|
|
662
|
+
disabled={loading}
|
|
663
|
+
>
|
|
664
|
+
Regenerate Key
|
|
665
|
+
</button>
|
|
666
|
+
</>
|
|
667
|
+
)}
|
|
668
|
+
</div>
|
|
669
|
+
|
|
670
|
+
{/* GitHub App status */}
|
|
671
|
+
<div className="border-t border-edge pt-2">
|
|
672
|
+
<div className="flex items-center gap-1 mb-1">
|
|
673
|
+
<Settings size={11} className="text-content-secondary" />
|
|
674
|
+
<span className="text-[10px] uppercase tracking-wider text-content-secondary font-medium">GitHub App</span>
|
|
675
|
+
</div>
|
|
676
|
+
<div className="flex justify-between py-0.5">
|
|
677
|
+
<span className="text-content-secondary text-[11px]">Status:</span>
|
|
678
|
+
<span className={`text-[11px] ${gitStatus?.githubAppConfigured ? 'text-[#69db7c]' : 'text-content-muted'}`}>
|
|
679
|
+
{gitStatus?.githubAppConfigured ? 'Configured' : 'Not configured'}
|
|
680
|
+
</span>
|
|
681
|
+
</div>
|
|
682
|
+
{gitStatus?.githubRepoFullName && (
|
|
683
|
+
<div className="flex justify-between py-0.5">
|
|
684
|
+
<span className="text-content-secondary text-[11px]">Repo:</span>
|
|
685
|
+
<span className="text-[11px] text-content">{gitStatus.githubRepoFullName}</span>
|
|
686
|
+
</div>
|
|
687
|
+
)}
|
|
688
|
+
</div>
|
|
689
|
+
</div>
|
|
690
|
+
)}
|
|
691
|
+
</div>
|
|
692
|
+
)}
|
|
693
|
+
|
|
694
|
+
{/* Pull confirmation dialog */}
|
|
695
|
+
{showPullConfirm && (
|
|
696
|
+
<div className="fixed inset-0 z-[1000] flex items-center justify-center bg-black/50">
|
|
697
|
+
<div className="bg-surface-alt border border-edge rounded-lg shadow-lg max-w-sm w-full mx-4 p-4">
|
|
698
|
+
<h3 className="text-sm font-semibold text-content mb-2">Pull from remote?</h3>
|
|
699
|
+
<p className="text-xs text-content-secondary mb-4 leading-relaxed">
|
|
700
|
+
This project has local files that may be overwritten by pulling from the remote.
|
|
701
|
+
Are you sure you want to continue?
|
|
702
|
+
</p>
|
|
703
|
+
<div className="flex justify-end gap-2">
|
|
704
|
+
<button
|
|
705
|
+
className="px-3 py-1.5 bg-transparent border border-edge rounded text-xs text-content-secondary cursor-pointer hover:bg-tab-hover hover:text-content"
|
|
706
|
+
onClick={() => setShowPullConfirm(false)}
|
|
707
|
+
>
|
|
708
|
+
Cancel
|
|
709
|
+
</button>
|
|
710
|
+
<button
|
|
711
|
+
className="px-3 py-1.5 bg-error border-none rounded text-xs text-white cursor-pointer hover:opacity-90"
|
|
712
|
+
onClick={handlePullConfirmed}
|
|
713
|
+
>
|
|
714
|
+
Pull & Overwrite
|
|
715
|
+
</button>
|
|
716
|
+
</div>
|
|
717
|
+
</div>
|
|
718
|
+
</div>
|
|
719
|
+
)}
|
|
720
|
+
</div>
|
|
721
|
+
);
|
|
722
|
+
};
|