@jonsoc/console-app 1.1.34

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 (217) hide show
  1. package/.opencode/agent/css.md +149 -0
  2. package/README.md +32 -0
  3. package/package.json +49 -0
  4. package/public/apple-touch-icon-v3.png +1 -0
  5. package/public/apple-touch-icon.png +1 -0
  6. package/public/email +1 -0
  7. package/public/favicon-96x96-v3.png +1 -0
  8. package/public/favicon-96x96.png +1 -0
  9. package/public/favicon-v3.ico +1 -0
  10. package/public/favicon-v3.svg +1 -0
  11. package/public/favicon.ico +1 -0
  12. package/public/favicon.svg +1 -0
  13. package/public/opencode-brand-assets.zip +0 -0
  14. package/public/robots.txt +6 -0
  15. package/public/site.webmanifest +1 -0
  16. package/public/social-share-black.png +1 -0
  17. package/public/social-share-zen.png +1 -0
  18. package/public/social-share.png +1 -0
  19. package/public/theme.json +182 -0
  20. package/public/web-app-manifest-192x192.png +1 -0
  21. package/public/web-app-manifest-512x512.png +1 -0
  22. package/script/generate-sitemap.ts +103 -0
  23. package/src/app.css +1 -0
  24. package/src/app.tsx +27 -0
  25. package/src/asset/black/hero.png +0 -0
  26. package/src/asset/brand/opencode-brand-assets.zip +0 -0
  27. package/src/asset/brand/opencode-logo-dark.png +0 -0
  28. package/src/asset/brand/opencode-logo-dark.svg +16 -0
  29. package/src/asset/brand/opencode-logo-light.png +0 -0
  30. package/src/asset/brand/opencode-logo-light.svg +16 -0
  31. package/src/asset/brand/opencode-wordmark-dark.png +0 -0
  32. package/src/asset/brand/opencode-wordmark-dark.svg +30 -0
  33. package/src/asset/brand/opencode-wordmark-light.png +0 -0
  34. package/src/asset/brand/opencode-wordmark-light.svg +30 -0
  35. package/src/asset/brand/opencode-wordmark-simple-dark.png +0 -0
  36. package/src/asset/brand/opencode-wordmark-simple-dark.svg +22 -0
  37. package/src/asset/brand/opencode-wordmark-simple-light.png +0 -0
  38. package/src/asset/brand/opencode-wordmark-simple-light.svg +22 -0
  39. package/src/asset/brand/preview-opencode-dark.png +0 -0
  40. package/src/asset/brand/preview-opencode-logo-dark.png +0 -0
  41. package/src/asset/brand/preview-opencode-logo-light.png +0 -0
  42. package/src/asset/brand/preview-opencode-wordmark-dark.png +0 -0
  43. package/src/asset/brand/preview-opencode-wordmark-light.png +0 -0
  44. package/src/asset/brand/preview-opencode-wordmark-simple-dark.png +0 -0
  45. package/src/asset/brand/preview-opencode-wordmark-simple-light.png +0 -0
  46. package/src/asset/lander/avatar-adam.png +0 -0
  47. package/src/asset/lander/avatar-david.png +0 -0
  48. package/src/asset/lander/avatar-dax.png +0 -0
  49. package/src/asset/lander/avatar-frank.png +0 -0
  50. package/src/asset/lander/avatar-jay.png +0 -0
  51. package/src/asset/lander/brand-assets-dark.svg +10 -0
  52. package/src/asset/lander/brand-assets-light.svg +10 -0
  53. package/src/asset/lander/brand.png +0 -0
  54. package/src/asset/lander/check.svg +3 -0
  55. package/src/asset/lander/copy.svg +3 -0
  56. package/src/asset/lander/desktop-app-icon.png +0 -0
  57. package/src/asset/lander/dock.png +0 -0
  58. package/src/asset/lander/logo-dark.svg +11 -0
  59. package/src/asset/lander/logo-light.svg +11 -0
  60. package/src/asset/lander/opencode-comparison-min.mp4 +0 -0
  61. package/src/asset/lander/opencode-comparison-poster.png +0 -0
  62. package/src/asset/lander/opencode-desktop-icon.png +0 -0
  63. package/src/asset/lander/opencode-logo-dark.svg +11 -0
  64. package/src/asset/lander/opencode-logo-light.svg +11 -0
  65. package/src/asset/lander/opencode-min.mp4 +0 -0
  66. package/src/asset/lander/opencode-poster.png +0 -0
  67. package/src/asset/lander/opencode-wordmark-dark.svg +25 -0
  68. package/src/asset/lander/opencode-wordmark-light.svg +25 -0
  69. package/src/asset/lander/screenshot-github.png +0 -0
  70. package/src/asset/lander/screenshot-splash.png +0 -0
  71. package/src/asset/lander/screenshot-vscode.png +0 -0
  72. package/src/asset/lander/screenshot.png +0 -0
  73. package/src/asset/lander/wordmark-dark.svg +3 -0
  74. package/src/asset/lander/wordmark-light.svg +3 -0
  75. package/src/asset/logo-ornate-dark.svg +18 -0
  76. package/src/asset/logo-ornate-light.svg +18 -0
  77. package/src/asset/logo.svg +18 -0
  78. package/src/asset/zen-ornate-dark.svg +8 -0
  79. package/src/asset/zen-ornate-light.svg +8 -0
  80. package/src/component/dropdown.css +80 -0
  81. package/src/component/dropdown.tsx +79 -0
  82. package/src/component/email-signup.tsx +48 -0
  83. package/src/component/faq.tsx +33 -0
  84. package/src/component/footer.tsx +38 -0
  85. package/src/component/header-context-menu.css +63 -0
  86. package/src/component/header.tsx +279 -0
  87. package/src/component/icon.tsx +257 -0
  88. package/src/component/legal.tsx +20 -0
  89. package/src/component/modal.css +66 -0
  90. package/src/component/modal.tsx +24 -0
  91. package/src/component/spotlight.css +15 -0
  92. package/src/component/spotlight.tsx +820 -0
  93. package/src/config.ts +29 -0
  94. package/src/context/auth.session.ts +0 -0
  95. package/src/context/auth.ts +116 -0
  96. package/src/context/auth.withActor.ts +7 -0
  97. package/src/entry-client.tsx +4 -0
  98. package/src/entry-server.tsx +30 -0
  99. package/src/global.d.ts +5 -0
  100. package/src/lib/github.ts +38 -0
  101. package/src/middleware.ts +5 -0
  102. package/src/routes/[...404].css +130 -0
  103. package/src/routes/[...404].tsx +38 -0
  104. package/src/routes/api/enterprise.ts +47 -0
  105. package/src/routes/auth/[...callback].ts +41 -0
  106. package/src/routes/auth/authorize.ts +10 -0
  107. package/src/routes/auth/index.ts +12 -0
  108. package/src/routes/auth/logout.ts +17 -0
  109. package/src/routes/auth/status.ts +7 -0
  110. package/src/routes/bench/[id].tsx +365 -0
  111. package/src/routes/bench/index.tsx +86 -0
  112. package/src/routes/bench/submission.ts +29 -0
  113. package/src/routes/black/common.tsx +62 -0
  114. package/src/routes/black/index.tsx +108 -0
  115. package/src/routes/black/subscribe/[plan].tsx +449 -0
  116. package/src/routes/black/workspace.css +214 -0
  117. package/src/routes/black/workspace.tsx +229 -0
  118. package/src/routes/black.css +828 -0
  119. package/src/routes/black.tsx +285 -0
  120. package/src/routes/brand/index.css +555 -0
  121. package/src/routes/brand/index.tsx +252 -0
  122. package/src/routes/changelog/index.css +477 -0
  123. package/src/routes/changelog/index.tsx +147 -0
  124. package/src/routes/debug/index.ts +13 -0
  125. package/src/routes/desktop-feedback.ts +5 -0
  126. package/src/routes/discord.ts +5 -0
  127. package/src/routes/docs/[...path].ts +20 -0
  128. package/src/routes/docs/index.ts +20 -0
  129. package/src/routes/download/[platform].ts +38 -0
  130. package/src/routes/download/index.css +750 -0
  131. package/src/routes/download/index.tsx +482 -0
  132. package/src/routes/download/types.ts +4 -0
  133. package/src/routes/enterprise/index.css +578 -0
  134. package/src/routes/enterprise/index.tsx +251 -0
  135. package/src/routes/index.css +1251 -0
  136. package/src/routes/index.tsx +840 -0
  137. package/src/routes/legal/privacy-policy/index.css +343 -0
  138. package/src/routes/legal/privacy-policy/index.tsx +1512 -0
  139. package/src/routes/legal/terms-of-service/index.css +254 -0
  140. package/src/routes/legal/terms-of-service/index.tsx +512 -0
  141. package/src/routes/openapi.json.ts +7 -0
  142. package/src/routes/s/[id].ts +20 -0
  143. package/src/routes/stripe/webhook.ts +532 -0
  144. package/src/routes/t/[...path].tsx +20 -0
  145. package/src/routes/temp.tsx +172 -0
  146. package/src/routes/user-menu.css +18 -0
  147. package/src/routes/user-menu.tsx +32 -0
  148. package/src/routes/workspace/[id]/billing/billing-section.module.css +185 -0
  149. package/src/routes/workspace/[id]/billing/billing-section.tsx +240 -0
  150. package/src/routes/workspace/[id]/billing/black-section.module.css +142 -0
  151. package/src/routes/workspace/[id]/billing/black-section.tsx +269 -0
  152. package/src/routes/workspace/[id]/billing/black-waitlist-section.module.css +23 -0
  153. package/src/routes/workspace/[id]/billing/index.tsx +32 -0
  154. package/src/routes/workspace/[id]/billing/monthly-limit-section.module.css +96 -0
  155. package/src/routes/workspace/[id]/billing/monthly-limit-section.tsx +133 -0
  156. package/src/routes/workspace/[id]/billing/payment-section.module.css +93 -0
  157. package/src/routes/workspace/[id]/billing/payment-section.tsx +122 -0
  158. package/src/routes/workspace/[id]/billing/reload-section.module.css +261 -0
  159. package/src/routes/workspace/[id]/billing/reload-section.tsx +213 -0
  160. package/src/routes/workspace/[id]/graph-section.module.css +145 -0
  161. package/src/routes/workspace/[id]/graph-section.tsx +475 -0
  162. package/src/routes/workspace/[id]/index.tsx +81 -0
  163. package/src/routes/workspace/[id]/keys/index.tsx +11 -0
  164. package/src/routes/workspace/[id]/keys/key-section.module.css +197 -0
  165. package/src/routes/workspace/[id]/keys/key-section.tsx +176 -0
  166. package/src/routes/workspace/[id]/members/index.tsx +11 -0
  167. package/src/routes/workspace/[id]/members/member-section.module.css +249 -0
  168. package/src/routes/workspace/[id]/members/member-section.tsx +343 -0
  169. package/src/routes/workspace/[id]/members/role-dropdown.css +72 -0
  170. package/src/routes/workspace/[id]/members/role-dropdown.tsx +43 -0
  171. package/src/routes/workspace/[id]/model-section.module.css +173 -0
  172. package/src/routes/workspace/[id]/model-section.tsx +174 -0
  173. package/src/routes/workspace/[id]/new-user-section.module.css +143 -0
  174. package/src/routes/workspace/[id]/new-user-section.tsx +104 -0
  175. package/src/routes/workspace/[id]/provider-section.module.css +138 -0
  176. package/src/routes/workspace/[id]/provider-section.tsx +188 -0
  177. package/src/routes/workspace/[id]/settings/index.tsx +11 -0
  178. package/src/routes/workspace/[id]/settings/settings-section.module.css +94 -0
  179. package/src/routes/workspace/[id]/settings/settings-section.tsx +122 -0
  180. package/src/routes/workspace/[id]/usage-section.module.css +185 -0
  181. package/src/routes/workspace/[id]/usage-section.tsx +200 -0
  182. package/src/routes/workspace/[id].css +308 -0
  183. package/src/routes/workspace/[id].tsx +62 -0
  184. package/src/routes/workspace/common.tsx +120 -0
  185. package/src/routes/workspace-picker.css +74 -0
  186. package/src/routes/workspace-picker.tsx +122 -0
  187. package/src/routes/workspace.css +107 -0
  188. package/src/routes/workspace.tsx +38 -0
  189. package/src/routes/zen/index.css +866 -0
  190. package/src/routes/zen/index.tsx +343 -0
  191. package/src/routes/zen/util/dataDumper.ts +44 -0
  192. package/src/routes/zen/util/error.ts +13 -0
  193. package/src/routes/zen/util/handler.ts +784 -0
  194. package/src/routes/zen/util/logger.ts +12 -0
  195. package/src/routes/zen/util/provider/anthropic.ts +752 -0
  196. package/src/routes/zen/util/provider/google.ts +75 -0
  197. package/src/routes/zen/util/provider/openai-compatible.ts +546 -0
  198. package/src/routes/zen/util/provider/openai.ts +630 -0
  199. package/src/routes/zen/util/provider/provider.ts +210 -0
  200. package/src/routes/zen/util/rateLimiter.ts +41 -0
  201. package/src/routes/zen/util/stickyProviderTracker.ts +16 -0
  202. package/src/routes/zen/util/trialLimiter.ts +49 -0
  203. package/src/routes/zen/v1/chat/completions.ts +11 -0
  204. package/src/routes/zen/v1/messages.ts +11 -0
  205. package/src/routes/zen/v1/models/[model].ts +13 -0
  206. package/src/routes/zen/v1/models.ts +60 -0
  207. package/src/routes/zen/v1/responses.ts +11 -0
  208. package/src/style/base.css +21 -0
  209. package/src/style/component/button.css +102 -0
  210. package/src/style/index.css +8 -0
  211. package/src/style/reset.css +76 -0
  212. package/src/style/token/color.css +91 -0
  213. package/src/style/token/font.css +21 -0
  214. package/src/style/token/space.css +46 -0
  215. package/sst-env.d.ts +9 -0
  216. package/tsconfig.json +21 -0
  217. package/vite.config.ts +25 -0
