kcg-code 0.0.0-stage → 0.1.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/README.md +228 -2
- package/bin/kcgcode.ts +13 -0
- package/bun-env.d.ts +19 -0
- package/kcg-code.config.example.json +3 -0
- package/package.json +83 -4
- package/public/logo.svg +1 -0
- package/public/manifest.json +18 -0
- package/public/sw.js +49 -0
- package/src/App.tsx +39 -0
- package/src/__tests__/manifest.test.ts +61 -0
- package/src/build-config.ts +9 -0
- package/src/cli/__tests__/dashboard.test.ts +62 -0
- package/src/cli/args.ts +123 -0
- package/src/cli/commands.ts +174 -0
- package/src/cli/dashboard.tsx +343 -0
- package/src/cli/index.ts +37 -0
- package/src/cli/theme.ts +100 -0
- package/src/components/auth/AuthGate.tsx +182 -0
- package/src/components/auth/AvatarEditor.tsx +236 -0
- package/src/components/auth/LockScreen.tsx +714 -0
- package/src/components/auth/ProfileAvatar.tsx +60 -0
- package/src/components/auth/WelcomeScreen.tsx +672 -0
- package/src/components/auth/__tests__/WelcomeScreen.test.tsx +42 -0
- package/src/components/auth/avatar-icons.tsx +114 -0
- package/src/components/layout/AppShell.tsx +142 -0
- package/src/components/layout/AppSidebar.tsx +419 -0
- package/src/components/layout/SearchDialog.tsx +189 -0
- package/src/components/layout/SidebarAccount.tsx +111 -0
- package/src/components/layout/SidebarSessionMenu.tsx +163 -0
- package/src/components/layout/ThemeToggle.tsx +67 -0
- package/src/components/projects/FolderBrowser.tsx +142 -0
- package/src/components/projects/HomeChat.tsx +250 -0
- package/src/components/projects/NewProjectDialog.tsx +249 -0
- package/src/components/projects/PanelSection.tsx +102 -0
- package/src/components/projects/ProjectInstructions.tsx +229 -0
- package/src/components/projects/ProjectRow.tsx +58 -0
- package/src/components/projects/ProjectSidePanel.tsx +448 -0
- package/src/components/sessions/AgentModeList.tsx +265 -0
- package/src/components/sessions/AgentPicker.tsx +123 -0
- package/src/components/sessions/AssistantTurn.tsx +145 -0
- package/src/components/sessions/AttachmentTile.tsx +161 -0
- package/src/components/sessions/CodeBlock.tsx +107 -0
- package/src/components/sessions/ComposerActionSheet.tsx +96 -0
- package/src/components/sessions/ConfirmDeleteProjectDialog.tsx +80 -0
- package/src/components/sessions/ConfirmSessionDeleteDialog.tsx +72 -0
- package/src/components/sessions/ContextPanel.tsx +561 -0
- package/src/components/sessions/DropPlate.tsx +165 -0
- package/src/components/sessions/FileDropOverlay.tsx +232 -0
- package/src/components/sessions/FileMentionDropdown.tsx +47 -0
- package/src/components/sessions/ImageLightbox.tsx +173 -0
- package/src/components/sessions/MarkdownContent.tsx +187 -0
- package/src/components/sessions/Mascot.tsx +47 -0
- package/src/components/sessions/MessageActions.tsx +60 -0
- package/src/components/sessions/MessageRail.tsx +121 -0
- package/src/components/sessions/ModelPicker.tsx +209 -0
- package/src/components/sessions/ModelSearchList.tsx +151 -0
- package/src/components/sessions/NewSessionDialog.tsx +189 -0
- package/src/components/sessions/PendingAttachments.tsx +127 -0
- package/src/components/sessions/PromptCard.tsx +1085 -0
- package/src/components/sessions/PromptPanel.tsx +112 -0
- package/src/components/sessions/SessionComposer.tsx +640 -0
- package/src/components/sessions/SessionHeader.tsx +175 -0
- package/src/components/sessions/SessionList.tsx +396 -0
- package/src/components/sessions/SessionRow.tsx +137 -0
- package/src/components/sessions/SessionTimeline.tsx +275 -0
- package/src/components/sessions/SessionTimelineSkeleton.tsx +83 -0
- package/src/components/sessions/SessionView.tsx +236 -0
- package/src/components/sessions/ThoughtProcessBlock.tsx +199 -0
- package/src/components/sessions/UserMessage.tsx +177 -0
- package/src/components/sessions/__tests__/ContextPanel.test.tsx +220 -0
- package/src/components/sessions/__tests__/MarkdownContent.test.tsx +89 -0
- package/src/components/sessions/__tests__/MessageRail.test.tsx +40 -0
- package/src/components/sessions/__tests__/PermissionCard.test.tsx +79 -0
- package/src/components/sessions/__tests__/PromptCard.test.ts +166 -0
- package/src/components/sessions/__tests__/QuestionCard.test.tsx +166 -0
- package/src/components/sessions/__tests__/SessionTimelineSkeleton.test.tsx +32 -0
- package/src/components/sessions/__tests__/SessionView.test.ts +386 -0
- package/src/components/settings/AboutGroup.tsx +170 -0
- package/src/components/settings/AccountGroup.tsx +122 -0
- package/src/components/settings/DevicesGroup.tsx +151 -0
- package/src/components/settings/LhrRemoteAccess.tsx +262 -0
- package/src/components/settings/PasskeysGroup.tsx +551 -0
- package/src/components/settings/RemoteAccessGroup.tsx +1226 -0
- package/src/components/settings/SecurityGroup.tsx +566 -0
- package/src/components/settings/WorkspaceGroup.tsx +370 -0
- package/src/components/settings/__tests__/AboutGroup.test.tsx +47 -0
- package/src/components/settings/__tests__/PasskeysGroup.test.tsx +109 -0
- package/src/components/settings/__tests__/WorkspaceGroup.test.tsx +49 -0
- package/src/components/settings/prefs.tsx +265 -0
- package/src/components/skills/InstallNotifications.tsx +325 -0
- package/src/components/skills/InstallSkillDialog.tsx +233 -0
- package/src/components/skills/SkillCards.tsx +210 -0
- package/src/components/skills/SkillsProjectPicker.tsx +178 -0
- package/src/components/ui/alert-dialog.tsx +179 -0
- package/src/components/ui/alert.tsx +60 -0
- package/src/components/ui/badge.tsx +46 -0
- package/src/components/ui/bubble.tsx +125 -0
- package/src/components/ui/button.tsx +62 -0
- package/src/components/ui/card.tsx +75 -0
- package/src/components/ui/collapsible.tsx +19 -0
- package/src/components/ui/dialog.tsx +142 -0
- package/src/components/ui/dropdown-menu.tsx +225 -0
- package/src/components/ui/empty.tsx +94 -0
- package/src/components/ui/field.tsx +234 -0
- package/src/components/ui/input-group.tsx +161 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/label.tsx +19 -0
- package/src/components/ui/message-scroller.tsx +131 -0
- package/src/components/ui/message.tsx +85 -0
- package/src/components/ui/popover.tsx +46 -0
- package/src/components/ui/select.tsx +172 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +138 -0
- package/src/components/ui/sidebar.tsx +699 -0
- package/src/components/ui/skeleton.tsx +15 -0
- package/src/components/ui/sonner.tsx +45 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/switch.tsx +32 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/tooltip.tsx +51 -0
- package/src/config.ts +85 -0
- package/src/db.ts +146 -0
- package/src/frontend.tsx +51 -0
- package/src/hooks/__tests__/useMention.test.ts +79 -0
- package/src/hooks/__tests__/useResyncOnReconnect.test.ts +128 -0
- package/src/hooks/__tests__/useRouter.test.ts +17 -0
- package/src/hooks/__tests__/useSmoothText.react.test.ts +194 -0
- package/src/hooks/__tests__/useSmoothText.test.ts +61 -0
- package/src/hooks/__tests__/useTheme.test.ts +118 -0
- package/src/hooks/use-mobile.ts +16 -0
- package/src/hooks/useAgentPicker.ts +97 -0
- package/src/hooks/useMediaQuery.ts +49 -0
- package/src/hooks/useMention.ts +236 -0
- package/src/hooks/useProjects.ts +88 -0
- package/src/hooks/useResyncOnReconnect.ts +28 -0
- package/src/hooks/useRouter.ts +92 -0
- package/src/hooks/useSessionChat.ts +427 -0
- package/src/hooks/useSessionContext.ts +106 -0
- package/src/hooks/useSessionList.ts +234 -0
- package/src/hooks/useSidebarData.ts +108 -0
- package/src/hooks/useSmoothText.ts +122 -0
- package/src/hooks/useTheme.ts +164 -0
- package/src/hooks/useWebSocket.ts +196 -0
- package/src/index.css +27 -0
- package/src/index.html +14 -0
- package/src/index.ts +49 -0
- package/src/lib/__tests__/attachments.test.ts +58 -0
- package/src/lib/__tests__/avatar-presets.test.ts +25 -0
- package/src/lib/__tests__/collapsible.test.ts +101 -0
- package/src/lib/__tests__/composer.test.ts +118 -0
- package/src/lib/__tests__/highlight.test.ts +42 -0
- package/src/lib/__tests__/home-chat.test.ts +64 -0
- package/src/lib/__tests__/lock-screen.test.ts +64 -0
- package/src/lib/__tests__/model-picker.test.ts +205 -0
- package/src/lib/__tests__/onboarding.test.ts +41 -0
- package/src/lib/__tests__/permission-info.test.ts +61 -0
- package/src/lib/__tests__/project-extensions.test.ts +52 -0
- package/src/lib/__tests__/project-overview.test.ts +173 -0
- package/src/lib/__tests__/references.test.ts +121 -0
- package/src/lib/__tests__/routes.test.ts +134 -0
- package/src/lib/__tests__/session-summary.test.ts +115 -0
- package/src/lib/__tests__/session-title.test.ts +58 -0
- package/src/lib/__tests__/sidebar-groups.test.ts +100 -0
- package/src/lib/__tests__/skill-installs.test.ts +134 -0
- package/src/lib/__tests__/skills-catalog.test.ts +96 -0
- package/src/lib/__tests__/tone.test.ts +37 -0
- package/src/lib/__tests__/turns-delta.test.ts +55 -0
- package/src/lib/api.ts +306 -0
- package/src/lib/attachments.ts +203 -0
- package/src/lib/auth.ts +92 -0
- package/src/lib/avatar-presets.ts +31 -0
- package/src/lib/collapsible.ts +75 -0
- package/src/lib/composer.ts +89 -0
- package/src/lib/data-events.ts +17 -0
- package/src/lib/headlines.ts +30 -0
- package/src/lib/highlight.ts +186 -0
- package/src/lib/layout.ts +20 -0
- package/src/lib/lock-screen.ts +54 -0
- package/src/lib/model-picker.ts +133 -0
- package/src/lib/onboarding.ts +38 -0
- package/src/lib/passkeys.ts +113 -0
- package/src/lib/pending-prompt.ts +53 -0
- package/src/lib/permission-info.ts +101 -0
- package/src/lib/project-extensions.ts +74 -0
- package/src/lib/project-overview.ts +117 -0
- package/src/lib/prompts.ts +87 -0
- package/src/lib/references.ts +106 -0
- package/src/lib/routes.ts +115 -0
- package/src/lib/session-handoff.ts +25 -0
- package/src/lib/session-status.ts +45 -0
- package/src/lib/session-summary.ts +68 -0
- package/src/lib/session-title.ts +63 -0
- package/src/lib/sidebar-groups.ts +78 -0
- package/src/lib/skill-installs.ts +360 -0
- package/src/lib/skills-catalog.ts +99 -0
- package/src/lib/time.ts +22 -0
- package/src/lib/tone.ts +80 -0
- package/src/lib/turns.ts +320 -0
- package/src/lib/utils.ts +6 -0
- package/src/logo.svg +31 -0
- package/src/pages/projects/ProjectDetailPage.tsx +130 -0
- package/src/pages/projects/ProjectsPage.tsx +97 -0
- package/src/pages/sessions/SessionPage.tsx +122 -0
- package/src/pages/settings/SettingsPage.tsx +299 -0
- package/src/pages/settings/__tests__/SettingsPage.test.ts +9 -0
- package/src/pages/skills/SkillsPage.tsx +701 -0
- package/src/paths.ts +121 -0
- package/src/runtime.ts +24 -0
- package/src/server/__tests__/app.test.ts +1031 -0
- package/src/server/__tests__/auth.test.ts +370 -0
- package/src/server/__tests__/config.test.ts +101 -0
- package/src/server/__tests__/db.test.ts +485 -0
- package/src/server/__tests__/host.test.ts +14 -0
- package/src/server/__tests__/tunnel-config.test.ts +38 -0
- package/src/server/app.ts +415 -0
- package/src/server/db/auth.ts +311 -0
- package/src/server/db/messages.ts +50 -0
- package/src/server/db/projects.ts +106 -0
- package/src/server/db/prompts.ts +78 -0
- package/src/server/db/rows.ts +150 -0
- package/src/server/db/schema.ts +221 -0
- package/src/server/db/sessions.ts +239 -0
- package/src/server/db/tunnel.ts +124 -0
- package/src/server/host.ts +12 -0
- package/src/server/result.ts +15 -0
- package/src/server/routes/auth-guard.ts +156 -0
- package/src/server/routes/auth.routes.ts +287 -0
- package/src/server/routes/helpers.ts +165 -0
- package/src/server/routes/meta.routes.ts +24 -0
- package/src/server/routes/passkeys.routes.ts +161 -0
- package/src/server/routes/projects.routes.ts +157 -0
- package/src/server/routes/sessions.routes.ts +197 -0
- package/src/server/routes/skills.routes.ts +117 -0
- package/src/server/routes/tunnel.routes.ts +132 -0
- package/src/server/routes/types.ts +35 -0
- package/src/server/routes/uploads.routes.ts +54 -0
- package/src/server/routes/workspace.routes.ts +127 -0
- package/src/server/services/__tests__/app-info.test.ts +18 -0
- package/src/server/services/__tests__/attachments.test.ts +159 -0
- package/src/server/services/__tests__/auth-passkeys.test.ts +358 -0
- package/src/server/services/__tests__/auth.test.ts +341 -0
- package/src/server/services/__tests__/frpc-installer.test.ts +83 -0
- package/src/server/services/__tests__/git-info.test.ts +74 -0
- package/src/server/services/__tests__/opencode.test.ts +320 -0
- package/src/server/services/__tests__/project-manager.test.ts +190 -0
- package/src/server/services/__tests__/sandbox.test.ts +141 -0
- package/src/server/services/__tests__/session-manager.test.ts +3469 -0
- package/src/server/services/__tests__/skill-install-jobs.test.ts +226 -0
- package/src/server/services/__tests__/skills-registry.test.ts +359 -0
- package/src/server/services/__tests__/tunnel-lhr.test.ts +234 -0
- package/src/server/services/__tests__/tunnel.test.ts +367 -0
- package/src/server/services/__tests__/websocket-gateway.test.ts +677 -0
- package/src/server/services/__tests__/workspace.test.ts +249 -0
- package/src/server/services/app-info.ts +29 -0
- package/src/server/services/attachments.ts +193 -0
- package/src/server/services/auth-passkeys.ts +459 -0
- package/src/server/services/auth.ts +633 -0
- package/src/server/services/frpc-installer.ts +183 -0
- package/src/server/services/git-info.ts +87 -0
- package/src/server/services/opencode-client.ts +714 -0
- package/src/server/services/opencode-server.ts +319 -0
- package/src/server/services/opencode-sse.ts +82 -0
- package/src/server/services/project-manager.ts +147 -0
- package/src/server/services/sandbox.ts +83 -0
- package/src/server/services/session-events.ts +256 -0
- package/src/server/services/session-manager.ts +1374 -0
- package/src/server/services/skill-install-jobs.ts +262 -0
- package/src/server/services/skills-registry.ts +468 -0
- package/src/server/services/tunnel-api.ts +207 -0
- package/src/server/services/tunnel-config.ts +45 -0
- package/src/server/services/tunnel-lhr.ts +387 -0
- package/src/server/services/tunnel.ts +633 -0
- package/src/server/services/turn-stream.ts +339 -0
- package/src/server/services/websocket-gateway.ts +374 -0
- package/src/server/services/workspace.ts +223 -0
- package/src/server/services/ws-transport.ts +121 -0
- package/src/types/context.ts +69 -0
- package/src/types/index.ts +13 -0
- package/src/types/message.ts +58 -0
- package/src/types/project.ts +16 -0
- package/src/types/prompt.ts +118 -0
- package/src/types/session.ts +52 -0
- package/src/types/turns.ts +47 -0
- package/src/ws-protocol.ts +93 -0
- package/styles/globals.css +311 -0
- package/tsconfig.json +37 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom instruction Project — seksi di panel samping + dialog editor.
|
|
3
|
+
*
|
|
4
|
+
* Instruksi disimpan lewat `PATCH /api/projects/:id` dan dikirim server
|
|
5
|
+
* sebagai `system` di setiap prompt Session milik Project ini (ditambahkan
|
|
6
|
+
* di atas AGENTS.md / instruksi bawaan opencode). Perubahan langsung berlaku
|
|
7
|
+
* untuk prompt berikutnya, termasuk di Session yang sudah berjalan.
|
|
8
|
+
*/
|
|
9
|
+
import { PencilIcon, PlusIcon, ScrollTextIcon } from "lucide-react";
|
|
10
|
+
import { useEffect, useId, useState } from "react";
|
|
11
|
+
import { toast } from "sonner";
|
|
12
|
+
import { PanelBody, PanelSection } from "@/components/projects/PanelSection";
|
|
13
|
+
import { Alert, AlertDescription } from "@/components/ui/alert";
|
|
14
|
+
import { Button } from "@/components/ui/button";
|
|
15
|
+
import {
|
|
16
|
+
Dialog,
|
|
17
|
+
DialogContent,
|
|
18
|
+
DialogDescription,
|
|
19
|
+
DialogFooter,
|
|
20
|
+
DialogHeader,
|
|
21
|
+
DialogTitle,
|
|
22
|
+
} from "@/components/ui/dialog";
|
|
23
|
+
import { Spinner } from "@/components/ui/spinner";
|
|
24
|
+
import { Textarea } from "@/components/ui/textarea";
|
|
25
|
+
import { ApiError, apiFetch } from "@/lib/api";
|
|
26
|
+
import { cn } from "@/lib/utils";
|
|
27
|
+
import type { Project } from "@/types";
|
|
28
|
+
|
|
29
|
+
/** Sama dengan batas server (`MAX_PROJECT_INSTRUCTIONS_LENGTH`). */
|
|
30
|
+
const MAX_LENGTH = 20_000;
|
|
31
|
+
|
|
32
|
+
export interface ProjectInstructionsProps {
|
|
33
|
+
project: Project;
|
|
34
|
+
/** Dipanggil dengan Project terbaru setelah instruksi tersimpan. */
|
|
35
|
+
onSaved: (project: Project) => void;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function ProjectInstructions({ project, onSaved }: ProjectInstructionsProps) {
|
|
39
|
+
const [open, setOpen] = useState(false);
|
|
40
|
+
const instructions = project.instructions ?? "";
|
|
41
|
+
const hasInstructions = instructions.length > 0;
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<PanelSection
|
|
45
|
+
id="instructions"
|
|
46
|
+
title="Instructions"
|
|
47
|
+
icon={<ScrollTextIcon />}
|
|
48
|
+
meta={hasInstructions ? `${instructions.length.toLocaleString()} characters` : "Not set"}
|
|
49
|
+
>
|
|
50
|
+
{hasInstructions ? (
|
|
51
|
+
<button
|
|
52
|
+
type="button"
|
|
53
|
+
onClick={() => setOpen(true)}
|
|
54
|
+
className="group flex w-full flex-col gap-2 px-3.5 py-3 text-left transition-colors hover:bg-muted/40 focus-visible:bg-muted/40 focus-visible:outline-none"
|
|
55
|
+
>
|
|
56
|
+
<span className="line-clamp-4 text-[13px] leading-relaxed whitespace-pre-line text-foreground/85">
|
|
57
|
+
{instructions}
|
|
58
|
+
</span>
|
|
59
|
+
<span className="flex items-center gap-1.5 text-xs font-medium text-muted-foreground group-hover:text-foreground">
|
|
60
|
+
<PencilIcon className="size-3.5" aria-hidden />
|
|
61
|
+
Edit
|
|
62
|
+
</span>
|
|
63
|
+
</button>
|
|
64
|
+
) : (
|
|
65
|
+
<PanelBody className="flex flex-col items-start gap-2.5">
|
|
66
|
+
<p className="text-xs leading-relaxed text-muted-foreground">
|
|
67
|
+
Tell the agent how to work in this project, like the language to reply in or tools to
|
|
68
|
+
prefer.
|
|
69
|
+
</p>
|
|
70
|
+
<Button type="button" variant="outline" size="sm" onClick={() => setOpen(true)}>
|
|
71
|
+
<PlusIcon data-icon="inline-start" />
|
|
72
|
+
Add instructions
|
|
73
|
+
</Button>
|
|
74
|
+
</PanelBody>
|
|
75
|
+
)}
|
|
76
|
+
|
|
77
|
+
<InstructionsDialog
|
|
78
|
+
open={open}
|
|
79
|
+
onOpenChange={setOpen}
|
|
80
|
+
project={project}
|
|
81
|
+
onSaved={(p) => {
|
|
82
|
+
onSaved(p);
|
|
83
|
+
setOpen(false);
|
|
84
|
+
}}
|
|
85
|
+
/>
|
|
86
|
+
</PanelSection>
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function InstructionsDialog({
|
|
91
|
+
open,
|
|
92
|
+
onOpenChange,
|
|
93
|
+
project,
|
|
94
|
+
onSaved,
|
|
95
|
+
}: {
|
|
96
|
+
open: boolean;
|
|
97
|
+
onOpenChange: (open: boolean) => void;
|
|
98
|
+
project: Project;
|
|
99
|
+
onSaved: (project: Project) => void;
|
|
100
|
+
}) {
|
|
101
|
+
const fieldId = useId();
|
|
102
|
+
const hintId = useId();
|
|
103
|
+
const [draft, setDraft] = useState(project.instructions ?? "");
|
|
104
|
+
const [saving, setSaving] = useState(false);
|
|
105
|
+
const [error, setError] = useState<string | null>(null);
|
|
106
|
+
|
|
107
|
+
// Reset draft tiap dialog dibuka — batal edit tidak meninggalkan sisa.
|
|
108
|
+
useEffect(() => {
|
|
109
|
+
if (!open) return;
|
|
110
|
+
setDraft(project.instructions ?? "");
|
|
111
|
+
setError(null);
|
|
112
|
+
}, [open, project.instructions]);
|
|
113
|
+
|
|
114
|
+
const tooLong = draft.length > MAX_LENGTH;
|
|
115
|
+
const unchanged = draft.trim() === (project.instructions ?? "");
|
|
116
|
+
|
|
117
|
+
const save = async () => {
|
|
118
|
+
setSaving(true);
|
|
119
|
+
setError(null);
|
|
120
|
+
try {
|
|
121
|
+
const res = await apiFetch(`/api/projects/${project.id}`, {
|
|
122
|
+
method: "PATCH",
|
|
123
|
+
body: JSON.stringify({ instructions: draft }),
|
|
124
|
+
});
|
|
125
|
+
const body = (await res.json()) as { project: Project };
|
|
126
|
+
onSaved(body.project);
|
|
127
|
+
toast.success(body.project.instructions ? "Instructions saved." : "Instructions cleared.");
|
|
128
|
+
} catch (e) {
|
|
129
|
+
setError(e instanceof ApiError ? e.message : "Failed to save instructions");
|
|
130
|
+
} finally {
|
|
131
|
+
setSaving(false);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<Dialog open={open} onOpenChange={(o) => !saving && onOpenChange(o)}>
|
|
137
|
+
<DialogContent className="sm:max-w-xl">
|
|
138
|
+
<DialogHeader>
|
|
139
|
+
<DialogTitle>Custom instructions</DialogTitle>
|
|
140
|
+
<DialogDescription>
|
|
141
|
+
Sent to the agent with every prompt in {project.name}, on top of AGENTS.md. Changes
|
|
142
|
+
apply from the next message.
|
|
143
|
+
</DialogDescription>
|
|
144
|
+
</DialogHeader>
|
|
145
|
+
|
|
146
|
+
<form
|
|
147
|
+
id={`${fieldId}-form`}
|
|
148
|
+
onSubmit={(e) => {
|
|
149
|
+
e.preventDefault();
|
|
150
|
+
if (!tooLong && !unchanged) void save();
|
|
151
|
+
}}
|
|
152
|
+
className="flex flex-col gap-2"
|
|
153
|
+
>
|
|
154
|
+
<label htmlFor={fieldId} className="sr-only">
|
|
155
|
+
Instructions
|
|
156
|
+
</label>
|
|
157
|
+
<Textarea
|
|
158
|
+
id={fieldId}
|
|
159
|
+
value={draft}
|
|
160
|
+
onChange={(e) => setDraft(e.target.value)}
|
|
161
|
+
onKeyDown={(e) => {
|
|
162
|
+
// Ctrl/Cmd+Enter = simpan, seperti editor pada umumnya.
|
|
163
|
+
if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) {
|
|
164
|
+
e.preventDefault();
|
|
165
|
+
e.currentTarget.form?.requestSubmit();
|
|
166
|
+
}
|
|
167
|
+
}}
|
|
168
|
+
placeholder={
|
|
169
|
+
"e.g.\n- Reply in Bahasa Indonesia\n- Use Bun, not Node\n- Keep commits small"
|
|
170
|
+
}
|
|
171
|
+
aria-describedby={hintId}
|
|
172
|
+
aria-invalid={tooLong || undefined}
|
|
173
|
+
disabled={saving}
|
|
174
|
+
className="max-h-[50dvh] min-h-40 font-mono text-sm leading-relaxed"
|
|
175
|
+
autoFocus
|
|
176
|
+
/>
|
|
177
|
+
<p
|
|
178
|
+
id={hintId}
|
|
179
|
+
className={cn(
|
|
180
|
+
"text-right text-xs tabular-nums text-muted-foreground",
|
|
181
|
+
tooLong && "text-destructive",
|
|
182
|
+
)}
|
|
183
|
+
>
|
|
184
|
+
{draft.length.toLocaleString()} / {MAX_LENGTH.toLocaleString()}
|
|
185
|
+
</p>
|
|
186
|
+
{error && (
|
|
187
|
+
<Alert variant="destructive">
|
|
188
|
+
<AlertDescription>{error}</AlertDescription>
|
|
189
|
+
</Alert>
|
|
190
|
+
)}
|
|
191
|
+
</form>
|
|
192
|
+
|
|
193
|
+
<DialogFooter className="gap-2 sm:justify-between">
|
|
194
|
+
{project.instructions ? (
|
|
195
|
+
<Button
|
|
196
|
+
type="button"
|
|
197
|
+
variant="ghost"
|
|
198
|
+
className="text-muted-foreground sm:mr-auto"
|
|
199
|
+
disabled={saving}
|
|
200
|
+
onClick={() => setDraft("")}
|
|
201
|
+
>
|
|
202
|
+
Clear
|
|
203
|
+
</Button>
|
|
204
|
+
) : (
|
|
205
|
+
<span className="hidden sm:block" />
|
|
206
|
+
)}
|
|
207
|
+
<div className="flex flex-col-reverse gap-2 sm:flex-row">
|
|
208
|
+
<Button
|
|
209
|
+
type="button"
|
|
210
|
+
variant="outline"
|
|
211
|
+
disabled={saving}
|
|
212
|
+
onClick={() => onOpenChange(false)}
|
|
213
|
+
>
|
|
214
|
+
Cancel
|
|
215
|
+
</Button>
|
|
216
|
+
<Button
|
|
217
|
+
type="submit"
|
|
218
|
+
form={`${fieldId}-form`}
|
|
219
|
+
disabled={saving || tooLong || unchanged}
|
|
220
|
+
>
|
|
221
|
+
{saving && <Spinner data-icon="inline-start" />}
|
|
222
|
+
Save
|
|
223
|
+
</Button>
|
|
224
|
+
</div>
|
|
225
|
+
</DialogFooter>
|
|
226
|
+
</DialogContent>
|
|
227
|
+
</Dialog>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Satu baris Project di homepage. Seluruh baris adalah tombol tunggal (bukan
|
|
3
|
+
* tombol di dalam tombol) agar target sentuh besar di layar HP dan tetap
|
|
4
|
+
* dapat diakses lewat keyboard.
|
|
5
|
+
*/
|
|
6
|
+
import { ArrowRightIcon } from "lucide-react";
|
|
7
|
+
import { formatRelativeTime, type ProjectOverview } from "@/lib/project-overview";
|
|
8
|
+
|
|
9
|
+
export interface ProjectRowProps {
|
|
10
|
+
overview: ProjectOverview;
|
|
11
|
+
onOpen: () => void;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function ProjectRow({ overview, onOpen }: ProjectRowProps) {
|
|
15
|
+
const { project, sessionCount, runningCount, lastSession, lastActivityAt } = overview;
|
|
16
|
+
const hasSession = lastSession !== null;
|
|
17
|
+
|
|
18
|
+
return (
|
|
19
|
+
<button
|
|
20
|
+
type="button"
|
|
21
|
+
onClick={onOpen}
|
|
22
|
+
className="group flex w-full items-center gap-3 rounded-xl border bg-card px-4 py-3 text-left shadow-sm transition-all hover:border-primary/40 hover:bg-accent hover:shadow-md focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-none"
|
|
23
|
+
aria-label={
|
|
24
|
+
hasSession ? `Resume last session of ${project.name}` : `Open project ${project.name}`
|
|
25
|
+
}
|
|
26
|
+
>
|
|
27
|
+
<span className="relative flex size-10 shrink-0 items-center justify-center rounded-lg bg-primary/10 text-base font-semibold text-primary uppercase">
|
|
28
|
+
{project.name.trim().charAt(0) || "?"}
|
|
29
|
+
{runningCount > 0 && (
|
|
30
|
+
<span
|
|
31
|
+
className="absolute -top-0.5 -right-0.5 size-2.5 rounded-full bg-emerald-500 ring-2 ring-card"
|
|
32
|
+
aria-hidden
|
|
33
|
+
/>
|
|
34
|
+
)}
|
|
35
|
+
</span>
|
|
36
|
+
|
|
37
|
+
<span className="flex min-w-0 flex-1 flex-col gap-0.5">
|
|
38
|
+
<span className="truncate text-base font-medium group-hover:text-primary">
|
|
39
|
+
{project.name}
|
|
40
|
+
</span>
|
|
41
|
+
<span className="truncate font-mono text-sm text-muted-foreground">{project.path}</span>
|
|
42
|
+
<span className="truncate text-sm text-muted-foreground">
|
|
43
|
+
{sessionCount === 0
|
|
44
|
+
? "No sessions"
|
|
45
|
+
: `${sessionCount} session${sessionCount === 1 ? "" : "s"}`}
|
|
46
|
+
{runningCount > 0 && ` · ${runningCount} running`}
|
|
47
|
+
{" · "}
|
|
48
|
+
{formatRelativeTime(lastActivityAt)}
|
|
49
|
+
</span>
|
|
50
|
+
</span>
|
|
51
|
+
|
|
52
|
+
<span className="flex shrink-0 items-center gap-1 text-sm font-medium text-muted-foreground group-hover:text-primary">
|
|
53
|
+
<span className="hidden sm:inline">{hasSession ? "Resume" : "Open"}</span>
|
|
54
|
+
<ArrowRightIcon className="size-4 transition-transform group-hover:translate-x-0.5" />
|
|
55
|
+
</span>
|
|
56
|
+
</button>
|
|
57
|
+
);
|
|
58
|
+
}
|