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,94 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ function Empty({ className, ...props }: React.ComponentProps<"div">) {
6
+ return (
7
+ <div
8
+ data-slot="empty"
9
+ className={cn(
10
+ "flex min-w-0 flex-1 flex-col items-center justify-center gap-6 rounded-lg border-dashed p-6 text-center text-balance md:p-12",
11
+ className,
12
+ )}
13
+ {...props}
14
+ />
15
+ );
16
+ }
17
+
18
+ function EmptyHeader({ className, ...props }: React.ComponentProps<"div">) {
19
+ return (
20
+ <div
21
+ data-slot="empty-header"
22
+ className={cn("flex max-w-sm flex-col items-center gap-2 text-center", className)}
23
+ {...props}
24
+ />
25
+ );
26
+ }
27
+
28
+ const emptyMediaVariants = cva(
29
+ "mb-2 flex shrink-0 items-center justify-center [&_svg]:pointer-events-none [&_svg]:shrink-0",
30
+ {
31
+ variants: {
32
+ variant: {
33
+ default: "bg-transparent",
34
+ icon: "flex size-10 shrink-0 items-center justify-center rounded-lg bg-muted text-foreground [&_svg:not([class*='size-'])]:size-6",
35
+ },
36
+ },
37
+ defaultVariants: {
38
+ variant: "default",
39
+ },
40
+ },
41
+ );
42
+
43
+ function EmptyMedia({
44
+ className,
45
+ variant = "default",
46
+ ...props
47
+ }: React.ComponentProps<"div"> & VariantProps<typeof emptyMediaVariants>) {
48
+ return (
49
+ <div
50
+ data-slot="empty-icon"
51
+ data-variant={variant}
52
+ className={cn(emptyMediaVariants({ variant, className }))}
53
+ {...props}
54
+ />
55
+ );
56
+ }
57
+
58
+ function EmptyTitle({ className, ...props }: React.ComponentProps<"div">) {
59
+ return (
60
+ <div
61
+ data-slot="empty-title"
62
+ className={cn("text-xl font-medium tracking-tight", className)}
63
+ {...props}
64
+ />
65
+ );
66
+ }
67
+
68
+ function EmptyDescription({ className, ...props }: React.ComponentProps<"p">) {
69
+ return (
70
+ <div
71
+ data-slot="empty-description"
72
+ className={cn(
73
+ "text-base/relaxed text-muted-foreground [&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
74
+ className,
75
+ )}
76
+ {...props}
77
+ />
78
+ );
79
+ }
80
+
81
+ function EmptyContent({ className, ...props }: React.ComponentProps<"div">) {
82
+ return (
83
+ <div
84
+ data-slot="empty-content"
85
+ className={cn(
86
+ "flex w-full max-w-sm min-w-0 flex-col items-center gap-4 text-base text-balance",
87
+ className,
88
+ )}
89
+ {...props}
90
+ />
91
+ );
92
+ }
93
+
94
+ export { Empty, EmptyContent, EmptyDescription, EmptyHeader, EmptyMedia, EmptyTitle };
@@ -0,0 +1,234 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import { useMemo } from "react";
3
+ import { Label } from "@/components/ui/label";
4
+ import { Separator } from "@/components/ui/separator";
5
+ import { cn } from "@/lib/utils";
6
+
7
+ function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
8
+ return (
9
+ <fieldset
10
+ data-slot="field-set"
11
+ className={cn(
12
+ "flex flex-col gap-6",
13
+ "has-[>[data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
14
+ className,
15
+ )}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ function FieldLegend({
22
+ className,
23
+ variant = "legend",
24
+ ...props
25
+ }: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
26
+ return (
27
+ <legend
28
+ data-slot="field-legend"
29
+ data-variant={variant}
30
+ className={cn(
31
+ "mb-3 font-medium",
32
+ "data-[variant=legend]:text-base",
33
+ "data-[variant=label]:text-sm",
34
+ className,
35
+ )}
36
+ {...props}
37
+ />
38
+ );
39
+ }
40
+
41
+ function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
42
+ return (
43
+ <div
44
+ data-slot="field-group"
45
+ className={cn(
46
+ "group/field-group @container/field-group flex w-full flex-col gap-7 data-[slot=checkbox-group]:gap-3 [&>[data-slot=field-group]]:gap-4",
47
+ className,
48
+ )}
49
+ {...props}
50
+ />
51
+ );
52
+ }
53
+
54
+ const fieldVariants = cva("group/field flex w-full gap-3 data-[invalid=true]:text-destructive", {
55
+ variants: {
56
+ orientation: {
57
+ vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
58
+ horizontal: [
59
+ "flex-row items-center",
60
+ "[&>[data-slot=field-label]]:flex-auto",
61
+ "has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
62
+ ],
63
+ responsive: [
64
+ "flex-col @md/field-group:flex-row @md/field-group:items-center [&>*]:w-full @md/field-group:[&>*]:w-auto [&>.sr-only]:w-auto",
65
+ "@md/field-group:[&>[data-slot=field-label]]:flex-auto",
66
+ "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
67
+ ],
68
+ },
69
+ },
70
+ defaultVariants: {
71
+ orientation: "vertical",
72
+ },
73
+ });
74
+
75
+ function Field({
76
+ className,
77
+ orientation = "vertical",
78
+ ...props
79
+ }: React.ComponentProps<"div"> & VariantProps<typeof fieldVariants>) {
80
+ return (
81
+ // biome-ignore lint/a11y/useSemanticElements: div role=group dari shadcn upstream
82
+ <div
83
+ role="group"
84
+ data-slot="field"
85
+ data-orientation={orientation}
86
+ className={cn(fieldVariants({ orientation }), className)}
87
+ {...props}
88
+ />
89
+ );
90
+ }
91
+
92
+ function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
93
+ return (
94
+ <div
95
+ data-slot="field-content"
96
+ className={cn("group/field-content flex flex-1 flex-col gap-1.5 leading-snug", className)}
97
+ {...props}
98
+ />
99
+ );
100
+ }
101
+
102
+ function FieldLabel({ className, ...props }: React.ComponentProps<typeof Label>) {
103
+ return (
104
+ <Label
105
+ data-slot="field-label"
106
+ className={cn(
107
+ "group/field-label peer/field-label flex w-fit gap-2 leading-snug group-data-[disabled=true]/field:opacity-50",
108
+ "has-[>[data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4",
109
+ "has-data-[state=checked]:border-primary has-data-[state=checked]:bg-primary/5 dark:has-data-[state=checked]:bg-primary/10",
110
+ className,
111
+ )}
112
+ {...props}
113
+ />
114
+ );
115
+ }
116
+
117
+ function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
118
+ return (
119
+ <div
120
+ data-slot="field-label"
121
+ className={cn(
122
+ "flex w-fit items-center gap-2 text-sm leading-snug font-medium group-data-[disabled=true]/field:opacity-50",
123
+ className,
124
+ )}
125
+ {...props}
126
+ />
127
+ );
128
+ }
129
+
130
+ function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
131
+ return (
132
+ <p
133
+ data-slot="field-description"
134
+ className={cn(
135
+ "text-sm leading-normal font-normal text-muted-foreground group-has-[[data-orientation=horizontal]]/field:text-balance",
136
+ "last:mt-0 nth-last-2:-mt-1 [[data-variant=legend]+&]:-mt-1.5",
137
+ "[&>a]:underline [&>a]:underline-offset-4 [&>a:hover]:text-primary",
138
+ className,
139
+ )}
140
+ {...props}
141
+ />
142
+ );
143
+ }
144
+
145
+ function FieldSeparator({
146
+ children,
147
+ className,
148
+ ...props
149
+ }: React.ComponentProps<"div"> & {
150
+ children?: React.ReactNode;
151
+ }) {
152
+ return (
153
+ <div
154
+ data-slot="field-separator"
155
+ data-content={!!children}
156
+ className={cn(
157
+ "relative -my-2 h-5 text-sm group-data-[variant=outline]/field-group:-mb-2",
158
+ className,
159
+ )}
160
+ {...props}
161
+ >
162
+ <Separator className="absolute inset-0 top-1/2" />
163
+ {children && (
164
+ <span
165
+ className="relative mx-auto block w-fit bg-background px-2 text-muted-foreground"
166
+ data-slot="field-separator-content"
167
+ >
168
+ {children}
169
+ </span>
170
+ )}
171
+ </div>
172
+ );
173
+ }
174
+
175
+ function FieldError({
176
+ className,
177
+ children,
178
+ errors,
179
+ ...props
180
+ }: React.ComponentProps<"div"> & {
181
+ errors?: Array<{ message?: string } | undefined>;
182
+ }) {
183
+ const content = useMemo(() => {
184
+ if (children) {
185
+ return children;
186
+ }
187
+
188
+ if (!errors?.length) {
189
+ return null;
190
+ }
191
+
192
+ const uniqueErrors = [...new Map(errors.map((error) => [error?.message, error])).values()];
193
+
194
+ if (uniqueErrors?.length === 1) {
195
+ return uniqueErrors[0]?.message;
196
+ }
197
+
198
+ return (
199
+ <ul className="ml-4 flex list-disc flex-col gap-1">
200
+ {uniqueErrors.map((error) =>
201
+ error?.message ? <li key={error.message}>{error.message}</li> : null,
202
+ )}
203
+ </ul>
204
+ );
205
+ }, [children, errors]);
206
+
207
+ if (!content) {
208
+ return null;
209
+ }
210
+
211
+ return (
212
+ <div
213
+ role="alert"
214
+ data-slot="field-error"
215
+ className={cn("text-sm font-normal text-destructive", className)}
216
+ {...props}
217
+ >
218
+ {content}
219
+ </div>
220
+ );
221
+ }
222
+
223
+ export {
224
+ Field,
225
+ FieldContent,
226
+ FieldDescription,
227
+ FieldError,
228
+ FieldGroup,
229
+ FieldLabel,
230
+ FieldLegend,
231
+ FieldSeparator,
232
+ FieldSet,
233
+ FieldTitle,
234
+ };
@@ -0,0 +1,161 @@
1
+ import { cva, type VariantProps } from "class-variance-authority";
2
+ import type * as React from "react";
3
+ import { Button } from "@/components/ui/button";
4
+ import { Input } from "@/components/ui/input";
5
+ import { Textarea } from "@/components/ui/textarea";
6
+ import { cn } from "@/lib/utils";
7
+
8
+ function InputGroup({ className, ...props }: React.ComponentProps<"div">) {
9
+ return (
10
+ // biome-ignore lint/a11y/useSemanticElements: div role=group dari shadcn upstream
11
+ <div
12
+ data-slot="input-group"
13
+ role="group"
14
+ className={cn(
15
+ "group/input-group relative flex w-full items-center rounded-md border border-input shadow-xs transition-[color,box-shadow] outline-none dark:bg-input/30",
16
+ "h-9 min-w-0 has-[>textarea]:h-auto",
17
+
18
+ // Variants based on alignment.
19
+ "has-[>[data-align=inline-start]]:[&>input]:pl-2",
20
+ "has-[>[data-align=inline-end]]:[&>input]:pr-2",
21
+ "has-[>[data-align=block-start]]:h-auto has-[>[data-align=block-start]]:flex-col has-[>[data-align=block-start]]:[&>input]:pb-3",
22
+ "has-[>[data-align=block-end]]:h-auto has-[>[data-align=block-end]]:flex-col has-[>[data-align=block-end]]:[&>input]:pt-3",
23
+
24
+ // Focus state.
25
+ "has-[[data-slot=input-group-control]:focus-visible]:border-ring has-[[data-slot=input-group-control]:focus-visible]:ring-[3px] has-[[data-slot=input-group-control]:focus-visible]:ring-ring/50",
26
+
27
+ // Error state.
28
+ "has-[[data-slot][aria-invalid=true]]:border-destructive has-[[data-slot][aria-invalid=true]]:ring-destructive/20 dark:has-[[data-slot][aria-invalid=true]]:ring-destructive/40",
29
+
30
+ className,
31
+ )}
32
+ {...props}
33
+ />
34
+ );
35
+ }
36
+
37
+ const inputGroupAddonVariants = cva(
38
+ "flex h-auto cursor-text items-center justify-center gap-2 py-1.5 text-sm font-medium text-muted-foreground select-none group-data-[disabled=true]/input-group:opacity-50 [&>kbd]:rounded-[calc(var(--radius)-5px)] [&>svg:not([class*='size-'])]:size-4",
39
+ {
40
+ variants: {
41
+ align: {
42
+ "inline-start": "order-first pl-3 has-[>button]:ml-[-0.45rem] has-[>kbd]:ml-[-0.35rem]",
43
+ "inline-end": "order-last pr-3 has-[>button]:mr-[-0.45rem] has-[>kbd]:mr-[-0.35rem]",
44
+ "block-start":
45
+ "order-first w-full justify-start px-3 pt-3 group-has-[>input]/input-group:pt-2.5 [.border-b]:pb-3",
46
+ "block-end":
47
+ "order-last w-full justify-start px-3 pb-3 group-has-[>input]/input-group:pb-2.5 [.border-t]:pt-3",
48
+ },
49
+ },
50
+ defaultVariants: {
51
+ align: "inline-start",
52
+ },
53
+ },
54
+ );
55
+
56
+ function InputGroupAddon({
57
+ className,
58
+ align = "inline-start",
59
+ ...props
60
+ }: React.ComponentProps<"div"> & VariantProps<typeof inputGroupAddonVariants>) {
61
+ return (
62
+ // biome-ignore lint/a11y/useSemanticElements: div role=group dari shadcn upstream
63
+ // biome-ignore lint/a11y/useKeyWithClickEvents: klik area addon hanya memfokuskan kontrol (kontrol sendiri sudah dapat difokus keyboard)
64
+ <div
65
+ role="group"
66
+ data-slot="input-group-addon"
67
+ data-align={align}
68
+ className={cn(inputGroupAddonVariants({ align }), className)}
69
+ onClick={(e) => {
70
+ // Klik dari konten portal (Dialog/Popover milik tombol di addon)
71
+ // tetap bubble lewat React — jangan curi fokusnya ke textarea.
72
+ if (!e.currentTarget.contains(e.target as Node)) return;
73
+ if ((e.target as HTMLElement).closest("button")) {
74
+ return;
75
+ }
76
+ e.currentTarget.parentElement?.querySelector<HTMLElement>("input, textarea")?.focus();
77
+ }}
78
+ {...props}
79
+ />
80
+ );
81
+ }
82
+
83
+ const inputGroupButtonVariants = cva("flex items-center gap-2 text-sm shadow-none", {
84
+ variants: {
85
+ size: {
86
+ xs: "h-6 gap-1 rounded-[calc(var(--radius)-5px)] px-2 has-[>svg]:px-2 [&>svg:not([class*='size-'])]:size-3.5",
87
+ sm: "h-8 gap-1.5 rounded-md px-2.5 has-[>svg]:px-2.5",
88
+ "icon-xs": "size-6 rounded-[calc(var(--radius)-5px)] p-0 has-[>svg]:p-0",
89
+ "icon-sm": "size-8 p-0 has-[>svg]:p-0",
90
+ },
91
+ },
92
+ defaultVariants: {
93
+ size: "xs",
94
+ },
95
+ });
96
+
97
+ function InputGroupButton({
98
+ className,
99
+ type = "button",
100
+ variant = "ghost",
101
+ size = "xs",
102
+ ...props
103
+ }: Omit<React.ComponentProps<typeof Button>, "size"> &
104
+ VariantProps<typeof inputGroupButtonVariants>) {
105
+ return (
106
+ <Button
107
+ type={type}
108
+ data-size={size}
109
+ variant={variant}
110
+ className={cn(inputGroupButtonVariants({ size }), className)}
111
+ {...props}
112
+ />
113
+ );
114
+ }
115
+
116
+ function InputGroupText({ className, ...props }: React.ComponentProps<"span">) {
117
+ return (
118
+ <span
119
+ className={cn(
120
+ "flex items-center gap-2 text-sm text-muted-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4",
121
+ className,
122
+ )}
123
+ {...props}
124
+ />
125
+ );
126
+ }
127
+
128
+ function InputGroupInput({ className, ...props }: React.ComponentProps<"input">) {
129
+ return (
130
+ <Input
131
+ data-slot="input-group-control"
132
+ className={cn(
133
+ "flex-1 rounded-none border-0 bg-transparent shadow-none focus-visible:ring-0 dark:bg-transparent",
134
+ className,
135
+ )}
136
+ {...props}
137
+ />
138
+ );
139
+ }
140
+
141
+ function InputGroupTextarea({ className, ...props }: React.ComponentProps<"textarea">) {
142
+ return (
143
+ <Textarea
144
+ data-slot="input-group-control"
145
+ className={cn(
146
+ "flex-1 resize-none rounded-none border-0 bg-transparent py-3 shadow-none focus-visible:ring-0 dark:bg-transparent",
147
+ className,
148
+ )}
149
+ {...props}
150
+ />
151
+ );
152
+ }
153
+
154
+ export {
155
+ InputGroup,
156
+ InputGroupAddon,
157
+ InputGroupButton,
158
+ InputGroupInput,
159
+ InputGroupText,
160
+ InputGroupTextarea,
161
+ };
@@ -0,0 +1,21 @@
1
+ import type * as React from "react";
2
+
3
+ import { cn } from "@/lib/utils";
4
+
5
+ function Input({ className, type, ...props }: React.ComponentProps<"input">) {
6
+ return (
7
+ <input
8
+ type={type}
9
+ data-slot="input"
10
+ className={cn(
11
+ "file:text-foreground placeholder:text-muted-foreground selection:bg-primary selection:text-primary-foreground dark:bg-input/30 border-input h-9 w-full min-w-0 rounded-md border bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm",
12
+ "focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px]",
13
+ "aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
14
+ className,
15
+ )}
16
+ {...props}
17
+ />
18
+ );
19
+ }
20
+
21
+ export { Input };
@@ -0,0 +1,19 @@
1
+ import { Label as LabelPrimitive } from "radix-ui";
2
+ import type * as React from "react";
3
+
4
+ import { cn } from "@/lib/utils";
5
+
6
+ function Label({ className, ...props }: React.ComponentProps<typeof LabelPrimitive.Root>) {
7
+ return (
8
+ <LabelPrimitive.Root
9
+ data-slot="label"
10
+ className={cn(
11
+ "flex items-center gap-2 text-sm leading-none font-medium select-none group-data-[disabled=true]:pointer-events-none group-data-[disabled=true]:opacity-50 peer-disabled:cursor-not-allowed peer-disabled:opacity-50",
12
+ className,
13
+ )}
14
+ {...props}
15
+ />
16
+ );
17
+ }
18
+
19
+ export { Label };
@@ -0,0 +1,131 @@
1
+ import {
2
+ MessageScroller as MessageScrollerPrimitive,
3
+ useMessageScroller,
4
+ useMessageScrollerScrollable,
5
+ useMessageScrollerVisibility,
6
+ } from "@shadcn/react/message-scroller";
7
+ import { ArrowDownIcon } from "lucide-react";
8
+ import type * as React from "react";
9
+ import { Button } from "@/components/ui/button";
10
+ import { cn } from "@/lib/utils";
11
+
12
+ function MessageScrollerProvider(
13
+ props: React.ComponentProps<typeof MessageScrollerPrimitive.Provider>,
14
+ ) {
15
+ return <MessageScrollerPrimitive.Provider {...props} />;
16
+ }
17
+
18
+ function MessageScroller({
19
+ className,
20
+ ...props
21
+ }: React.ComponentProps<typeof MessageScrollerPrimitive.Root>) {
22
+ return (
23
+ <MessageScrollerPrimitive.Root
24
+ data-slot="message-scroller"
25
+ className={cn(
26
+ "group/message-scroller relative flex size-full min-h-0 flex-col overflow-hidden",
27
+ className,
28
+ )}
29
+ {...props}
30
+ />
31
+ );
32
+ }
33
+
34
+ function MessageScrollerViewport({
35
+ className,
36
+ ...props
37
+ }: React.ComponentProps<typeof MessageScrollerPrimitive.Viewport>) {
38
+ return (
39
+ <MessageScrollerPrimitive.Viewport
40
+ data-slot="message-scroller-viewport"
41
+ className={cn(
42
+ "size-full min-h-0 min-w-0 scroll-fade-b scrollbar-thin scrollbar-gutter-stable overflow-y-auto overscroll-contain contain-content data-autoscrolling:scrollbar-none",
43
+ className,
44
+ )}
45
+ {...props}
46
+ />
47
+ );
48
+ }
49
+
50
+ function MessageScrollerContent({
51
+ className,
52
+ ...props
53
+ }: React.ComponentProps<typeof MessageScrollerPrimitive.Content>) {
54
+ return (
55
+ <MessageScrollerPrimitive.Content
56
+ data-slot="message-scroller-content"
57
+ className={cn("flex h-max min-h-full flex-col gap-8", className)}
58
+ {...props}
59
+ />
60
+ );
61
+ }
62
+
63
+ function MessageScrollerItem({
64
+ className,
65
+ scrollAnchor = false,
66
+ ...props
67
+ }: React.ComponentProps<typeof MessageScrollerPrimitive.Item>) {
68
+ return (
69
+ <MessageScrollerPrimitive.Item
70
+ data-slot="message-scroller-item"
71
+ scrollAnchor={scrollAnchor}
72
+ className={cn(
73
+ "min-w-0 shrink-0 [contain-intrinsic-size:auto_10rem] [content-visibility:auto]",
74
+ className,
75
+ )}
76
+ {...props}
77
+ />
78
+ );
79
+ }
80
+
81
+ function MessageScrollerButton({
82
+ direction = "end",
83
+ className,
84
+ children,
85
+ render,
86
+ variant = "secondary",
87
+ size = "icon-sm",
88
+ ...props
89
+ }: React.ComponentProps<typeof MessageScrollerPrimitive.Button> &
90
+ Pick<React.ComponentProps<typeof Button>, "variant" | "size">) {
91
+ return (
92
+ <MessageScrollerPrimitive.Button
93
+ data-slot="message-scroller-button"
94
+ data-direction={direction}
95
+ data-variant={variant}
96
+ data-size={size}
97
+ direction={direction}
98
+ className={cn(
99
+ // `left-1/2` + `-translate-x-1/2` memusatkan tombol secara horizontal.
100
+ // Sebelumnya `inset-s-1/2` dipakai, tapi itu bukan utility Tailwind
101
+ // yang valid — tidak ada CSS yang di-emit, sehingga tombol kehilangan
102
+ // posisi horizontalnya dan tergeser ke kiri oleh translate.
103
+ "absolute left-1/2 -translate-x-1/2 border-border bg-background text-foreground transition-[translate,scale,opacity] duration-200 hover:bg-muted hover:text-foreground data-[active=false]:pointer-events-none data-[active=false]:scale-95 data-[active=false]:opacity-0 data-[active=false]:duration-400 data-[active=false]:ease-[cubic-bezier(0.7,0,0.84,0)] data-[active=true]:translate-y-0 data-[active=true]:scale-100 data-[active=true]:opacity-100 data-[active=true]:ease-[cubic-bezier(0.23,1,0.32,1)] data-[direction=end]:bottom-4 data-[direction=end]:data-[active=false]:translate-y-full data-[direction=start]:top-4 data-[direction=start]:data-[active=false]:-translate-y-full data-[direction=start]:[&_svg]:rotate-180",
104
+ className,
105
+ )}
106
+ render={render ?? <Button variant={variant} size={size} />}
107
+ {...props}
108
+ >
109
+ {children ?? (
110
+ <>
111
+ <ArrowDownIcon />
112
+ <span className="sr-only">
113
+ {direction === "end" ? "Scroll to end" : "Scroll to start"}
114
+ </span>
115
+ </>
116
+ )}
117
+ </MessageScrollerPrimitive.Button>
118
+ );
119
+ }
120
+
121
+ export {
122
+ MessageScroller,
123
+ MessageScrollerButton,
124
+ MessageScrollerContent,
125
+ MessageScrollerItem,
126
+ MessageScrollerProvider,
127
+ MessageScrollerViewport,
128
+ useMessageScroller,
129
+ useMessageScrollerScrollable,
130
+ useMessageScrollerVisibility,
131
+ };