kcg-code 0.0.0-stage → 0.2.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 +230 -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 +84 -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__/daemon.test.ts +84 -0
- package/src/cli/__tests__/dashboard.test.ts +224 -0
- package/src/cli/__tests__/keep-awake.test.ts +44 -0
- package/src/cli/__tests__/port.test.ts +86 -0
- package/src/cli/args.ts +186 -0
- package/src/cli/attach.ts +154 -0
- package/src/cli/commands.ts +327 -0
- package/src/cli/daemon.ts +275 -0
- package/src/cli/dashboard.tsx +1060 -0
- package/src/cli/index.ts +47 -0
- package/src/cli/keep-awake.ts +148 -0
- package/src/cli/port.ts +92 -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 +147 -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 +287 -0
- package/src/components/projects/HomeRecentSessions.tsx +90 -0
- package/src/components/projects/NewProjectDialog.tsx +249 -0
- package/src/components/projects/ProjectInstructions.tsx +218 -0
- package/src/components/projects/ProjectRow.tsx +58 -0
- package/src/components/projects/ProjectSidePanel.tsx +514 -0
- package/src/components/sessions/AgentLogo.tsx +42 -0
- package/src/components/sessions/AgentModeList.tsx +265 -0
- package/src/components/sessions/AgentPicker.tsx +132 -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 +92 -0
- package/src/components/sessions/ContextPanel.tsx +661 -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 +247 -0
- package/src/components/sessions/ModelSearchList.tsx +231 -0
- package/src/components/sessions/NewSessionDialog.tsx +172 -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 +641 -0
- package/src/components/sessions/SessionHeader.tsx +176 -0
- package/src/components/sessions/SessionList.tsx +503 -0
- package/src/components/sessions/SessionRow.tsx +237 -0
- package/src/components/sessions/SessionTimeline.tsx +275 -0
- package/src/components/sessions/SessionTimelineSkeleton.tsx +83 -0
- package/src/components/sessions/SessionView.tsx +240 -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 +256 -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 +257 -0
- package/src/components/settings/PasskeysGroup.tsx +551 -0
- package/src/components/settings/RemoteAccessGroup.tsx +1201 -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 +333 -0
- package/src/components/skills/InstallNotifications.tsx +326 -0
- package/src/components/skills/InstallSkillDialog.tsx +390 -0
- package/src/components/skills/SkillCards.tsx +254 -0
- package/src/components/skills/SkillsProjectPicker.tsx +178 -0
- package/src/components/ui/adw-dialog.tsx +114 -0
- package/src/components/ui/adw-toggle-group.tsx +75 -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/search-entry.tsx +70 -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 +114 -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 +101 -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 +327 -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 +83 -0
- package/src/lib/__tests__/project-overview.test.ts +188 -0
- package/src/lib/__tests__/references.test.ts +121 -0
- package/src/lib/__tests__/routes.test.ts +134 -0
- package/src/lib/__tests__/session-selection.test.ts +41 -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 +135 -0
- package/src/lib/__tests__/skills-catalog.test.ts +124 -0
- package/src/lib/__tests__/tone.test.ts +37 -0
- package/src/lib/__tests__/turns-delta.test.ts +55 -0
- package/src/lib/agent-types.ts +49 -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 +136 -0
- package/src/lib/project-overview.ts +123 -0
- package/src/lib/prompts.ts +87 -0
- package/src/lib/references.ts +106 -0
- package/src/lib/routes.ts +124 -0
- package/src/lib/session-handoff.ts +25 -0
- package/src/lib/session-selection.ts +40 -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 +362 -0
- package/src/lib/skills-catalog.ts +138 -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 +131 -0
- package/src/pages/projects/ProjectsPage.tsx +97 -0
- package/src/pages/sessions/SessionPage.tsx +122 -0
- package/src/pages/settings/SettingsPage.tsx +366 -0
- package/src/pages/settings/__tests__/SettingsPage.test.ts +16 -0
- package/src/pages/skills/SkillsPage.tsx +745 -0
- package/src/paths.ts +121 -0
- package/src/runtime.ts +24 -0
- package/src/server/__tests__/app.test.ts +1044 -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 +427 -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 +167 -0
- package/src/server/routes/sessions.routes.ts +197 -0
- package/src/server/routes/skills.routes.ts +136 -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__/claude-code.test.ts +609 -0
- package/src/server/services/__tests__/claude-mcp.test.ts +90 -0
- package/src/server/services/__tests__/claude-skills.test.ts +63 -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 +3509 -0
- package/src/server/services/__tests__/skill-install-jobs.test.ts +245 -0
- package/src/server/services/__tests__/skills-registry.test.ts +365 -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/claude-code-client.ts +1093 -0
- package/src/server/services/claude-code-translate.ts +370 -0
- package/src/server/services/claude-mcp.ts +122 -0
- package/src/server/services/claude-skills.ts +102 -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 +1439 -0
- package/src/server/services/skill-install-jobs.ts +275 -0
- package/src/server/services/skills-registry.ts +480 -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,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tampilan satu lampiran non-gambar: ikon berwarna sesuai jenis file + nama
|
|
3
|
+
* + keterangan singkat (ekstensi · ukuran). Dipakai composer (lampiran
|
|
4
|
+
* pending & animasi plate) dan bubble pesan user (tautan unduh).
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
FileArchiveIcon,
|
|
8
|
+
FileAudioIcon,
|
|
9
|
+
FileCodeIcon,
|
|
10
|
+
FileIcon,
|
|
11
|
+
FileSpreadsheetIcon,
|
|
12
|
+
FileTextIcon,
|
|
13
|
+
FileVideoIcon,
|
|
14
|
+
ImageIcon,
|
|
15
|
+
type LucideIcon,
|
|
16
|
+
} from "lucide-react";
|
|
17
|
+
import { attachmentKind, fileExtension, formatBytes } from "@/lib/attachments";
|
|
18
|
+
import { cn } from "@/lib/utils";
|
|
19
|
+
|
|
20
|
+
const CODE_EXT = new Set([
|
|
21
|
+
"js",
|
|
22
|
+
"mjs",
|
|
23
|
+
"cjs",
|
|
24
|
+
"jsx",
|
|
25
|
+
"ts",
|
|
26
|
+
"mts",
|
|
27
|
+
"cts",
|
|
28
|
+
"tsx",
|
|
29
|
+
"vue",
|
|
30
|
+
"svelte",
|
|
31
|
+
"astro",
|
|
32
|
+
"py",
|
|
33
|
+
"rb",
|
|
34
|
+
"php",
|
|
35
|
+
"java",
|
|
36
|
+
"kt",
|
|
37
|
+
"kts",
|
|
38
|
+
"scala",
|
|
39
|
+
"go",
|
|
40
|
+
"rs",
|
|
41
|
+
"c",
|
|
42
|
+
"h",
|
|
43
|
+
"cc",
|
|
44
|
+
"cpp",
|
|
45
|
+
"hpp",
|
|
46
|
+
"cs",
|
|
47
|
+
"swift",
|
|
48
|
+
"m",
|
|
49
|
+
"dart",
|
|
50
|
+
"lua",
|
|
51
|
+
"pl",
|
|
52
|
+
"r",
|
|
53
|
+
"jl",
|
|
54
|
+
"ex",
|
|
55
|
+
"exs",
|
|
56
|
+
"erl",
|
|
57
|
+
"hs",
|
|
58
|
+
"clj",
|
|
59
|
+
"sql",
|
|
60
|
+
"sh",
|
|
61
|
+
"bash",
|
|
62
|
+
"zsh",
|
|
63
|
+
"fish",
|
|
64
|
+
"ps1",
|
|
65
|
+
"bat",
|
|
66
|
+
"html",
|
|
67
|
+
"htm",
|
|
68
|
+
"css",
|
|
69
|
+
"scss",
|
|
70
|
+
"sass",
|
|
71
|
+
"less",
|
|
72
|
+
"json",
|
|
73
|
+
"jsonc",
|
|
74
|
+
"yaml",
|
|
75
|
+
"yml",
|
|
76
|
+
"toml",
|
|
77
|
+
"xml",
|
|
78
|
+
"graphql",
|
|
79
|
+
"gql",
|
|
80
|
+
"proto",
|
|
81
|
+
"tf",
|
|
82
|
+
"hcl",
|
|
83
|
+
"nix",
|
|
84
|
+
"svg",
|
|
85
|
+
]);
|
|
86
|
+
const SHEET_EXT = new Set(["csv", "tsv", "xls", "xlsx", "ods", "numbers"]);
|
|
87
|
+
const ARCHIVE_EXT = new Set(["zip", "tar", "gz", "tgz", "bz2", "xz", "7z", "rar", "zst"]);
|
|
88
|
+
|
|
89
|
+
interface Glyph {
|
|
90
|
+
Icon: LucideIcon;
|
|
91
|
+
/** Kelas warna kotak ikon (latar tipis + warna ikon). */
|
|
92
|
+
tone: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function fileGlyph(mime: string, filename: string): Glyph {
|
|
96
|
+
const ext = fileExtension(filename);
|
|
97
|
+
const kind = attachmentKind(mime, filename);
|
|
98
|
+
if (kind === "image")
|
|
99
|
+
return { Icon: ImageIcon, tone: "bg-sky-500/15 text-sky-600 dark:text-sky-400" };
|
|
100
|
+
if (kind === "pdf")
|
|
101
|
+
return { Icon: FileTextIcon, tone: "bg-red-500/15 text-red-600 dark:text-red-400" };
|
|
102
|
+
if (SHEET_EXT.has(ext)) {
|
|
103
|
+
return {
|
|
104
|
+
Icon: FileSpreadsheetIcon,
|
|
105
|
+
tone: "bg-emerald-500/15 text-emerald-600 dark:text-emerald-400",
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (CODE_EXT.has(ext)) {
|
|
109
|
+
return { Icon: FileCodeIcon, tone: "bg-violet-500/15 text-violet-600 dark:text-violet-400" };
|
|
110
|
+
}
|
|
111
|
+
if (ARCHIVE_EXT.has(ext)) {
|
|
112
|
+
return { Icon: FileArchiveIcon, tone: "bg-amber-500/15 text-amber-700 dark:text-amber-400" };
|
|
113
|
+
}
|
|
114
|
+
if (mime.startsWith("audio/")) {
|
|
115
|
+
return { Icon: FileAudioIcon, tone: "bg-pink-500/15 text-pink-600 dark:text-pink-400" };
|
|
116
|
+
}
|
|
117
|
+
if (mime.startsWith("video/")) {
|
|
118
|
+
return { Icon: FileVideoIcon, tone: "bg-indigo-500/15 text-indigo-600 dark:text-indigo-400" };
|
|
119
|
+
}
|
|
120
|
+
if (kind === "text") return { Icon: FileTextIcon, tone: "bg-muted text-muted-foreground" };
|
|
121
|
+
return { Icon: FileIcon, tone: "bg-muted text-muted-foreground" };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/** "PDF · 1.2 MB" — ekstensi (atau "File") + ukuran bila diketahui. */
|
|
125
|
+
export function fileMeta(filename: string, size?: number): string {
|
|
126
|
+
const ext = fileExtension(filename);
|
|
127
|
+
const label = ext ? ext.toUpperCase() : "File";
|
|
128
|
+
return size !== undefined && size > 0 ? `${label} · ${formatBytes(size)}` : label;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function FileTile({
|
|
132
|
+
filename,
|
|
133
|
+
mime,
|
|
134
|
+
size,
|
|
135
|
+
className,
|
|
136
|
+
}: {
|
|
137
|
+
filename: string;
|
|
138
|
+
mime: string;
|
|
139
|
+
size?: number;
|
|
140
|
+
className?: string;
|
|
141
|
+
}) {
|
|
142
|
+
const { Icon, tone } = fileGlyph(mime, filename);
|
|
143
|
+
return (
|
|
144
|
+
<span
|
|
145
|
+
className={cn(
|
|
146
|
+
"flex min-w-0 items-center gap-2.5 rounded-lg border bg-card px-2.5 text-left",
|
|
147
|
+
className,
|
|
148
|
+
)}
|
|
149
|
+
>
|
|
150
|
+
<span className={cn("flex size-10 shrink-0 items-center justify-center rounded-md", tone)}>
|
|
151
|
+
<Icon className="size-5" aria-hidden />
|
|
152
|
+
</span>
|
|
153
|
+
<span className="flex min-w-0 flex-col">
|
|
154
|
+
<span className="truncate text-[13px] leading-5 font-medium">{filename}</span>
|
|
155
|
+
<span className="truncate text-[11.5px] leading-4 text-muted-foreground">
|
|
156
|
+
{fileMeta(filename, size)}
|
|
157
|
+
</span>
|
|
158
|
+
</span>
|
|
159
|
+
</span>
|
|
160
|
+
);
|
|
161
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blok kode Markdown dengan syntax highlighting (Shiki) + tombol salin.
|
|
3
|
+
*
|
|
4
|
+
* - Teks polos dirender LANGSUNG (tanpa menunggu), lalu diganti token
|
|
5
|
+
* berwarna begitu grammar siap — tidak ada kedipan kosong / layout shift.
|
|
6
|
+
* - Saat streaming, isi blok berubah tiap token baru. Highlight di-debounce
|
|
7
|
+
* singkat; selama itu token lama tetap tampil untuk bagian yang sudah
|
|
8
|
+
* ada dan sisanya teks polos.
|
|
9
|
+
* - Token dirender sebagai `<span>` React (bukan innerHTML), warna lewat CSS
|
|
10
|
+
* variable `--shiki-light` / `--shiki-dark` (lihat `.shiki-code` di
|
|
11
|
+
* `styles/globals.css`).
|
|
12
|
+
*/
|
|
13
|
+
import { CheckIcon, CopyIcon } from "lucide-react";
|
|
14
|
+
import { useEffect, useState } from "react";
|
|
15
|
+
import { type CodeToken, highlightCode, resolveLang } from "@/lib/highlight";
|
|
16
|
+
import { cn } from "@/lib/utils";
|
|
17
|
+
|
|
18
|
+
export interface CodeBlockProps {
|
|
19
|
+
code: string;
|
|
20
|
+
/** Info-string fence (`ts`, `language-tsx`, …). */
|
|
21
|
+
language: string | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Jeda highlight ulang saat isi berubah (streaming). */
|
|
25
|
+
const DEBOUNCE_MS = 120;
|
|
26
|
+
|
|
27
|
+
export function CodeBlock({ code, language }: CodeBlockProps) {
|
|
28
|
+
const lang = resolveLang(language);
|
|
29
|
+
const [tokens, setTokens] = useState<{ code: string; lines: CodeToken[][] } | null>(null);
|
|
30
|
+
const [copied, setCopied] = useState(false);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (!lang) return;
|
|
34
|
+
let cancelled = false;
|
|
35
|
+
const id = setTimeout(() => {
|
|
36
|
+
void highlightCode(code, lang).then((lines) => {
|
|
37
|
+
if (!cancelled && lines) setTokens({ code, lines });
|
|
38
|
+
});
|
|
39
|
+
}, DEBOUNCE_MS);
|
|
40
|
+
return () => {
|
|
41
|
+
cancelled = true;
|
|
42
|
+
clearTimeout(id);
|
|
43
|
+
};
|
|
44
|
+
}, [code, lang]);
|
|
45
|
+
|
|
46
|
+
const copy = async () => {
|
|
47
|
+
try {
|
|
48
|
+
await navigator.clipboard.writeText(code);
|
|
49
|
+
setCopied(true);
|
|
50
|
+
setTimeout(() => setCopied(false), 1500);
|
|
51
|
+
} catch {
|
|
52
|
+
/* Clipboard butuh konteks aman; abaikan diam-diam. */
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Token masih cocok dengan awal kode (streaming menambah di akhir):
|
|
57
|
+
// tampilkan token + sisa teks polos. Isi berubah di tengah -> teks polos.
|
|
58
|
+
const fresh = tokens && code.startsWith(tokens.code) ? tokens : null;
|
|
59
|
+
const tail = fresh ? code.slice(fresh.code.length) : code;
|
|
60
|
+
const label = language?.replace(/^language-/, "") || "text";
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
// Tanpa border: blok dibedakan dari teks hanya lewat latar & sudut besar
|
|
64
|
+
// (ala Gemini). Header tanpa garis pemisah, menyatu dengan isi.
|
|
65
|
+
<div className="my-3 max-w-full overflow-hidden rounded-2xl bg-muted/70 first:mt-0 last:mb-0 dark:bg-muted/50">
|
|
66
|
+
<div className="flex items-center justify-between gap-2 pt-4 pr-4 pl-6 sm:pl-7">
|
|
67
|
+
<span className="text-[13px] font-medium text-foreground/80 uppercase">{label}</span>
|
|
68
|
+
<button
|
|
69
|
+
type="button"
|
|
70
|
+
onClick={() => void copy()}
|
|
71
|
+
aria-label={copied ? "Copied" : "Copy code"}
|
|
72
|
+
title={copied ? "Copied" : "Copy code"}
|
|
73
|
+
className="flex size-8 items-center justify-center rounded-full text-muted-foreground transition-colors outline-none hover:bg-foreground/10 hover:text-foreground focus-visible:ring-[3px] focus-visible:ring-ring/40"
|
|
74
|
+
>
|
|
75
|
+
{copied ? (
|
|
76
|
+
<CheckIcon className="size-4" aria-hidden />
|
|
77
|
+
) : (
|
|
78
|
+
<CopyIcon className="size-4" aria-hidden />
|
|
79
|
+
)}
|
|
80
|
+
</button>
|
|
81
|
+
</div>
|
|
82
|
+
<pre
|
|
83
|
+
className={cn(
|
|
84
|
+
// Padding lega, line-height rapat (1.45) agar kode padat terbaca.
|
|
85
|
+
"overflow-x-auto px-6 pt-3 pb-6 font-mono sm:px-7 text-[14px] leading-[1.45]",
|
|
86
|
+
fresh && "shiki-code",
|
|
87
|
+
)}
|
|
88
|
+
>
|
|
89
|
+
<code>
|
|
90
|
+
{fresh?.lines.map((line, i) => (
|
|
91
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: baris = posisi dalam kode
|
|
92
|
+
<span key={i}>
|
|
93
|
+
{line.map((t, j) => (
|
|
94
|
+
// biome-ignore lint/suspicious/noArrayIndexKey: token = posisi dalam baris
|
|
95
|
+
<span key={j} style={t.style}>
|
|
96
|
+
{t.content}
|
|
97
|
+
</span>
|
|
98
|
+
))}
|
|
99
|
+
{i < fresh.lines.length - 1 ? "\n" : ""}
|
|
100
|
+
</span>
|
|
101
|
+
))}
|
|
102
|
+
{tail}
|
|
103
|
+
</code>
|
|
104
|
+
</pre>
|
|
105
|
+
</div>
|
|
106
|
+
);
|
|
107
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sheet aksi mobile di composer (lampirkan file + ganti agent mode).
|
|
3
|
+
*
|
|
4
|
+
* Susunan: judul kecil "Add to message" + aksi lampiran sebagai baris
|
|
5
|
+
* (ikon, label, keterangan) — lalu grup "Mode" berisi pilihan agent ala
|
|
6
|
+
* radio. Mode pembantu (compaction/title/summary) dilipat di "More modes".
|
|
7
|
+
*/
|
|
8
|
+
import { ChevronRightIcon, PaperclipIcon } from "lucide-react";
|
|
9
|
+
import { AgentModeList } from "@/components/sessions/AgentModeList";
|
|
10
|
+
import { SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet";
|
|
11
|
+
import type { UseAgentPickerResult } from "@/hooks/useAgentPicker";
|
|
12
|
+
|
|
13
|
+
export interface ComposerActionSheetProps {
|
|
14
|
+
/** Input tidak boleh dipakai (Session mati / sibuk) — aksi ikut terkunci. */
|
|
15
|
+
disabled: boolean;
|
|
16
|
+
/** Buka file picker (sheet ditutup oleh pemanggil). */
|
|
17
|
+
onPickImage: () => void;
|
|
18
|
+
agentPicker: UseAgentPickerResult;
|
|
19
|
+
activeAgent: string | null;
|
|
20
|
+
onPickAgent: (name: string | null) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function ComposerActionSheet({
|
|
24
|
+
disabled,
|
|
25
|
+
onPickImage,
|
|
26
|
+
agentPicker,
|
|
27
|
+
activeAgent,
|
|
28
|
+
onPickAgent,
|
|
29
|
+
}: ComposerActionSheetProps) {
|
|
30
|
+
return (
|
|
31
|
+
<SheetContent
|
|
32
|
+
side="bottom"
|
|
33
|
+
className="max-h-[80dvh] gap-0 pb-[max(0.75rem,env(safe-area-inset-bottom))]"
|
|
34
|
+
>
|
|
35
|
+
<SheetHeader className="sr-only">
|
|
36
|
+
<SheetTitle>Message options</SheetTitle>
|
|
37
|
+
<SheetDescription>Attach files or choose how the agent works.</SheetDescription>
|
|
38
|
+
</SheetHeader>
|
|
39
|
+
|
|
40
|
+
<div className="flex min-h-0 flex-col gap-4 overflow-y-auto overscroll-contain px-3 pt-1 pb-1">
|
|
41
|
+
{/* Lampiran */}
|
|
42
|
+
<section aria-labelledby="sheet-attach" className="flex flex-col gap-1">
|
|
43
|
+
<h3 id="sheet-attach" className="px-3 pb-1 text-xs font-medium text-muted-foreground">
|
|
44
|
+
Add to message
|
|
45
|
+
</h3>
|
|
46
|
+
<button
|
|
47
|
+
type="button"
|
|
48
|
+
disabled={disabled}
|
|
49
|
+
onClick={onPickImage}
|
|
50
|
+
className="flex min-h-14 w-full items-center gap-3 rounded-xl px-3 py-2.5 text-left transition-colors outline-none hover:bg-muted/60 focus-visible:ring-[3px] focus-visible:ring-ring/50 active:bg-muted disabled:opacity-50"
|
|
51
|
+
>
|
|
52
|
+
<span
|
|
53
|
+
className="flex size-9 shrink-0 items-center justify-center rounded-lg bg-sky-500/10 text-sky-600 dark:text-sky-400"
|
|
54
|
+
aria-hidden
|
|
55
|
+
>
|
|
56
|
+
<PaperclipIcon className="size-[18px]" />
|
|
57
|
+
</span>
|
|
58
|
+
<span className="flex min-w-0 flex-1 flex-col">
|
|
59
|
+
<span className="text-[15px] leading-5">Photos and files</span>
|
|
60
|
+
<span className="text-[13px] leading-snug text-muted-foreground">
|
|
61
|
+
Images, documents, code, and more
|
|
62
|
+
</span>
|
|
63
|
+
</span>
|
|
64
|
+
<ChevronRightIcon className="size-4 shrink-0 text-muted-foreground" aria-hidden />
|
|
65
|
+
</button>
|
|
66
|
+
</section>
|
|
67
|
+
|
|
68
|
+
<div className="mx-3 border-t" />
|
|
69
|
+
|
|
70
|
+
{/* Mode agent */}
|
|
71
|
+
<section aria-labelledby="sheet-mode" className="flex flex-col gap-1">
|
|
72
|
+
<div className="flex flex-col px-3 pb-1">
|
|
73
|
+
<h3 id="sheet-mode" className="text-xs font-medium text-muted-foreground">
|
|
74
|
+
Mode
|
|
75
|
+
</h3>
|
|
76
|
+
<p className="text-[12px] text-muted-foreground/80">
|
|
77
|
+
How the agent handles your next message
|
|
78
|
+
</p>
|
|
79
|
+
</div>
|
|
80
|
+
<div>
|
|
81
|
+
<AgentModeList
|
|
82
|
+
variant="sheet"
|
|
83
|
+
agents={agentPicker.agents}
|
|
84
|
+
loading={agentPicker.loading}
|
|
85
|
+
error={agentPicker.error}
|
|
86
|
+
activeAgent={activeAgent}
|
|
87
|
+
saving={agentPicker.saving}
|
|
88
|
+
disabled={disabled}
|
|
89
|
+
onPick={onPickAgent}
|
|
90
|
+
/>
|
|
91
|
+
</div>
|
|
92
|
+
</section>
|
|
93
|
+
</div>
|
|
94
|
+
</SheetContent>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Konfirmasi hapus Project (beserta seluruh Session-nya) dari halaman daftar
|
|
3
|
+
* Session. Server membersihkan Session terlebih dahulu; direktori kerja di
|
|
4
|
+
* filesystem tidak disentuh.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
AlertDialog,
|
|
9
|
+
AlertDialogAction,
|
|
10
|
+
AlertDialogCancel,
|
|
11
|
+
AlertDialogContent,
|
|
12
|
+
AlertDialogDescription,
|
|
13
|
+
AlertDialogFooter,
|
|
14
|
+
AlertDialogHeader,
|
|
15
|
+
AlertDialogTitle,
|
|
16
|
+
} from "@/components/ui/alert-dialog";
|
|
17
|
+
import { Spinner } from "@/components/ui/spinner";
|
|
18
|
+
|
|
19
|
+
export interface ConfirmDeleteProjectDialogProps {
|
|
20
|
+
open: boolean;
|
|
21
|
+
/** Tutup dialog (ditolak bila aksi sedang berjalan). */
|
|
22
|
+
onOpenChange: (open: boolean) => void;
|
|
23
|
+
projectName: string;
|
|
24
|
+
/** Jumlah Session milik project — disertakan ke teks peringatan. */
|
|
25
|
+
sessionTotal: number;
|
|
26
|
+
deleting: boolean;
|
|
27
|
+
/** Jalankan penghapusan Project. */
|
|
28
|
+
onConfirm: () => void;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function ConfirmDeleteProjectDialog({
|
|
32
|
+
open,
|
|
33
|
+
onOpenChange,
|
|
34
|
+
projectName,
|
|
35
|
+
sessionTotal,
|
|
36
|
+
deleting,
|
|
37
|
+
onConfirm,
|
|
38
|
+
}: ConfirmDeleteProjectDialogProps) {
|
|
39
|
+
return (
|
|
40
|
+
<AlertDialog
|
|
41
|
+
open={open}
|
|
42
|
+
onOpenChange={(next) => {
|
|
43
|
+
if (!deleting) onOpenChange(next);
|
|
44
|
+
}}
|
|
45
|
+
>
|
|
46
|
+
<AlertDialogContent>
|
|
47
|
+
<AlertDialogHeader>
|
|
48
|
+
<AlertDialogTitle>Delete project “{projectName}”?</AlertDialogTitle>
|
|
49
|
+
<AlertDialogDescription>
|
|
50
|
+
{sessionTotal > 0
|
|
51
|
+
? `${sessionTotal} session${sessionTotal === 1 ? "" : "s"} of this project will also be permanently deleted, including their conversation history on the opencode server. `
|
|
52
|
+
: ""}
|
|
53
|
+
The working folder on the server is not deleted — only the project registration in KCG
|
|
54
|
+
Code. This action cannot be undone.
|
|
55
|
+
</AlertDialogDescription>
|
|
56
|
+
</AlertDialogHeader>
|
|
57
|
+
<AlertDialogFooter>
|
|
58
|
+
<AlertDialogCancel disabled={deleting}>Cancel</AlertDialogCancel>
|
|
59
|
+
<AlertDialogAction
|
|
60
|
+
variant="destructive"
|
|
61
|
+
onClick={(e) => {
|
|
62
|
+
e.preventDefault();
|
|
63
|
+
onConfirm();
|
|
64
|
+
}}
|
|
65
|
+
disabled={deleting}
|
|
66
|
+
>
|
|
67
|
+
{deleting ? (
|
|
68
|
+
<>
|
|
69
|
+
<Spinner data-icon="inline-start" />
|
|
70
|
+
Deleting…
|
|
71
|
+
</>
|
|
72
|
+
) : (
|
|
73
|
+
"Delete project"
|
|
74
|
+
)}
|
|
75
|
+
</AlertDialogAction>
|
|
76
|
+
</AlertDialogFooter>
|
|
77
|
+
</AlertDialogContent>
|
|
78
|
+
</AlertDialog>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Konfirmasi hapus Session permanen (dipakai header chat `SessionView`, menu
|
|
3
|
+
* baris `SessionList`, dan hapus massal selection mode). Menghapus juga
|
|
4
|
+
* riwayat percakapan di agent (opencode / Claude Code) — tidak bisa
|
|
5
|
+
* dibatalkan. `count` > 1 = hapus massal; `progress` menampilkan kemajuan.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { useState } from "react";
|
|
9
|
+
import {
|
|
10
|
+
AlertDialog,
|
|
11
|
+
AlertDialogAction,
|
|
12
|
+
AlertDialogCancel,
|
|
13
|
+
AlertDialogContent,
|
|
14
|
+
AlertDialogDescription,
|
|
15
|
+
AlertDialogFooter,
|
|
16
|
+
AlertDialogHeader,
|
|
17
|
+
AlertDialogTitle,
|
|
18
|
+
} from "@/components/ui/alert-dialog";
|
|
19
|
+
import { Spinner } from "@/components/ui/spinner";
|
|
20
|
+
import { pluralSessions } from "@/lib/session-selection";
|
|
21
|
+
|
|
22
|
+
export interface ConfirmSessionDeleteDialogProps {
|
|
23
|
+
open: boolean;
|
|
24
|
+
/** Tutup dialog (ditolak bila aksi sedang berjalan). */
|
|
25
|
+
onOpenChange: (open: boolean) => void;
|
|
26
|
+
deleting: boolean;
|
|
27
|
+
/** Jalankan penghapusan (engine). */
|
|
28
|
+
onConfirm: () => void;
|
|
29
|
+
/** Jumlah Session yang dihapus (default 1). */
|
|
30
|
+
count?: number;
|
|
31
|
+
/** Kemajuan hapus massal. */
|
|
32
|
+
progress?: { done: number; total: number } | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function ConfirmSessionDeleteDialog({
|
|
36
|
+
open,
|
|
37
|
+
onOpenChange,
|
|
38
|
+
deleting,
|
|
39
|
+
onConfirm,
|
|
40
|
+
count = 1,
|
|
41
|
+
progress = null,
|
|
42
|
+
}: ConfirmSessionDeleteDialogProps) {
|
|
43
|
+
// Jumlah dibekukan selama animasi tutup: setelah hapus massal pilihan
|
|
44
|
+
// sudah kosong, tapi teks dialog yang memudar tidak boleh berganti.
|
|
45
|
+
const [shownCount, setShownCount] = useState(count);
|
|
46
|
+
if (open && count !== shownCount) setShownCount(count);
|
|
47
|
+
const many = shownCount > 1;
|
|
48
|
+
return (
|
|
49
|
+
<AlertDialog
|
|
50
|
+
open={open}
|
|
51
|
+
onOpenChange={(next) => {
|
|
52
|
+
if (!deleting) onOpenChange(next);
|
|
53
|
+
}}
|
|
54
|
+
>
|
|
55
|
+
<AlertDialogContent>
|
|
56
|
+
<AlertDialogHeader>
|
|
57
|
+
<AlertDialogTitle>
|
|
58
|
+
{many ? `Delete ${pluralSessions(shownCount)}?` : "Delete this session?"}
|
|
59
|
+
</AlertDialogTitle>
|
|
60
|
+
<AlertDialogDescription>
|
|
61
|
+
{many ? "Their conversation histories" : "The conversation history"} will also be
|
|
62
|
+
permanently deleted from the agent. This action cannot be undone.
|
|
63
|
+
</AlertDialogDescription>
|
|
64
|
+
</AlertDialogHeader>
|
|
65
|
+
<AlertDialogFooter>
|
|
66
|
+
<AlertDialogCancel disabled={deleting}>Cancel</AlertDialogCancel>
|
|
67
|
+
<AlertDialogAction
|
|
68
|
+
variant="destructive"
|
|
69
|
+
onClick={(e) => {
|
|
70
|
+
e.preventDefault();
|
|
71
|
+
onConfirm();
|
|
72
|
+
}}
|
|
73
|
+
disabled={deleting}
|
|
74
|
+
>
|
|
75
|
+
{deleting ? (
|
|
76
|
+
<>
|
|
77
|
+
<Spinner data-icon="inline-start" />
|
|
78
|
+
{progress && progress.total > 1
|
|
79
|
+
? `Deleting ${Math.min(progress.done + 1, progress.total)} of ${progress.total}…`
|
|
80
|
+
: "Deleting…"}
|
|
81
|
+
</>
|
|
82
|
+
) : many ? (
|
|
83
|
+
`Delete ${pluralSessions(shownCount)}`
|
|
84
|
+
) : (
|
|
85
|
+
"Delete permanently"
|
|
86
|
+
)}
|
|
87
|
+
</AlertDialogAction>
|
|
88
|
+
</AlertDialogFooter>
|
|
89
|
+
</AlertDialogContent>
|
|
90
|
+
</AlertDialog>
|
|
91
|
+
);
|
|
92
|
+
}
|