@@ -0,0 +1,174 @@
1
+ import { Model } from "@jonsoc/console-core/model.js"
2
+ import { query, action, useParams, createAsync, json } from "@solidjs/router"
3
+ import { createMemo, For, Show } from "solid-js"
4
+ import { withActor } from "~/context/auth.withActor"
5
+ import { ZenData } from "@jonsoc/console-core/model.js"
6
+ import styles from "./model-section.module.css"
7
+ import { querySessionInfo } from "../common"
8
+ import {
9
+ IconAlibaba,
10
+ IconAnthropic,
11
+ IconGemini,
12
+ IconMiniMax,
13
+ IconMoonshotAI,
14
+ IconOpenAI,
15
+ IconStealth,
16
+ IconXai,
17
+ IconZai,
18
+ } from "~/component/icon"
19
+
20
+ const getModelLab = (modelId: string) => {
21
+ if (modelId.startsWith("claude")) return "Anthropic"
22
+ if (modelId.startsWith("gpt")) return "OpenAI"
23
+ if (modelId.startsWith("gemini")) return "Google"
24
+ if (modelId.startsWith("kimi")) return "Moonshot AI"
25
+ if (modelId.startsWith("glm")) return "Z.ai"
26
+ if (modelId.startsWith("qwen")) return "Alibaba"
27
+ if (modelId.startsWith("minimax")) return "MiniMax"
28
+ if (modelId.startsWith("grok")) return "xAI"
29
+ return "Stealth"
30
+ }
31
+
32
+ const getModelsInfo = query(async (workspaceID: string) => {
33
+ "use server"
34
+ return withActor(async () => {
35
+ return {
36
+ all: Object.entries(ZenData.list().models)
37
+ .filter(([id, _model]) => !["claude-3-5-haiku"].includes(id))
38
+ .filter(([id, _model]) => !id.startsWith("alpha-"))
39
+ .sort(([idA, modelA], [idB, modelB]) => {
40
+ const priority = ["big-pickle", "minimax", "grok", "claude", "gpt", "gemini"]
41
+ const getPriority = (id: string) => {
42
+ const index = priority.findIndex((p) => id.startsWith(p))
43
+ return index === -1 ? Infinity : index
44
+ }
45
+ const pA = getPriority(idA)
46
+ const pB = getPriority(idB)
47
+ if (pA !== pB) return pA - pB
48
+
49
+ const modelAName = Array.isArray(modelA) ? modelA[0].name : modelA.name
50
+ const modelBName = Array.isArray(modelB) ? modelB[0].name : modelB.name
51
+ return modelAName.localeCompare(modelBName)
52
+ })
53
+ .map(([id, model]) => ({ id, name: Array.isArray(model) ? model[0].name : model.name })),
54
+ disabled: await Model.listDisabled(),
55
+ }
56
+ }, workspaceID)
57
+ }, "model.info")
58
+
59
+ const updateModel = action(async (form: FormData) => {
60
+ "use server"
61
+ const model = form.get("model")?.toString()
62
+ if (!model) return { error: "Model is required" }
63
+ const workspaceID = form.get("workspaceID")?.toString()
64
+ if (!workspaceID) return { error: "Workspace ID is required" }
65
+ const enabled = form.get("enabled")?.toString() === "true"
66
+ return json(
67
+ withActor(async () => {
68
+ if (enabled) {
69
+ await Model.disable({ model })
70
+ } else {
71
+ await Model.enable({ model })
72
+ }
73
+ }, workspaceID),
74
+ { revalidate: getModelsInfo.key },
75
+ )
76
+ }, "model.toggle")
77
+
78
+ export function ModelSection() {
79
+ const params = useParams()
80
+ const modelsInfo = createAsync(() => getModelsInfo(params.id!))
81
+ const userInfo = createAsync(() => querySessionInfo(params.id!))
82
+
83
+ const modelsWithLab = createMemo(() => {
84
+ const info = modelsInfo()
85
+ if (!info) return []
86
+ return info.all.map((model) => ({
87
+ ...model,
88
+ lab: getModelLab(model.id),
89
+ }))
90
+ })
91
+ return (
92
+ <section class={styles.root}>
93
+ <div data-slot="section-title">
94
+ <h2>Models</h2>
95
+ <p>
96
+ Manage which models workspace members can access. <a href="/docs/zen#pricing ">Learn more</a>.
97
+ </p>
98
+ </div>
99
+ <div data-slot="models-list">
100
+ <Show when={modelsInfo()}>
101
+ <div data-slot="models-table">
102
+ <table data-slot="models-table-element">
103
+ <thead>
104
+ <tr>
105
+ <th>Model</th>
106
+ <th></th>
107
+ <th>Enabled</th>
108
+ </tr>
109
+ </thead>
110
+ <tbody>
111
+ <For each={modelsWithLab()}>
112
+ {({ id, name, lab }) => {
113
+ const isEnabled = createMemo(() => !modelsInfo()!.disabled.includes(id))
114
+ return (
115
+ <tr data-slot="model-row" data-disabled={!isEnabled()}>
116
+ <td data-slot="model-name">
117
+ <div>
118
+ {(() => {
119
+ switch (lab) {
120
+ case "OpenAI":
121
+ return <IconOpenAI width={16} height={16} />
122
+ case "Anthropic":
123
+ return <IconAnthropic width={16} height={16} />
124
+ case "Google":
125
+ return <IconGemini width={16} height={16} />
126
+ case "Moonshot AI":
127
+ return <IconMoonshotAI width={16} height={16} />
128
+ case "Z.ai":
129
+ return <IconZai width={16} height={16} />
130
+ case "Alibaba":
131
+ return <IconAlibaba width={16} height={16} />
132
+ case "xAI":
133
+ return <IconXai width={16} height={16} />
134
+ case "MiniMax":
135
+ return <IconMiniMax width={16} height={16} />
136
+ default:
137
+ return <IconStealth width={16} height={16} />
138
+ }
139
+ })()}
140
+ <span>{name}</span>
141
+ </div>
142
+ </td>
143
+ <td data-slot="model-lab">{lab}</td>
144
+ <td data-slot="model-toggle">
145
+ <form action={updateModel} method="post">
146
+ <input type="hidden" name="model" value={id} />
147
+ <input type="hidden" name="workspaceID" value={params.id} />
148
+ <input type="hidden" name="enabled" value={isEnabled().toString()} />
149
+ <label data-slot="model-toggle-label">
150
+ <input
151
+ type="checkbox"
152
+ checked={isEnabled()}
153
+ disabled={!userInfo()?.isAdmin}
154
+ onChange={(e) => {
155
+ const form = e.currentTarget.closest("form")
156
+ if (form) form.requestSubmit()
157
+ }}
158
+ />
159
+ <span></span>
160
+ </label>
161
+ </form>
162
+ </td>
163
+ </tr>
164
+ )
165
+ }}
166
+ </For>
167
+ </tbody>
168
+ </table>
169
+ </div>
170
+ </Show>
171
+ </div>
172
+ </section>
173
+ )
174
+ }
@@ -0,0 +1,143 @@
1
+ .root {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: var(--space-8);
5
+ padding: var(--space-6);
6
+ background-color: var(--color-bg-surface);
7
+ border: 1px dashed var(--color-border);
8
+ border-radius: var(--border-radius-sm);
9
+
10
+ @media (max-width: 30rem) {
11
+ gap: var(--space-8);
12
+ padding: var(--space-4);
13
+ }
14
+
15
+ [data-component="feature-grid"] {
16
+ display: grid;
17
+ grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
18
+ gap: var(--space-6);
19
+
20
+ @media (max-width: 30rem) {
21
+ grid-template-columns: 1fr;
22
+ gap: var(--space-4);
23
+ }
24
+
25
+ [data-slot="feature"] {
26
+ display: flex;
27
+ flex-direction: column;
28
+ gap: var(--space-2);
29
+ padding: var(--space-4);
30
+ border: 1px solid var(--color-border);
31
+ border-radius: var(--border-radius-sm);
32
+
33
+ h3 {
34
+ font-size: var(--font-size-sm);
35
+ font-weight: 600;
36
+ margin: 0;
37
+ color: var(--color-text);
38
+ text-transform: uppercase;
39
+ letter-spacing: -0.025rem;
40
+ }
41
+
42
+ p {
43
+ font-size: var(--font-size-sm);
44
+ line-height: 1.5;
45
+ margin: 0;
46
+ color: var(--color-text-muted);
47
+ }
48
+ }
49
+ }
50
+
51
+ [data-component="api-key-highlight"] {
52
+ display: flex;
53
+ flex-direction: column;
54
+ gap: var(--space-6);
55
+
56
+ [data-slot="key-display"] {
57
+ display: flex;
58
+ flex-direction: column;
59
+ gap: var(--space-3);
60
+
61
+ [data-slot="key-container"] {
62
+ display: flex;
63
+ gap: var(--space-3);
64
+ padding: var(--space-4);
65
+ border: 2px solid var(--color-accent);
66
+ border-radius: var(--border-radius-sm);
67
+ align-items: center;
68
+
69
+ @media (max-width: 40rem) {
70
+ flex-direction: column;
71
+ gap: var(--space-3);
72
+ align-items: stretch;
73
+ }
74
+
75
+ [data-slot="key-value"] {
76
+ flex: 1;
77
+ font-family: var(--font-mono);
78
+ font-size: var(--font-size-sm);
79
+ color: var(--color-text);
80
+ background-color: var(--color-bg);
81
+ padding: var(--space-3);
82
+ border-radius: var(--border-radius-sm);
83
+ border: 1px solid var(--color-border);
84
+ word-break: break-all;
85
+ line-height: 1.4;
86
+
87
+ @media (max-width: 40rem) {
88
+ font-size: var(--font-size-xs);
89
+ padding: var(--space-2-5);
90
+ }
91
+ }
92
+
93
+ button {
94
+ display: flex;
95
+ align-items: center;
96
+ gap: var(--space-2);
97
+ padding: var(--space-3) var(--space-4);
98
+ font-size: var(--font-size-sm);
99
+ font-weight: 500;
100
+ white-space: nowrap;
101
+ min-width: 130px;
102
+
103
+ @media (max-width: 40rem) {
104
+ justify-content: center;
105
+ padding: var(--space-2-5) var(--space-3);
106
+ font-size: var(--font-size-xs);
107
+ min-width: 96px;
108
+ }
109
+ }
110
+ }
111
+ }
112
+ }
113
+
114
+ [data-component="next-steps"] {
115
+ display: flex;
116
+ flex-direction: column;
117
+ gap: var(--space-6);
118
+
119
+ ol {
120
+ margin: 0;
121
+ padding-left: 0;
122
+ display: flex;
123
+ flex-direction: column;
124
+ gap: var(--space-2);
125
+ list-style-position: inside;
126
+
127
+ li {
128
+ font-size: var(--font-size-md);
129
+ line-height: 1.5;
130
+ color: var(--color-text-secondary);
131
+
132
+ code {
133
+ font-family: var(--font-mono);
134
+ font-size: var(--font-size-sm);
135
+ padding: var(--space-1) var(--space-2);
136
+ border: 1px solid var(--color-border);
137
+ border-radius: var(--border-radius-sm);
138
+ color: var(--color-text);
139
+ }
140
+ }
141
+ }
142
+ }
143
+ }
@@ -0,0 +1,104 @@
1
+ import { query, useParams, createAsync } from "@solidjs/router"
2
+ import { createMemo, createSignal, Show } from "solid-js"
3
+ import { IconCopy, IconCheck } from "~/component/icon"
4
+ import { Key } from "@jonsoc/console-core/key.js"
5
+ import { Billing } from "@jonsoc/console-core/billing.js"
6
+ import { withActor } from "~/context/auth.withActor"
7
+ import styles from "./new-user-section.module.css"
8
+
9
+ const getUsageInfo = query(async (workspaceID: string) => {
10
+ "use server"
11
+ return withActor(async () => {
12
+ return await Billing.usages()
13
+ }, workspaceID)
14
+ }, "usage.list")
15
+
16
+ const listKeys = query(async (workspaceID: string) => {
17
+ "use server"
18
+ return withActor(() => Key.list(), workspaceID)
19
+ }, "key.list")
20
+
21
+ export function NewUserSection() {
22
+ const params = useParams()
23
+ const [copiedKey, setCopiedKey] = createSignal(false)
24
+ const keys = createAsync(() => listKeys(params.id!))
25
+ const usage = createAsync(() => getUsageInfo(params.id!))
26
+ const isNew = createMemo(() => {
27
+ const keysList = keys()
28
+ const usageList = usage()
29
+ return keysList?.length === 1 && (!usageList || usageList.length === 0)
30
+ })
31
+ const defaultKey = createMemo(() => {
32
+ const key = keys()?.at(-1)?.key
33
+ if (!key) return undefined
34
+ return {
35
+ actual: key,
36
+ masked: key.slice(0, 8) + "*".repeat(key.length - 12) + key.slice(-4),
37
+ }
38
+ })
39
+
40
+ return (
41
+ <Show when={isNew()}>
42
+ <div class={styles.root}>
43
+ <div data-component="feature-grid">
44
+ <div data-slot="feature">
45
+ <h3>Tested & Verified Models</h3>
46
+ <p>We've benchmarked and tested models specifically for coding agents to ensure the best performance.</p>
47
+ </div>
48
+ <div data-slot="feature">
49
+ <h3>Highest Quality</h3>
50
+ <p>Access models configured for optimal performance - no downgrades or routing to cheaper providers.</p>
51
+ </div>
52
+ <div data-slot="feature">
53
+ <h3>No Lock-in</h3>
54
+ <p>Use Zen with any coding agent, and continue using other providers with jonsoc whenever you want.</p>
55
+ </div>
56
+ </div>
57
+
58
+ <div data-component="api-key-highlight">
59
+ <Show when={defaultKey()}>
60
+ <div data-slot="key-display">
61
+ <div data-slot="key-container">
62
+ <code data-slot="key-value">{defaultKey()?.masked}</code>
63
+ <button
64
+ data-color="primary"
65
+ disabled={copiedKey()}
66
+ onClick={async () => {
67
+ await navigator.clipboard.writeText(defaultKey()?.actual ?? "")
68
+ setCopiedKey(true)
69
+ setTimeout(() => setCopiedKey(false), 2000)
70
+ }}
71
+ title="Copy API key"
72
+ >
73
+ <Show
74
+ when={copiedKey()}
75
+ fallback={
76
+ <>
77
+ <IconCopy style={{ width: "16px", height: "16px" }} /> Copy Key
78
+ </>
79
+ }
80
+ >
81
+ <IconCheck style={{ width: "16px", height: "16px" }} /> Copied!
82
+ </Show>
83
+ </button>
84
+ </div>
85
+ </div>
86
+ </Show>
87
+ </div>
88
+
89
+ <div data-component="next-steps">
90
+ <ol>
91
+ <li>Enable billing</li>
92
+ <li>
93
+ Run <code>jonsoc auth login</code> and select jonsoc
94
+ </li>
95
+ <li>Paste your API key</li>
96
+ <li>
97
+ Start jonsoc and run <code>/models</code> to select a model
98
+ </li>
99
+ </ol>
100
+ </div>
101
+ </div>
102
+ </Show>
103
+ )
104
+ }
@@ -0,0 +1,138 @@
1
+ .root {
2
+ [data-slot="providers-table"] {
3
+ overflow-x: auto;
4
+ }
5
+
6
+ [data-slot="providers-table-element"] {
7
+ width: 100%;
8
+ border-collapse: collapse;
9
+ font-size: var(--font-size-sm);
10
+
11
+ thead {
12
+ border-bottom: 1px solid var(--color-border);
13
+ }
14
+
15
+ th {
16
+ padding: var(--space-3) var(--space-4);
17
+ text-align: left;
18
+ font-weight: normal;
19
+ color: var(--color-text-muted);
20
+ text-transform: uppercase;
21
+
22
+ &:nth-child(1) {
23
+ width: 180px;
24
+ }
25
+
26
+ &:nth-child(3) {
27
+ width: 200px;
28
+ }
29
+ }
30
+
31
+ td {
32
+ padding: var(--space-3) var(--space-4);
33
+ border-bottom: 1px solid var(--color-border-muted);
34
+ color: var(--color-text-muted);
35
+ font-family: var(--font-mono);
36
+
37
+ &[data-slot="provider-name"] {
38
+ color: var(--color-text);
39
+ font-family: var(--font-mono);
40
+ font-weight: 500;
41
+ }
42
+
43
+ &[data-slot="provider-key"] {
44
+ text-align: left;
45
+ color: var(--color-text-secondary);
46
+
47
+ [data-slot="edit-form"] {
48
+ display: flex;
49
+ flex-direction: column;
50
+ gap: var(--space-3);
51
+ max-width: 100%;
52
+
53
+ [data-slot="input-wrapper"] {
54
+ display: flex;
55
+ flex-direction: column;
56
+ gap: var(--space-1);
57
+ max-width: 100%;
58
+
59
+ input {
60
+ padding: var(--space-2) var(--space-3);
61
+ border: 1px solid var(--color-border);
62
+ border-radius: var(--border-radius-sm);
63
+ background-color: var(--color-bg);
64
+ color: var(--color-text);
65
+ font-size: var(--font-size-sm);
66
+ font-family: var(--font-mono);
67
+ width: 100%;
68
+ box-sizing: border-box;
69
+
70
+ &:focus {
71
+ outline: none;
72
+ border-color: var(--color-accent);
73
+ }
74
+
75
+ &::placeholder {
76
+ color: var(--color-text-disabled);
77
+ }
78
+ }
79
+
80
+ [data-slot="form-error"] {
81
+ color: var(--color-danger);
82
+ font-size: var(--font-size-sm);
83
+ line-height: 1.4;
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ &[data-slot="provider-action"] {
90
+ text-align: left;
91
+ font-family: var(--font-sans);
92
+ white-space: nowrap;
93
+
94
+ [data-slot="configured-actions"] {
95
+ display: flex;
96
+ gap: var(--space-2);
97
+
98
+ [data-slot="delete-form"] {
99
+ opacity: 0;
100
+ pointer-events: none;
101
+ transition: opacity 0.2s;
102
+ }
103
+
104
+ &:hover [data-slot="delete-form"] {
105
+ opacity: 1;
106
+ pointer-events: auto;
107
+ }
108
+ }
109
+
110
+ [data-slot="form-actions"] {
111
+ display: flex;
112
+ gap: var(--space-2);
113
+ }
114
+ }
115
+ }
116
+
117
+ tbody tr {
118
+ &:hover {
119
+ [data-slot="provider-action"] [data-slot="delete-form"] {
120
+ opacity: 1;
121
+ pointer-events: auto;
122
+ }
123
+ }
124
+
125
+ &:last-child td {
126
+ border-bottom: none;
127
+ }
128
+ }
129
+
130
+ @media (max-width: 40rem) {
131
+ th,
132
+ td {
133
+ padding: var(--space-2) var(--space-3);
134
+ font-size: var(--font-size-xs);
135
+ }
136
+ }
137
+ }
138
+ }