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.
Files changed (307) hide show
  1. package/README.md +230 -2
  2. package/bin/kcgcode.ts +13 -0
  3. package/bun-env.d.ts +19 -0
  4. package/kcg-code.config.example.json +3 -0
  5. package/package.json +84 -4
  6. package/public/logo.svg +1 -0
  7. package/public/manifest.json +18 -0
  8. package/public/sw.js +49 -0
  9. package/src/App.tsx +39 -0
  10. package/src/__tests__/manifest.test.ts +61 -0
  11. package/src/build-config.ts +9 -0
  12. package/src/cli/__tests__/daemon.test.ts +84 -0
  13. package/src/cli/__tests__/dashboard.test.ts +224 -0
  14. package/src/cli/__tests__/keep-awake.test.ts +44 -0
  15. package/src/cli/__tests__/port.test.ts +86 -0
  16. package/src/cli/args.ts +186 -0
  17. package/src/cli/attach.ts +154 -0
  18. package/src/cli/commands.ts +327 -0
  19. package/src/cli/daemon.ts +275 -0
  20. package/src/cli/dashboard.tsx +1060 -0
  21. package/src/cli/index.ts +47 -0
  22. package/src/cli/keep-awake.ts +148 -0
  23. package/src/cli/port.ts +92 -0
  24. package/src/cli/theme.ts +100 -0
  25. package/src/components/auth/AuthGate.tsx +182 -0
  26. package/src/components/auth/AvatarEditor.tsx +236 -0
  27. package/src/components/auth/LockScreen.tsx +714 -0
  28. package/src/components/auth/ProfileAvatar.tsx +60 -0
  29. package/src/components/auth/WelcomeScreen.tsx +672 -0
  30. package/src/components/auth/__tests__/WelcomeScreen.test.tsx +42 -0
  31. package/src/components/auth/avatar-icons.tsx +114 -0
  32. package/src/components/layout/AppShell.tsx +147 -0
  33. package/src/components/layout/AppSidebar.tsx +419 -0
  34. package/src/components/layout/SearchDialog.tsx +189 -0
  35. package/src/components/layout/SidebarAccount.tsx +111 -0
  36. package/src/components/layout/SidebarSessionMenu.tsx +163 -0
  37. package/src/components/layout/ThemeToggle.tsx +67 -0
  38. package/src/components/projects/FolderBrowser.tsx +142 -0
  39. package/src/components/projects/HomeChat.tsx +287 -0
  40. package/src/components/projects/HomeRecentSessions.tsx +90 -0
  41. package/src/components/projects/NewProjectDialog.tsx +249 -0
  42. package/src/components/projects/ProjectInstructions.tsx +218 -0
  43. package/src/components/projects/ProjectRow.tsx +58 -0
  44. package/src/components/projects/ProjectSidePanel.tsx +514 -0
  45. package/src/components/sessions/AgentLogo.tsx +42 -0
  46. package/src/components/sessions/AgentModeList.tsx +265 -0
  47. package/src/components/sessions/AgentPicker.tsx +132 -0
  48. package/src/components/sessions/AssistantTurn.tsx +145 -0
  49. package/src/components/sessions/AttachmentTile.tsx +161 -0
  50. package/src/components/sessions/CodeBlock.tsx +107 -0
  51. package/src/components/sessions/ComposerActionSheet.tsx +96 -0
  52. package/src/components/sessions/ConfirmDeleteProjectDialog.tsx +80 -0
  53. package/src/components/sessions/ConfirmSessionDeleteDialog.tsx +92 -0
  54. package/src/components/sessions/ContextPanel.tsx +661 -0
  55. package/src/components/sessions/DropPlate.tsx +165 -0
  56. package/src/components/sessions/FileDropOverlay.tsx +232 -0
  57. package/src/components/sessions/FileMentionDropdown.tsx +47 -0
  58. package/src/components/sessions/ImageLightbox.tsx +173 -0
  59. package/src/components/sessions/MarkdownContent.tsx +187 -0
  60. package/src/components/sessions/Mascot.tsx +47 -0
  61. package/src/components/sessions/MessageActions.tsx +60 -0
  62. package/src/components/sessions/MessageRail.tsx +121 -0
  63. package/src/components/sessions/ModelPicker.tsx +247 -0
  64. package/src/components/sessions/ModelSearchList.tsx +231 -0
  65. package/src/components/sessions/NewSessionDialog.tsx +172 -0
  66. package/src/components/sessions/PendingAttachments.tsx +127 -0
  67. package/src/components/sessions/PromptCard.tsx +1085 -0
  68. package/src/components/sessions/PromptPanel.tsx +112 -0
  69. package/src/components/sessions/SessionComposer.tsx +641 -0
  70. package/src/components/sessions/SessionHeader.tsx +176 -0
  71. package/src/components/sessions/SessionList.tsx +503 -0
  72. package/src/components/sessions/SessionRow.tsx +237 -0
  73. package/src/components/sessions/SessionTimeline.tsx +275 -0
  74. package/src/components/sessions/SessionTimelineSkeleton.tsx +83 -0
  75. package/src/components/sessions/SessionView.tsx +240 -0
  76. package/src/components/sessions/ThoughtProcessBlock.tsx +199 -0
  77. package/src/components/sessions/UserMessage.tsx +177 -0
  78. package/src/components/sessions/__tests__/ContextPanel.test.tsx +256 -0
  79. package/src/components/sessions/__tests__/MarkdownContent.test.tsx +89 -0
  80. package/src/components/sessions/__tests__/MessageRail.test.tsx +40 -0
  81. package/src/components/sessions/__tests__/PermissionCard.test.tsx +79 -0
  82. package/src/components/sessions/__tests__/PromptCard.test.ts +166 -0
  83. package/src/components/sessions/__tests__/QuestionCard.test.tsx +166 -0
  84. package/src/components/sessions/__tests__/SessionTimelineSkeleton.test.tsx +32 -0
  85. package/src/components/sessions/__tests__/SessionView.test.ts +386 -0
  86. package/src/components/settings/AboutGroup.tsx +170 -0
  87. package/src/components/settings/AccountGroup.tsx +122 -0
  88. package/src/components/settings/DevicesGroup.tsx +151 -0
  89. package/src/components/settings/LhrRemoteAccess.tsx +257 -0
  90. package/src/components/settings/PasskeysGroup.tsx +551 -0
  91. package/src/components/settings/RemoteAccessGroup.tsx +1201 -0
  92. package/src/components/settings/SecurityGroup.tsx +566 -0
  93. package/src/components/settings/WorkspaceGroup.tsx +370 -0
  94. package/src/components/settings/__tests__/AboutGroup.test.tsx +47 -0
  95. package/src/components/settings/__tests__/PasskeysGroup.test.tsx +109 -0
  96. package/src/components/settings/__tests__/WorkspaceGroup.test.tsx +49 -0
  97. package/src/components/settings/prefs.tsx +333 -0
  98. package/src/components/skills/InstallNotifications.tsx +326 -0
  99. package/src/components/skills/InstallSkillDialog.tsx +390 -0
  100. package/src/components/skills/SkillCards.tsx +254 -0
  101. package/src/components/skills/SkillsProjectPicker.tsx +178 -0
  102. package/src/components/ui/adw-dialog.tsx +114 -0
  103. package/src/components/ui/adw-toggle-group.tsx +75 -0
  104. package/src/components/ui/alert-dialog.tsx +179 -0
  105. package/src/components/ui/alert.tsx +60 -0
  106. package/src/components/ui/badge.tsx +46 -0
  107. package/src/components/ui/bubble.tsx +125 -0
  108. package/src/components/ui/button.tsx +62 -0
  109. package/src/components/ui/card.tsx +75 -0
  110. package/src/components/ui/collapsible.tsx +19 -0
  111. package/src/components/ui/dialog.tsx +142 -0
  112. package/src/components/ui/dropdown-menu.tsx +225 -0
  113. package/src/components/ui/empty.tsx +94 -0
  114. package/src/components/ui/field.tsx +234 -0
  115. package/src/components/ui/input-group.tsx +161 -0
  116. package/src/components/ui/input.tsx +21 -0
  117. package/src/components/ui/label.tsx +19 -0
  118. package/src/components/ui/message-scroller.tsx +131 -0
  119. package/src/components/ui/message.tsx +85 -0
  120. package/src/components/ui/popover.tsx +46 -0
  121. package/src/components/ui/search-entry.tsx +70 -0
  122. package/src/components/ui/select.tsx +172 -0
  123. package/src/components/ui/separator.tsx +28 -0
  124. package/src/components/ui/sheet.tsx +138 -0
  125. package/src/components/ui/sidebar.tsx +699 -0
  126. package/src/components/ui/skeleton.tsx +15 -0
  127. package/src/components/ui/sonner.tsx +45 -0
  128. package/src/components/ui/spinner.tsx +16 -0
  129. package/src/components/ui/switch.tsx +32 -0
  130. package/src/components/ui/textarea.tsx +18 -0
  131. package/src/components/ui/tooltip.tsx +51 -0
  132. package/src/config.ts +114 -0
  133. package/src/db.ts +146 -0
  134. package/src/frontend.tsx +51 -0
  135. package/src/hooks/__tests__/useMention.test.ts +79 -0
  136. package/src/hooks/__tests__/useResyncOnReconnect.test.ts +128 -0
  137. package/src/hooks/__tests__/useRouter.test.ts +17 -0
  138. package/src/hooks/__tests__/useSmoothText.react.test.ts +194 -0
  139. package/src/hooks/__tests__/useSmoothText.test.ts +61 -0
  140. package/src/hooks/__tests__/useTheme.test.ts +118 -0
  141. package/src/hooks/use-mobile.ts +16 -0
  142. package/src/hooks/useAgentPicker.ts +101 -0
  143. package/src/hooks/useMediaQuery.ts +49 -0
  144. package/src/hooks/useMention.ts +236 -0
  145. package/src/hooks/useProjects.ts +88 -0
  146. package/src/hooks/useResyncOnReconnect.ts +28 -0
  147. package/src/hooks/useRouter.ts +92 -0
  148. package/src/hooks/useSessionChat.ts +427 -0
  149. package/src/hooks/useSessionContext.ts +106 -0
  150. package/src/hooks/useSessionList.ts +327 -0
  151. package/src/hooks/useSidebarData.ts +108 -0
  152. package/src/hooks/useSmoothText.ts +122 -0
  153. package/src/hooks/useTheme.ts +164 -0
  154. package/src/hooks/useWebSocket.ts +196 -0
  155. package/src/index.css +27 -0
  156. package/src/index.html +14 -0
  157. package/src/index.ts +49 -0
  158. package/src/lib/__tests__/attachments.test.ts +58 -0
  159. package/src/lib/__tests__/avatar-presets.test.ts +25 -0
  160. package/src/lib/__tests__/collapsible.test.ts +101 -0
  161. package/src/lib/__tests__/composer.test.ts +118 -0
  162. package/src/lib/__tests__/highlight.test.ts +42 -0
  163. package/src/lib/__tests__/home-chat.test.ts +64 -0
  164. package/src/lib/__tests__/lock-screen.test.ts +64 -0
  165. package/src/lib/__tests__/model-picker.test.ts +205 -0
  166. package/src/lib/__tests__/onboarding.test.ts +41 -0
  167. package/src/lib/__tests__/permission-info.test.ts +61 -0
  168. package/src/lib/__tests__/project-extensions.test.ts +83 -0
  169. package/src/lib/__tests__/project-overview.test.ts +188 -0
  170. package/src/lib/__tests__/references.test.ts +121 -0
  171. package/src/lib/__tests__/routes.test.ts +134 -0
  172. package/src/lib/__tests__/session-selection.test.ts +41 -0
  173. package/src/lib/__tests__/session-summary.test.ts +115 -0
  174. package/src/lib/__tests__/session-title.test.ts +58 -0
  175. package/src/lib/__tests__/sidebar-groups.test.ts +100 -0
  176. package/src/lib/__tests__/skill-installs.test.ts +135 -0
  177. package/src/lib/__tests__/skills-catalog.test.ts +124 -0
  178. package/src/lib/__tests__/tone.test.ts +37 -0
  179. package/src/lib/__tests__/turns-delta.test.ts +55 -0
  180. package/src/lib/agent-types.ts +49 -0
  181. package/src/lib/api.ts +306 -0
  182. package/src/lib/attachments.ts +203 -0
  183. package/src/lib/auth.ts +92 -0
  184. package/src/lib/avatar-presets.ts +31 -0
  185. package/src/lib/collapsible.ts +75 -0
  186. package/src/lib/composer.ts +89 -0
  187. package/src/lib/data-events.ts +17 -0
  188. package/src/lib/headlines.ts +30 -0
  189. package/src/lib/highlight.ts +186 -0
  190. package/src/lib/layout.ts +20 -0
  191. package/src/lib/lock-screen.ts +54 -0
  192. package/src/lib/model-picker.ts +133 -0
  193. package/src/lib/onboarding.ts +38 -0
  194. package/src/lib/passkeys.ts +113 -0
  195. package/src/lib/pending-prompt.ts +53 -0
  196. package/src/lib/permission-info.ts +101 -0
  197. package/src/lib/project-extensions.ts +136 -0
  198. package/src/lib/project-overview.ts +123 -0
  199. package/src/lib/prompts.ts +87 -0
  200. package/src/lib/references.ts +106 -0
  201. package/src/lib/routes.ts +124 -0
  202. package/src/lib/session-handoff.ts +25 -0
  203. package/src/lib/session-selection.ts +40 -0
  204. package/src/lib/session-status.ts +45 -0
  205. package/src/lib/session-summary.ts +68 -0
  206. package/src/lib/session-title.ts +63 -0
  207. package/src/lib/sidebar-groups.ts +78 -0
  208. package/src/lib/skill-installs.ts +362 -0
  209. package/src/lib/skills-catalog.ts +138 -0
  210. package/src/lib/time.ts +22 -0
  211. package/src/lib/tone.ts +80 -0
  212. package/src/lib/turns.ts +320 -0
  213. package/src/lib/utils.ts +6 -0
  214. package/src/logo.svg +31 -0
  215. package/src/pages/projects/ProjectDetailPage.tsx +131 -0
  216. package/src/pages/projects/ProjectsPage.tsx +97 -0
  217. package/src/pages/sessions/SessionPage.tsx +122 -0
  218. package/src/pages/settings/SettingsPage.tsx +366 -0
  219. package/src/pages/settings/__tests__/SettingsPage.test.ts +16 -0
  220. package/src/pages/skills/SkillsPage.tsx +745 -0
  221. package/src/paths.ts +121 -0
  222. package/src/runtime.ts +24 -0
  223. package/src/server/__tests__/app.test.ts +1044 -0
  224. package/src/server/__tests__/auth.test.ts +370 -0
  225. package/src/server/__tests__/config.test.ts +101 -0
  226. package/src/server/__tests__/db.test.ts +485 -0
  227. package/src/server/__tests__/host.test.ts +14 -0
  228. package/src/server/__tests__/tunnel-config.test.ts +38 -0
  229. package/src/server/app.ts +427 -0
  230. package/src/server/db/auth.ts +311 -0
  231. package/src/server/db/messages.ts +50 -0
  232. package/src/server/db/projects.ts +106 -0
  233. package/src/server/db/prompts.ts +78 -0
  234. package/src/server/db/rows.ts +150 -0
  235. package/src/server/db/schema.ts +221 -0
  236. package/src/server/db/sessions.ts +239 -0
  237. package/src/server/db/tunnel.ts +124 -0
  238. package/src/server/host.ts +12 -0
  239. package/src/server/result.ts +15 -0
  240. package/src/server/routes/auth-guard.ts +156 -0
  241. package/src/server/routes/auth.routes.ts +287 -0
  242. package/src/server/routes/helpers.ts +165 -0
  243. package/src/server/routes/meta.routes.ts +24 -0
  244. package/src/server/routes/passkeys.routes.ts +161 -0
  245. package/src/server/routes/projects.routes.ts +167 -0
  246. package/src/server/routes/sessions.routes.ts +197 -0
  247. package/src/server/routes/skills.routes.ts +136 -0
  248. package/src/server/routes/tunnel.routes.ts +132 -0
  249. package/src/server/routes/types.ts +35 -0
  250. package/src/server/routes/uploads.routes.ts +54 -0
  251. package/src/server/routes/workspace.routes.ts +127 -0
  252. package/src/server/services/__tests__/app-info.test.ts +18 -0
  253. package/src/server/services/__tests__/attachments.test.ts +159 -0
  254. package/src/server/services/__tests__/auth-passkeys.test.ts +358 -0
  255. package/src/server/services/__tests__/auth.test.ts +341 -0
  256. package/src/server/services/__tests__/claude-code.test.ts +609 -0
  257. package/src/server/services/__tests__/claude-mcp.test.ts +90 -0
  258. package/src/server/services/__tests__/claude-skills.test.ts +63 -0
  259. package/src/server/services/__tests__/frpc-installer.test.ts +83 -0
  260. package/src/server/services/__tests__/git-info.test.ts +74 -0
  261. package/src/server/services/__tests__/opencode.test.ts +320 -0
  262. package/src/server/services/__tests__/project-manager.test.ts +190 -0
  263. package/src/server/services/__tests__/sandbox.test.ts +141 -0
  264. package/src/server/services/__tests__/session-manager.test.ts +3509 -0
  265. package/src/server/services/__tests__/skill-install-jobs.test.ts +245 -0
  266. package/src/server/services/__tests__/skills-registry.test.ts +365 -0
  267. package/src/server/services/__tests__/tunnel-lhr.test.ts +234 -0
  268. package/src/server/services/__tests__/tunnel.test.ts +367 -0
  269. package/src/server/services/__tests__/websocket-gateway.test.ts +677 -0
  270. package/src/server/services/__tests__/workspace.test.ts +249 -0
  271. package/src/server/services/app-info.ts +29 -0
  272. package/src/server/services/attachments.ts +193 -0
  273. package/src/server/services/auth-passkeys.ts +459 -0
  274. package/src/server/services/auth.ts +633 -0
  275. package/src/server/services/claude-code-client.ts +1093 -0
  276. package/src/server/services/claude-code-translate.ts +370 -0
  277. package/src/server/services/claude-mcp.ts +122 -0
  278. package/src/server/services/claude-skills.ts +102 -0
  279. package/src/server/services/frpc-installer.ts +183 -0
  280. package/src/server/services/git-info.ts +87 -0
  281. package/src/server/services/opencode-client.ts +714 -0
  282. package/src/server/services/opencode-server.ts +319 -0
  283. package/src/server/services/opencode-sse.ts +82 -0
  284. package/src/server/services/project-manager.ts +147 -0
  285. package/src/server/services/sandbox.ts +83 -0
  286. package/src/server/services/session-events.ts +256 -0
  287. package/src/server/services/session-manager.ts +1439 -0
  288. package/src/server/services/skill-install-jobs.ts +275 -0
  289. package/src/server/services/skills-registry.ts +480 -0
  290. package/src/server/services/tunnel-api.ts +207 -0
  291. package/src/server/services/tunnel-config.ts +45 -0
  292. package/src/server/services/tunnel-lhr.ts +387 -0
  293. package/src/server/services/tunnel.ts +633 -0
  294. package/src/server/services/turn-stream.ts +339 -0
  295. package/src/server/services/websocket-gateway.ts +374 -0
  296. package/src/server/services/workspace.ts +223 -0
  297. package/src/server/services/ws-transport.ts +121 -0
  298. package/src/types/context.ts +69 -0
  299. package/src/types/index.ts +13 -0
  300. package/src/types/message.ts +58 -0
  301. package/src/types/project.ts +16 -0
  302. package/src/types/prompt.ts +118 -0
  303. package/src/types/session.ts +52 -0
  304. package/src/types/turns.ts +47 -0
  305. package/src/ws-protocol.ts +93 -0
  306. package/styles/globals.css +311 -0
  307. 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
+ }