@assistkick/create 1.10.0 → 1.11.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/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/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 +70 -0
- 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
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PermissionModeSelector — UI control for selecting Chat v2 permission mode.
|
|
3
|
+
* Allows the user to choose between skip, allowed_tools, and prompt modes,
|
|
4
|
+
* and configure the allowed tools list when in allowed_tools mode.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import React, { useState, useCallback, useRef, useEffect } from 'react';
|
|
8
|
+
|
|
9
|
+
export type PermissionMode = 'skip' | 'allowed_tools';
|
|
10
|
+
|
|
11
|
+
/** Known Claude Code built-in tools for autocomplete. */
|
|
12
|
+
const KNOWN_TOOLS = [
|
|
13
|
+
'Bash',
|
|
14
|
+
'Read',
|
|
15
|
+
'Write',
|
|
16
|
+
'Edit',
|
|
17
|
+
'MultiEdit',
|
|
18
|
+
'Glob',
|
|
19
|
+
'Grep',
|
|
20
|
+
'LS',
|
|
21
|
+
'Agent',
|
|
22
|
+
'NotebookEdit',
|
|
23
|
+
'WebFetch',
|
|
24
|
+
'WebSearch',
|
|
25
|
+
'TodoRead',
|
|
26
|
+
'TodoWrite',
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
interface PermissionModeSelectorProps {
|
|
30
|
+
mode: PermissionMode;
|
|
31
|
+
allowedTools: string[];
|
|
32
|
+
onModeChange: (mode: PermissionMode) => void;
|
|
33
|
+
onAllowedToolsChange: (tools: string[]) => void;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const MODE_LABELS: Record<PermissionMode, { label: string; description: string }> = {
|
|
37
|
+
skip: {
|
|
38
|
+
label: 'Skip all permissions',
|
|
39
|
+
description: 'Claude can use any tool without asking',
|
|
40
|
+
},
|
|
41
|
+
allowed_tools: {
|
|
42
|
+
label: 'Selective auto-approve',
|
|
43
|
+
description: 'Only tools in the allowed list run without prompting',
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export function PermissionModeSelector({
|
|
48
|
+
mode,
|
|
49
|
+
allowedTools,
|
|
50
|
+
onModeChange,
|
|
51
|
+
onAllowedToolsChange,
|
|
52
|
+
}: PermissionModeSelectorProps) {
|
|
53
|
+
const [newTool, setNewTool] = useState('');
|
|
54
|
+
const [showSuggestions, setShowSuggestions] = useState(false);
|
|
55
|
+
const [highlightIdx, setHighlightIdx] = useState(-1);
|
|
56
|
+
const inputRef = useRef<HTMLInputElement>(null);
|
|
57
|
+
const suggestionsRef = useRef<HTMLDivElement>(null);
|
|
58
|
+
|
|
59
|
+
const suggestions = KNOWN_TOOLS.filter(
|
|
60
|
+
(t) =>
|
|
61
|
+
!allowedTools.includes(t) &&
|
|
62
|
+
(newTool.trim() === '' || t.toLowerCase().includes(newTool.trim().toLowerCase()))
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const handleAddTool = useCallback((toolName?: string) => {
|
|
66
|
+
const trimmed = (toolName ?? newTool).trim();
|
|
67
|
+
if (trimmed && !allowedTools.includes(trimmed)) {
|
|
68
|
+
onAllowedToolsChange([...allowedTools, trimmed]);
|
|
69
|
+
setNewTool('');
|
|
70
|
+
setShowSuggestions(false);
|
|
71
|
+
setHighlightIdx(-1);
|
|
72
|
+
}
|
|
73
|
+
}, [newTool, allowedTools, onAllowedToolsChange]);
|
|
74
|
+
|
|
75
|
+
const handleRemoveTool = useCallback((tool: string) => {
|
|
76
|
+
onAllowedToolsChange(allowedTools.filter(t => t !== tool));
|
|
77
|
+
}, [allowedTools, onAllowedToolsChange]);
|
|
78
|
+
|
|
79
|
+
const handleKeyDown = useCallback((e: React.KeyboardEvent) => {
|
|
80
|
+
if (e.key === 'Enter') {
|
|
81
|
+
e.preventDefault();
|
|
82
|
+
if (highlightIdx >= 0 && highlightIdx < suggestions.length) {
|
|
83
|
+
handleAddTool(suggestions[highlightIdx]);
|
|
84
|
+
} else {
|
|
85
|
+
handleAddTool();
|
|
86
|
+
}
|
|
87
|
+
} else if (e.key === 'ArrowDown') {
|
|
88
|
+
e.preventDefault();
|
|
89
|
+
setHighlightIdx((prev) => (prev < suggestions.length - 1 ? prev + 1 : 0));
|
|
90
|
+
} else if (e.key === 'ArrowUp') {
|
|
91
|
+
e.preventDefault();
|
|
92
|
+
setHighlightIdx((prev) => (prev > 0 ? prev - 1 : suggestions.length - 1));
|
|
93
|
+
} else if (e.key === 'Escape') {
|
|
94
|
+
setShowSuggestions(false);
|
|
95
|
+
setHighlightIdx(-1);
|
|
96
|
+
}
|
|
97
|
+
}, [handleAddTool, highlightIdx, suggestions]);
|
|
98
|
+
|
|
99
|
+
// Close suggestions on outside click
|
|
100
|
+
useEffect(() => {
|
|
101
|
+
const handler = (e: MouseEvent) => {
|
|
102
|
+
if (
|
|
103
|
+
suggestionsRef.current &&
|
|
104
|
+
!suggestionsRef.current.contains(e.target as Node) &&
|
|
105
|
+
inputRef.current &&
|
|
106
|
+
!inputRef.current.contains(e.target as Node)
|
|
107
|
+
) {
|
|
108
|
+
setShowSuggestions(false);
|
|
109
|
+
setHighlightIdx(-1);
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
document.addEventListener('mousedown', handler);
|
|
113
|
+
return () => document.removeEventListener('mousedown', handler);
|
|
114
|
+
}, []);
|
|
115
|
+
|
|
116
|
+
return (
|
|
117
|
+
<div className="flex flex-col gap-3">
|
|
118
|
+
<div className="font-mono text-[11px] font-semibold text-content-secondary uppercase tracking-wider">
|
|
119
|
+
Permission Mode
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<div className="flex flex-col gap-1.5">
|
|
123
|
+
{(Object.keys(MODE_LABELS) as PermissionMode[]).map((m) => (
|
|
124
|
+
<label
|
|
125
|
+
key={m}
|
|
126
|
+
className={`flex items-start gap-2 px-3 py-2 rounded-lg cursor-pointer border transition-colors duration-150 ${
|
|
127
|
+
mode === m
|
|
128
|
+
? 'border-accent bg-[rgba(var(--accent-rgb),0.05)]'
|
|
129
|
+
: 'border-transparent hover:bg-surface-raised'
|
|
130
|
+
}`}
|
|
131
|
+
>
|
|
132
|
+
<input
|
|
133
|
+
type="radio"
|
|
134
|
+
name="permissionMode"
|
|
135
|
+
value={m}
|
|
136
|
+
checked={mode === m}
|
|
137
|
+
onChange={() => onModeChange(m)}
|
|
138
|
+
className="mt-0.5 accent-accent"
|
|
139
|
+
/>
|
|
140
|
+
<div className="flex flex-col">
|
|
141
|
+
<span className="font-mono text-[13px] text-content">{MODE_LABELS[m].label}</span>
|
|
142
|
+
<span className="font-mono text-[11px] text-content-secondary">{MODE_LABELS[m].description}</span>
|
|
143
|
+
</div>
|
|
144
|
+
</label>
|
|
145
|
+
))}
|
|
146
|
+
</div>
|
|
147
|
+
|
|
148
|
+
{mode === 'allowed_tools' && (
|
|
149
|
+
<div className="flex flex-col gap-2 mt-1">
|
|
150
|
+
<div className="font-mono text-[11px] font-semibold text-content-secondary uppercase tracking-wider">
|
|
151
|
+
Allowed Tools
|
|
152
|
+
</div>
|
|
153
|
+
|
|
154
|
+
{allowedTools.length > 0 && (
|
|
155
|
+
<div className="flex flex-wrap gap-1.5">
|
|
156
|
+
{allowedTools.map(tool => (
|
|
157
|
+
<span
|
|
158
|
+
key={tool}
|
|
159
|
+
className="inline-flex items-center gap-1 px-2 py-1 bg-surface-raised border border-edge rounded text-xs font-mono text-content"
|
|
160
|
+
>
|
|
161
|
+
{tool}
|
|
162
|
+
<button
|
|
163
|
+
className="bg-none border-none text-content-secondary cursor-pointer px-0.5 text-sm leading-none hover:text-error"
|
|
164
|
+
onClick={() => handleRemoveTool(tool)}
|
|
165
|
+
type="button"
|
|
166
|
+
>
|
|
167
|
+
×
|
|
168
|
+
</button>
|
|
169
|
+
</span>
|
|
170
|
+
))}
|
|
171
|
+
</div>
|
|
172
|
+
)}
|
|
173
|
+
|
|
174
|
+
<div className="relative flex gap-2">
|
|
175
|
+
<div className="relative flex-1">
|
|
176
|
+
<input
|
|
177
|
+
ref={inputRef}
|
|
178
|
+
className="w-full h-8 px-2.5 bg-surface-raised border border-edge rounded text-content font-mono text-[12px] outline-none transition-[border-color] duration-150 focus:border-accent placeholder:text-content-muted"
|
|
179
|
+
type="text"
|
|
180
|
+
value={newTool}
|
|
181
|
+
onChange={e => {
|
|
182
|
+
setNewTool(e.target.value);
|
|
183
|
+
setShowSuggestions(true);
|
|
184
|
+
setHighlightIdx(-1);
|
|
185
|
+
}}
|
|
186
|
+
onFocus={() => setShowSuggestions(true)}
|
|
187
|
+
onKeyDown={handleKeyDown}
|
|
188
|
+
placeholder="Tool name (e.g. Read, Glob)"
|
|
189
|
+
/>
|
|
190
|
+
{showSuggestions && suggestions.length > 0 && (
|
|
191
|
+
<div
|
|
192
|
+
ref={suggestionsRef}
|
|
193
|
+
className="absolute left-0 right-0 bottom-full mb-1 max-h-48 overflow-y-auto bg-surface-raised border border-edge rounded shadow-lg z-50"
|
|
194
|
+
>
|
|
195
|
+
{suggestions.map((tool, i) => (
|
|
196
|
+
<button
|
|
197
|
+
key={tool}
|
|
198
|
+
type="button"
|
|
199
|
+
className={`w-full text-left px-2.5 py-1.5 font-mono text-[12px] cursor-pointer border-none ${
|
|
200
|
+
i === highlightIdx
|
|
201
|
+
? 'bg-accent text-white'
|
|
202
|
+
: 'bg-transparent text-content hover:bg-surface-alt'
|
|
203
|
+
}`}
|
|
204
|
+
onMouseDown={(e) => {
|
|
205
|
+
e.preventDefault();
|
|
206
|
+
handleAddTool(tool);
|
|
207
|
+
}}
|
|
208
|
+
onMouseEnter={() => setHighlightIdx(i)}
|
|
209
|
+
>
|
|
210
|
+
{tool}
|
|
211
|
+
</button>
|
|
212
|
+
))}
|
|
213
|
+
</div>
|
|
214
|
+
)}
|
|
215
|
+
</div>
|
|
216
|
+
<button
|
|
217
|
+
className="h-8 px-3 bg-transparent border border-accent rounded text-accent font-mono text-[12px] cursor-pointer transition-[background,color] duration-150 hover:bg-accent hover:text-white disabled:opacity-60 disabled:cursor-not-allowed"
|
|
218
|
+
onClick={() => handleAddTool()}
|
|
219
|
+
disabled={!newTool.trim()}
|
|
220
|
+
type="button"
|
|
221
|
+
>
|
|
222
|
+
Add
|
|
223
|
+
</button>
|
|
224
|
+
</div>
|
|
225
|
+
</div>
|
|
226
|
+
)}
|
|
227
|
+
</div>
|
|
228
|
+
);
|
|
229
|
+
}
|
package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import React, { useState, useRef, useEffect } from 'react';
|
|
2
2
|
import type { Project } from '../hooks/useProjects';
|
|
3
|
+
import type { PreviewApp } from '../stores/usePreviewStore';
|
|
4
|
+
import { toAppName } from '../utils/preview_utils';
|
|
5
|
+
import { IconButton } from './ds/IconButton';
|
|
6
|
+
import { Settings, Play, Square, ExternalLink, Pencil, X } from 'lucide-react';
|
|
3
7
|
|
|
4
8
|
interface ProjectSelectorProps {
|
|
5
9
|
projects: Project[];
|
|
@@ -7,25 +11,43 @@ interface ProjectSelectorProps {
|
|
|
7
11
|
onSelect: (id: string) => void;
|
|
8
12
|
onCreate: (name: string, type?: string) => Promise<any>;
|
|
9
13
|
onRename: (id: string, name: string) => Promise<void>;
|
|
14
|
+
onUpdatePreviewCommand: (id: string, previewCommand: string | null) => Promise<void>;
|
|
10
15
|
onArchive: (id: string) => Promise<void>;
|
|
11
|
-
|
|
16
|
+
previewApps: PreviewApp[];
|
|
17
|
+
onPreviewStart: (appName: string, projectId: string) => Promise<PreviewApp>;
|
|
18
|
+
onPreviewStop: (appName: string) => Promise<void>;
|
|
19
|
+
previewLoading: boolean;
|
|
12
20
|
}
|
|
13
21
|
|
|
22
|
+
type InlineEditMode = 'rename' | 'command';
|
|
23
|
+
|
|
14
24
|
export function ProjectSelector({
|
|
15
|
-
projects, selectedProjectId, onSelect, onCreate, onRename,
|
|
25
|
+
projects, selectedProjectId, onSelect, onCreate, onRename, onUpdatePreviewCommand, onArchive,
|
|
26
|
+
previewApps, onPreviewStart, onPreviewStop, previewLoading,
|
|
16
27
|
}: ProjectSelectorProps) {
|
|
17
28
|
const [open, setOpen] = useState(false);
|
|
18
29
|
const [creating, setCreating] = useState(false);
|
|
19
30
|
const [newName, setNewName] = useState('');
|
|
20
31
|
const [newType, setNewType] = useState<'software' | 'video'>('software');
|
|
21
|
-
const [
|
|
22
|
-
const [
|
|
32
|
+
const [editingId, setEditingId] = useState<string | null>(null);
|
|
33
|
+
const [editMode, setEditMode] = useState<InlineEditMode>('rename');
|
|
34
|
+
const [editValue, setEditValue] = useState('');
|
|
35
|
+
const [startingAppName, setStartingAppName] = useState<string | null>(null);
|
|
36
|
+
// When user clicks start but project has no command and no package.json fallback
|
|
37
|
+
const [promptCommandForId, setPromptCommandForId] = useState<string | null>(null);
|
|
23
38
|
const dropdownRef = useRef<HTMLDivElement>(null);
|
|
24
39
|
const createInputRef = useRef<HTMLInputElement>(null);
|
|
25
|
-
const
|
|
40
|
+
const editInputRef = useRef<HTMLInputElement>(null);
|
|
26
41
|
|
|
27
42
|
const selectedProject = projects.find(p => p.id === selectedProjectId);
|
|
28
43
|
|
|
44
|
+
// Build a lookup of running apps by appName
|
|
45
|
+
const runningApps = new Map(previewApps.map(a => [a.appName, a]));
|
|
46
|
+
|
|
47
|
+
// Check if the selected project has a running preview
|
|
48
|
+
const selectedAppName = selectedProject ? toAppName(selectedProject.name) : null;
|
|
49
|
+
const selectedIsRunning = selectedAppName ? runningApps.has(selectedAppName) : false;
|
|
50
|
+
|
|
29
51
|
// Close dropdown on outside click
|
|
30
52
|
useEffect(() => {
|
|
31
53
|
if (!open) return;
|
|
@@ -33,7 +55,8 @@ export function ProjectSelector({
|
|
|
33
55
|
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
|
|
34
56
|
setOpen(false);
|
|
35
57
|
setCreating(false);
|
|
36
|
-
|
|
58
|
+
setEditingId(null);
|
|
59
|
+
setPromptCommandForId(null);
|
|
37
60
|
}
|
|
38
61
|
};
|
|
39
62
|
document.addEventListener('mousedown', handler);
|
|
@@ -45,10 +68,10 @@ export function ProjectSelector({
|
|
|
45
68
|
if (creating) createInputRef.current?.focus();
|
|
46
69
|
}, [creating]);
|
|
47
70
|
|
|
48
|
-
// Focus
|
|
71
|
+
// Focus edit input when shown
|
|
49
72
|
useEffect(() => {
|
|
50
|
-
if (
|
|
51
|
-
}, [
|
|
73
|
+
if (editingId || promptCommandForId) editInputRef.current?.focus();
|
|
74
|
+
}, [editingId, promptCommandForId]);
|
|
52
75
|
|
|
53
76
|
const handleCreate = async () => {
|
|
54
77
|
const trimmed = newName.trim();
|
|
@@ -64,16 +87,28 @@ export function ProjectSelector({
|
|
|
64
87
|
}
|
|
65
88
|
};
|
|
66
89
|
|
|
67
|
-
const
|
|
68
|
-
const trimmed =
|
|
69
|
-
if (!
|
|
90
|
+
const handleEditSave = async () => {
|
|
91
|
+
const trimmed = editValue.trim();
|
|
92
|
+
if (!editingId) return;
|
|
93
|
+
|
|
70
94
|
try {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
95
|
+
if (editMode === 'rename') {
|
|
96
|
+
if (!trimmed) return;
|
|
97
|
+
await onRename(editingId, trimmed);
|
|
98
|
+
} else {
|
|
99
|
+
// command mode — empty string means clear
|
|
100
|
+
await onUpdatePreviewCommand(editingId, trimmed || null);
|
|
101
|
+
}
|
|
74
102
|
} catch {
|
|
75
103
|
// Error is handled by the caller
|
|
76
104
|
}
|
|
105
|
+
setEditingId(null);
|
|
106
|
+
setEditValue('');
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const handleEditCancel = () => {
|
|
110
|
+
setEditingId(null);
|
|
111
|
+
setEditValue('');
|
|
77
112
|
};
|
|
78
113
|
|
|
79
114
|
const handleArchive = async (id: string) => {
|
|
@@ -84,6 +119,81 @@ export function ProjectSelector({
|
|
|
84
119
|
}
|
|
85
120
|
};
|
|
86
121
|
|
|
122
|
+
const startRename = (e: React.MouseEvent, project: Project) => {
|
|
123
|
+
e.stopPropagation();
|
|
124
|
+
setEditingId(project.id);
|
|
125
|
+
setEditMode('rename');
|
|
126
|
+
setEditValue(project.name);
|
|
127
|
+
setPromptCommandForId(null);
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const startCommandEdit = (e: React.MouseEvent, project: Project) => {
|
|
131
|
+
e.stopPropagation();
|
|
132
|
+
setEditingId(project.id);
|
|
133
|
+
setEditMode('command');
|
|
134
|
+
setEditValue(project.previewCommand || '');
|
|
135
|
+
setPromptCommandForId(null);
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const handleCommandPromptSave = async () => {
|
|
139
|
+
if (!promptCommandForId) return;
|
|
140
|
+
const trimmed = editValue.trim();
|
|
141
|
+
if (!trimmed) return;
|
|
142
|
+
|
|
143
|
+
try {
|
|
144
|
+
await onUpdatePreviewCommand(promptCommandForId, trimmed);
|
|
145
|
+
// Now start the preview
|
|
146
|
+
const project = projects.find(p => p.id === promptCommandForId);
|
|
147
|
+
if (project) {
|
|
148
|
+
const appName = toAppName(project.name);
|
|
149
|
+
setStartingAppName(appName);
|
|
150
|
+
try {
|
|
151
|
+
await onPreviewStart(appName, project.id);
|
|
152
|
+
window.open(`/apps/${appName}/`, '_blank');
|
|
153
|
+
} finally {
|
|
154
|
+
setStartingAppName(null);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
} catch {
|
|
158
|
+
// Error handled by caller
|
|
159
|
+
}
|
|
160
|
+
setPromptCommandForId(null);
|
|
161
|
+
setEditValue('');
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const handlePreviewToggle = async (e: React.MouseEvent, project: Project) => {
|
|
165
|
+
e.stopPropagation();
|
|
166
|
+
const appName = toAppName(project.name);
|
|
167
|
+
const isRunning = runningApps.has(appName);
|
|
168
|
+
|
|
169
|
+
if (isRunning) {
|
|
170
|
+
await onPreviewStop(appName);
|
|
171
|
+
} else {
|
|
172
|
+
setStartingAppName(appName);
|
|
173
|
+
try {
|
|
174
|
+
// Attempt to start via backend — it will use previewCommand if set,
|
|
175
|
+
// otherwise auto-detect from package.json
|
|
176
|
+
await onPreviewStart(appName, project.id);
|
|
177
|
+
window.open(`/apps/${appName}/`, '_blank');
|
|
178
|
+
} catch (err: any) {
|
|
179
|
+
// If backend couldn't detect a start command, prompt the user to configure one
|
|
180
|
+
const message = err?.message || '';
|
|
181
|
+
if (message.includes('No dev server script found')) {
|
|
182
|
+
setPromptCommandForId(project.id);
|
|
183
|
+
setEditValue('');
|
|
184
|
+
}
|
|
185
|
+
// Other errors are handled by the caller
|
|
186
|
+
} finally {
|
|
187
|
+
setStartingAppName(null);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const handleOpenPreview = (e: React.MouseEvent, appName: string) => {
|
|
193
|
+
e.stopPropagation();
|
|
194
|
+
window.open(`/apps/${appName}/`, '_blank');
|
|
195
|
+
};
|
|
196
|
+
|
|
87
197
|
return (
|
|
88
198
|
<div className="relative" ref={dropdownRef}>
|
|
89
199
|
<button
|
|
@@ -91,6 +201,9 @@ export function ProjectSelector({
|
|
|
91
201
|
onClick={() => setOpen(v => !v)}
|
|
92
202
|
title="Select project"
|
|
93
203
|
>
|
|
204
|
+
{selectedIsRunning && (
|
|
205
|
+
<span className="shrink-0 w-2 h-2 rounded-full bg-emerald-500" title="Preview server running" />
|
|
206
|
+
)}
|
|
94
207
|
<span className="overflow-hidden text-ellipsis whitespace-nowrap">
|
|
95
208
|
{selectedProject?.name || 'No project'}
|
|
96
209
|
</span>
|
|
@@ -98,80 +211,133 @@ export function ProjectSelector({
|
|
|
98
211
|
</button>
|
|
99
212
|
|
|
100
213
|
{open && (
|
|
101
|
-
<div className="absolute top-[calc(100%+4px)] left-0 min-w-[
|
|
214
|
+
<div className="absolute top-[calc(100%+4px)] left-0 min-w-[280px] max-w-[360px] bg-panel border border-edge rounded-md shadow-[0_4px_12px_var(--panel-shadow)] z-[200] overflow-hidden">
|
|
102
215
|
<div className="max-h-60 overflow-y-auto py-1">
|
|
103
|
-
{projects.map(project =>
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
>
|
|
126
|
-
<span className="overflow-hidden text-ellipsis whitespace-nowrap">{project.name}</span>
|
|
127
|
-
{project.type === 'video' && (
|
|
128
|
-
<span className="shrink-0 rounded-full bg-purple-500/15 px-1.5 py-0.5 text-[9px] font-bold text-purple-400">Video</span>
|
|
129
|
-
)}
|
|
130
|
-
</button>
|
|
131
|
-
<div className="flex gap-0.5 opacity-0 transition-opacity duration-150 group-hover:opacity-100">
|
|
132
|
-
{onOpenGitModal && (
|
|
133
|
-
<button
|
|
134
|
-
className={`bg-none border-none text-xs cursor-pointer px-1 py-0.5 rounded-sm transition-[color,background] duration-150 hover:text-content hover:bg-surface-raised ${project.repoUrl ? 'text-accent' : 'text-content-secondary'}`}
|
|
135
|
-
title="Git Repository"
|
|
136
|
-
onClick={(e) => {
|
|
137
|
-
e.stopPropagation();
|
|
138
|
-
setOpen(false);
|
|
139
|
-
onOpenGitModal(project);
|
|
140
|
-
}}
|
|
141
|
-
>
|
|
142
|
-
<svg viewBox="0 0 16 16" width="14" height="14" fill="currentColor">
|
|
143
|
-
<path d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z" />
|
|
144
|
-
</svg>
|
|
145
|
-
</button>
|
|
146
|
-
)}
|
|
147
|
-
<button
|
|
148
|
-
className="bg-none border-none text-content-muted text-xs cursor-pointer px-1 py-0.5 rounded-sm transition-[color,background] duration-150 hover:text-content hover:bg-surface-raised"
|
|
149
|
-
title="Rename"
|
|
150
|
-
onClick={(e) => {
|
|
151
|
-
e.stopPropagation();
|
|
152
|
-
setRenamingId(project.id);
|
|
153
|
-
setRenameValue(project.name);
|
|
216
|
+
{projects.map(project => {
|
|
217
|
+
const appName = toAppName(project.name);
|
|
218
|
+
const isRunning = runningApps.has(appName);
|
|
219
|
+
const isStarting = startingAppName === appName;
|
|
220
|
+
const isPromptingCommand = promptCommandForId === project.id;
|
|
221
|
+
|
|
222
|
+
return (
|
|
223
|
+
<div
|
|
224
|
+
key={project.id}
|
|
225
|
+
className={`group flex flex-col hover:bg-tab-hover${project.id === selectedProjectId ? ' bg-tab-active' : ''}`}
|
|
226
|
+
>
|
|
227
|
+
{editingId === project.id ? (
|
|
228
|
+
<div className="flex items-center px-1 py-0.5">
|
|
229
|
+
<input
|
|
230
|
+
ref={editInputRef}
|
|
231
|
+
className="flex-1 py-[5px] px-2 bg-surface border border-accent rounded-sm text-content font-mono text-xs outline-none mx-1 my-0.5"
|
|
232
|
+
placeholder={editMode === 'rename' ? 'Project name...' : 'e.g. npm run dev, node server.js'}
|
|
233
|
+
value={editValue}
|
|
234
|
+
onChange={e => setEditValue(e.target.value)}
|
|
235
|
+
onKeyDown={e => {
|
|
236
|
+
if (e.key === 'Enter') handleEditSave();
|
|
237
|
+
if (e.key === 'Escape') handleEditCancel();
|
|
154
238
|
}}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
239
|
+
onBlur={handleEditSave}
|
|
240
|
+
/>
|
|
241
|
+
</div>
|
|
242
|
+
) : (
|
|
243
|
+
<>
|
|
244
|
+
<div className="flex items-center pr-1">
|
|
159
245
|
<button
|
|
160
|
-
className="
|
|
161
|
-
|
|
162
|
-
onClick={(e) => {
|
|
163
|
-
e.stopPropagation();
|
|
164
|
-
handleArchive(project.id);
|
|
165
|
-
}}
|
|
246
|
+
className="flex-1 flex items-center gap-1.5 text-left py-1.5 px-2 bg-none border-none text-content font-mono text-xs cursor-pointer overflow-hidden"
|
|
247
|
+
onClick={() => { onSelect(project.id); setOpen(false); }}
|
|
166
248
|
>
|
|
167
|
-
|
|
249
|
+
{isRunning && (
|
|
250
|
+
<span className="shrink-0 w-1.5 h-1.5 rounded-full bg-emerald-500" />
|
|
251
|
+
)}
|
|
252
|
+
<span className="overflow-hidden text-ellipsis whitespace-nowrap">{project.name}</span>
|
|
253
|
+
{project.type === 'video' && (
|
|
254
|
+
<span className="shrink-0 rounded-full bg-purple-500/15 px-1.5 py-0.5 text-[9px] font-bold text-purple-400">Video</span>
|
|
255
|
+
)}
|
|
168
256
|
</button>
|
|
257
|
+
<div className="flex items-center gap-0.5 opacity-0 transition-opacity duration-150 group-hover:opacity-100">
|
|
258
|
+
{/* Preview start/stop toggle */}
|
|
259
|
+
<IconButton
|
|
260
|
+
variant={isRunning ? 'accent' : 'ghost'}
|
|
261
|
+
size="sm"
|
|
262
|
+
label={isRunning ? 'Stop preview server' : 'Start preview server'}
|
|
263
|
+
disabled={previewLoading || isStarting}
|
|
264
|
+
onClick={(e) => handlePreviewToggle(e, project)}
|
|
265
|
+
>
|
|
266
|
+
{isStarting ? (
|
|
267
|
+
<span className="inline-block w-3 h-3 border border-current border-t-transparent rounded-full animate-spin" />
|
|
268
|
+
) : isRunning ? (
|
|
269
|
+
<Square className="w-3 h-3" />
|
|
270
|
+
) : (
|
|
271
|
+
<Play className="w-3 h-3" />
|
|
272
|
+
)}
|
|
273
|
+
</IconButton>
|
|
274
|
+
{/* Open Preview link for running servers */}
|
|
275
|
+
{isRunning && (
|
|
276
|
+
<IconButton
|
|
277
|
+
variant="ghost"
|
|
278
|
+
size="sm"
|
|
279
|
+
label="Open preview in new tab"
|
|
280
|
+
onClick={(e) => handleOpenPreview(e, appName)}
|
|
281
|
+
>
|
|
282
|
+
<ExternalLink className="w-3 h-3" />
|
|
283
|
+
</IconButton>
|
|
284
|
+
)}
|
|
285
|
+
{/* Settings/gear icon — edit preview command */}
|
|
286
|
+
<IconButton
|
|
287
|
+
variant="ghost"
|
|
288
|
+
size="sm"
|
|
289
|
+
label="Configure preview command"
|
|
290
|
+
onClick={(e) => startCommandEdit(e, project)}
|
|
291
|
+
>
|
|
292
|
+
<Settings className="w-3 h-3" />
|
|
293
|
+
</IconButton>
|
|
294
|
+
{/* Rename */}
|
|
295
|
+
<IconButton
|
|
296
|
+
variant="ghost"
|
|
297
|
+
size="sm"
|
|
298
|
+
label="Rename"
|
|
299
|
+
onClick={(e) => startRename(e, project)}
|
|
300
|
+
>
|
|
301
|
+
<Pencil className="w-3 h-3" />
|
|
302
|
+
</IconButton>
|
|
303
|
+
{/* Archive */}
|
|
304
|
+
{!project.isDefault && (
|
|
305
|
+
<IconButton
|
|
306
|
+
variant="danger"
|
|
307
|
+
size="sm"
|
|
308
|
+
label="Archive"
|
|
309
|
+
onClick={(e) => {
|
|
310
|
+
e.stopPropagation();
|
|
311
|
+
handleArchive(project.id);
|
|
312
|
+
}}
|
|
313
|
+
>
|
|
314
|
+
<X className="w-3 h-3" />
|
|
315
|
+
</IconButton>
|
|
316
|
+
)}
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
{/* Command prompt — shown when user tries to start without a configured command */}
|
|
320
|
+
{isPromptingCommand && (
|
|
321
|
+
<div className="flex items-center px-1 pb-1">
|
|
322
|
+
<input
|
|
323
|
+
ref={editInputRef}
|
|
324
|
+
className="flex-1 py-[5px] px-2 bg-surface border border-accent rounded-sm text-content font-mono text-xs outline-none mx-1"
|
|
325
|
+
placeholder="Start command (e.g. npm run dev)..."
|
|
326
|
+
value={editValue}
|
|
327
|
+
onChange={e => setEditValue(e.target.value)}
|
|
328
|
+
onKeyDown={e => {
|
|
329
|
+
if (e.key === 'Enter') handleCommandPromptSave();
|
|
330
|
+
if (e.key === 'Escape') { setPromptCommandForId(null); setEditValue(''); }
|
|
331
|
+
}}
|
|
332
|
+
onBlur={() => { setPromptCommandForId(null); setEditValue(''); }}
|
|
333
|
+
/>
|
|
334
|
+
</div>
|
|
169
335
|
)}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
)
|
|
336
|
+
</>
|
|
337
|
+
)}
|
|
338
|
+
</div>
|
|
339
|
+
);
|
|
340
|
+
})}
|
|
175
341
|
</div>
|
|
176
342
|
|
|
177
343
|
<div className="border-t border-edge p-1">
|
package/templates/assistkick-product-system/packages/frontend/src/components/QueuedMessageBubble.tsx
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueuedMessageBubble — displays a single queued user message with a
|
|
3
|
+
* pending/queued visual state and a remove button.
|
|
4
|
+
*
|
|
5
|
+
* Queued messages appear as user bubbles in the chat UI but with reduced
|
|
6
|
+
* opacity and a "queued" indicator to distinguish them from sent messages.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
interface Props {
|
|
10
|
+
id: string;
|
|
11
|
+
text: string;
|
|
12
|
+
onRemove: (id: string) => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const QueuedMessageBubble = ({ id, text, onRemove }: Props) => {
|
|
16
|
+
return (
|
|
17
|
+
<div className="flex justify-end items-start gap-2 group opacity-60">
|
|
18
|
+
<div className="flex flex-col items-end gap-1 max-w-[80%]">
|
|
19
|
+
<div className="relative bg-accent/10 border border-accent/30 rounded-xl rounded-tr-sm px-4 py-2.5">
|
|
20
|
+
<pre className="m-0 text-[13px] font-mono text-content whitespace-pre-wrap break-words">
|
|
21
|
+
{text}
|
|
22
|
+
</pre>
|
|
23
|
+
<button
|
|
24
|
+
type="button"
|
|
25
|
+
onClick={() => onRemove(id)}
|
|
26
|
+
className="absolute -top-2 -right-2 w-5 h-5 rounded-full bg-surface border border-edge flex items-center justify-center text-content-secondary text-xs cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity duration-150 hover:bg-error hover:text-white hover:border-error"
|
|
27
|
+
aria-label="Remove queued message"
|
|
28
|
+
>
|
|
29
|
+
×
|
|
30
|
+
</button>
|
|
31
|
+
</div>
|
|
32
|
+
<span className="text-[10px] font-mono text-content-muted uppercase tracking-wider">
|
|
33
|
+
queued
|
|
34
|
+
</span>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
);
|
|
38
|
+
};
|