@neurocode-ai/enterprise 1.18.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,32 @@
1
+ # SolidStart
2
+
3
+ Everything you need to build a Solid project, powered by [`solid-start`](https://start.solidjs.com);
4
+
5
+ ## Creating a project
6
+
7
+ ```bash
8
+ # create a new project in the current directory
9
+ npm init solid@latest
10
+
11
+ # create a new project in my-app
12
+ npm init solid@latest my-app
13
+ ```
14
+
15
+ ## Developing
16
+
17
+ Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
18
+
19
+ ```bash
20
+ npm run dev
21
+
22
+ # or start the server and open the app in a new browser tab
23
+ npm run dev -- --open
24
+ ```
25
+
26
+ ## Building
27
+
28
+ Solid apps are built with _presets_, which optimise your project for deployment to different environments.
29
+
30
+ By default, `npm run build` will generate a Node app that you can run with `npm start`. To use a different preset, add it to the `devDependencies` in `package.json` and specify in your `app.config.js`.
31
+
32
+ ## This project was created with the [Solid CLI](https://github.com/solidjs-community/solid-cli)
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@neurocode-ai/enterprise",
3
+ "version": "1.18.8",
4
+ "type": "module",
5
+ "license": "MIT",
6
+ "scripts": {
7
+ "typecheck": "tsgo --noEmit",
8
+ "dev": "vite dev",
9
+ "build": "vite build",
10
+ "build:cloudflare": "OPENCODE_DEPLOYMENT_TARGET=cloudflare vite build",
11
+ "start": "vite start",
12
+ "shell-prod": "sst shell --target Teams --stage production"
13
+ },
14
+ "dependencies": {
15
+ "@neurocode-ai/core": "1.18.8",
16
+ "@neurocode-ai/session-ui": "1.18.8",
17
+ "@neurocode-ai/ui": "1.18.8",
18
+ "aws4fetch": "^1.0.20",
19
+ "@pierre/diffs": "1.2.10",
20
+ "@solidjs/router": "0.15.4",
21
+ "@solidjs/start": "https://pkg.pr.new/@solidjs/start@dfb2020",
22
+ "@solidjs/meta": "0.29.4",
23
+ "@hono/standard-validator": "0.2.0",
24
+ "hono": "4.10.7",
25
+ "hono-openapi": "1.1.2",
26
+ "js-base64": "3.7.7",
27
+ "luxon": "3.6.1",
28
+ "nitro": "3.0.1-alpha.1",
29
+ "solid-js": "1.9.10",
30
+ "zod": "4.1.8"
31
+ },
32
+ "devDependencies": {
33
+ "@cloudflare/workers-types": "4.20251008.0",
34
+ "@tailwindcss/vite": "4.1.11",
35
+ "@typescript/native-preview": "7.0.0-dev.20251207.1",
36
+ "@types/bun": "1.3.13",
37
+ "@types/luxon": "3.7.1",
38
+ "tailwindcss": "4.1.11",
39
+ "typescript": "5.8.2",
40
+ "vite": "7.1.4"
41
+ },
42
+ "engines": {
43
+ "node": ">=22"
44
+ }
45
+ }
@@ -0,0 +1 @@
1
+ ../../ui/src/assets/favicon/apple-touch-icon-v3.png
@@ -0,0 +1 @@
1
+ ../../ui/src/assets/favicon/apple-touch-icon.png
@@ -0,0 +1 @@
1
+ ../../ui/src/assets/favicon/favicon-96x96-v3.png
@@ -0,0 +1 @@
1
+ ../../ui/src/assets/favicon/favicon-96x96.png
@@ -0,0 +1 @@
1
+ ../../ui/src/assets/favicon/favicon-v3.ico
@@ -0,0 +1 @@
1
+ ../../ui/src/assets/favicon/favicon-v3.svg
@@ -0,0 +1 @@
1
+ ../../ui/src/assets/favicon/favicon.ico
@@ -0,0 +1 @@
1
+ ../../ui/src/assets/favicon/favicon.svg
@@ -0,0 +1 @@
1
+ ../../ui/src/assets/favicon/site.webmanifest
@@ -0,0 +1 @@
1
+ ../../ui/src/assets/images/social-share-zen.png
@@ -0,0 +1 @@
1
+ ../../ui/src/assets/images/social-share.png
@@ -0,0 +1 @@
1
+ ../../ui/src/assets/favicon/web-app-manifest-192x192.png
@@ -0,0 +1 @@
1
+ ../../ui/src/assets/favicon/web-app-manifest-512x512.png
@@ -0,0 +1,15 @@
1
+ import { Storage } from "../src/core/storage"
2
+
3
+ // read share id from args
4
+ const args = process.argv.slice(2)
5
+ if (args.length !== 1) {
6
+ console.error("Usage: bun script/scrap.ts <shareID>")
7
+ process.exit(1)
8
+ }
9
+ const shareID = args[0]
10
+
11
+ await Storage.remove(["share", shareID])
12
+ const list = await Storage.list({ prefix: ["share_data", shareID] })
13
+ for (const item of list) {
14
+ await Storage.remove(item)
15
+ }
package/src/app.css ADDED
@@ -0,0 +1,2 @@
1
+ @import "@opencode-ai/ui/styles/tailwind";
2
+ @import "@opencode-ai/session-ui/styles";
package/src/app.tsx ADDED
@@ -0,0 +1,94 @@
1
+ import { Router } from "@solidjs/router"
2
+ import { FileRoutes } from "@solidjs/start/router"
3
+ import { Font } from "@neurocode-ai/ui/font"
4
+ import { MetaProvider } from "@solidjs/meta"
5
+ import { MarkedProvider } from "@neurocode-ai/ui/context/marked"
6
+ import { DialogProvider } from "@neurocode-ai/ui/context/dialog"
7
+ import { I18nProvider, type UiI18nParams } from "@neurocode-ai/ui/context"
8
+ import { dict as uiEn } from "@neurocode-ai/ui/i18n/en"
9
+ import { dict as uiZh } from "@neurocode-ai/ui/i18n/zh"
10
+ import { createEffect, createMemo, Suspense, type ParentProps } from "solid-js"
11
+ import { getRequestEvent } from "solid-js/web"
12
+ import "./app.css"
13
+ import { Favicon } from "@neurocode-ai/ui/favicon"
14
+
15
+ function resolveTemplate(text: string, params?: UiI18nParams) {
16
+ if (!params) return text
17
+ return text.replace(/{{\s*([^}]+?)\s*}}/g, (_, rawKey) => {
18
+ const key = String(rawKey)
19
+ const value = params[key]
20
+ return value === undefined ? "" : String(value)
21
+ })
22
+ }
23
+
24
+ function detectLocaleFromHeader(header: string | null | undefined) {
25
+ if (!header) return
26
+ for (const item of header.split(",")) {
27
+ const value = item.trim().split(";")[0]?.toLowerCase()
28
+ if (!value) continue
29
+ if (value.startsWith("zh")) return "zh" as const
30
+ if (value.startsWith("en")) return "en" as const
31
+ }
32
+ }
33
+
34
+ function detectLocale() {
35
+ const event = getRequestEvent()
36
+ const header = event?.request.headers.get("accept-language")
37
+ const headerLocale = detectLocaleFromHeader(header)
38
+ if (headerLocale) return headerLocale
39
+
40
+ if (typeof document === "object") {
41
+ const value = document.documentElement.lang?.toLowerCase() ?? ""
42
+ if (value.startsWith("zh")) return "zh" as const
43
+ if (value.startsWith("en")) return "en" as const
44
+ }
45
+
46
+ if (typeof navigator === "object") {
47
+ const languages = navigator.languages?.length ? navigator.languages : [navigator.language]
48
+ for (const language of languages) {
49
+ if (!language) continue
50
+ if (language.toLowerCase().startsWith("zh")) return "zh" as const
51
+ }
52
+ }
53
+
54
+ return "en" as const
55
+ }
56
+
57
+ function UiI18nBridge(props: ParentProps) {
58
+ const locale = createMemo(() => detectLocale())
59
+ const zh = uiZh as Partial<Record<string, string>>
60
+ const t = (key: keyof typeof uiEn, params?: UiI18nParams) => {
61
+ const value = locale() === "zh" ? (zh[key] ?? uiEn[key]) : uiEn[key]
62
+ const text = value ?? String(key)
63
+ return resolveTemplate(text, params)
64
+ }
65
+
66
+ createEffect(() => {
67
+ if (typeof document !== "object") return
68
+ document.documentElement.lang = locale()
69
+ })
70
+
71
+ return <I18nProvider value={{ locale, t }}>{props.children}</I18nProvider>
72
+ }
73
+
74
+ export default function App() {
75
+ return (
76
+ <Router
77
+ root={(props) => (
78
+ <MetaProvider>
79
+ <DialogProvider>
80
+ <MarkedProvider>
81
+ <Favicon />
82
+ <Font />
83
+ <UiI18nBridge>
84
+ <Suspense>{props.children}</Suspense>
85
+ </UiI18nBridge>
86
+ </MarkedProvider>
87
+ </DialogProvider>
88
+ </MetaProvider>
89
+ )}
90
+ >
91
+ <FileRoutes />
92
+ </Router>
93
+ )
94
+ }
@@ -0,0 +1,232 @@
1
+ import { Message, Model, Part, Session, SnapshotFileDiff } from "@neurocode-ai/sdk/v2"
2
+ import { iife } from "@neurocode-ai/core/util/iife"
3
+ import z from "zod"
4
+ import { Storage } from "./storage"
5
+
6
+ function fn<T extends z.ZodType, Result>(schema: T, cb: (input: z.infer<T>) => Result) {
7
+ return (input: z.infer<T>) => cb(schema.parse(input))
8
+ }
9
+
10
+ export namespace Share {
11
+ export const Info = z.object({
12
+ id: z.string(),
13
+ secret: z.string(),
14
+ sessionID: z.string(),
15
+ })
16
+ export type Info = z.infer<typeof Info>
17
+
18
+ export const Data = z.discriminatedUnion("type", [
19
+ z.object({
20
+ type: z.literal("session"),
21
+ data: z.custom<Session>(),
22
+ }),
23
+ z.object({
24
+ type: z.literal("message"),
25
+ data: z.custom<Message>(),
26
+ }),
27
+ z.object({
28
+ type: z.literal("part"),
29
+ data: z.custom<Part>(),
30
+ }),
31
+ z.object({
32
+ type: z.literal("session_diff"),
33
+ data: z.custom<SnapshotFileDiff[]>(),
34
+ }),
35
+ z.object({
36
+ type: z.literal("model"),
37
+ data: z.custom<Model[]>(),
38
+ }),
39
+ ])
40
+ export type Data = z.infer<typeof Data>
41
+
42
+ type Snapshot = {
43
+ data: Data[]
44
+ }
45
+
46
+ type Compaction = {
47
+ event?: string
48
+ data: Data[]
49
+ }
50
+
51
+ function key(item: Data) {
52
+ switch (item.type) {
53
+ case "session":
54
+ return "session"
55
+ case "message":
56
+ return `message/${item.data.id}`
57
+ case "part":
58
+ return `part/${item.data.messageID}/${item.data.id}`
59
+ case "session_diff":
60
+ return "session_diff"
61
+ case "model":
62
+ return "model"
63
+ }
64
+ }
65
+
66
+ function merge(...items: Data[][]) {
67
+ const map = new Map<string, Data>()
68
+ for (const list of items) {
69
+ for (const item of list) {
70
+ map.set(key(item), item)
71
+ }
72
+ }
73
+ return Array.from(map.entries())
74
+ .sort(([a], [b]) => a.localeCompare(b))
75
+ .map(([, item]) => item)
76
+ }
77
+
78
+ async function readSnapshot(shareID: string) {
79
+ return (await Storage.read<Snapshot>(["share_snapshot", shareID]))?.data
80
+ }
81
+
82
+ async function writeSnapshot(shareID: string, data: Data[]) {
83
+ await Storage.write<Snapshot>(["share_snapshot", shareID], { data })
84
+ }
85
+
86
+ async function legacy(shareID: string) {
87
+ const compaction: Compaction = (await Storage.read<Compaction>(["share_compaction", shareID])) ?? {
88
+ data: [],
89
+ event: undefined,
90
+ }
91
+ const list = await Storage.list({
92
+ prefix: ["share_event", shareID],
93
+ before: compaction.event,
94
+ }).then((x) => x.toReversed())
95
+ if (list.length === 0) {
96
+ if (compaction.data.length > 0) await writeSnapshot(shareID, compaction.data)
97
+ return compaction.data
98
+ }
99
+
100
+ const next = merge(
101
+ compaction.data,
102
+ await Promise.all(list.map(async (event) => await Storage.read<Data[]>(event))).then((x) =>
103
+ x.flatMap((item) => item ?? []),
104
+ ),
105
+ )
106
+
107
+ await Promise.all([
108
+ Storage.write(["share_compaction", shareID], {
109
+ event: list.at(-1)?.at(-1),
110
+ data: next,
111
+ }),
112
+ writeSnapshot(shareID, next),
113
+ ])
114
+ return next
115
+ }
116
+
117
+ export const create = fn(z.object({ sessionID: z.string() }), async (body) => {
118
+ const isTest = process.env.NODE_ENV === "test" || body.sessionID.startsWith("test_")
119
+ const info: Info = {
120
+ id: (isTest ? "test_" : "") + body.sessionID.slice(-8),
121
+ sessionID: body.sessionID,
122
+ secret: crypto.randomUUID(),
123
+ }
124
+ const exists = await get(info.id)
125
+ if (exists) throw new Errors.AlreadyExists(info.id)
126
+ await Promise.all([Storage.write(["share", info.id], info), writeSnapshot(info.id, [])])
127
+ return info
128
+ })
129
+
130
+ export async function get(id: string) {
131
+ return Storage.read<Info>(["share", id])
132
+ }
133
+
134
+ export const remove = fn(Info.pick({ id: true, secret: true }), async (body) => {
135
+ const share = await get(body.id)
136
+ if (!share) throw new Errors.NotFound(body.id)
137
+ if (share.secret !== body.secret) throw new Errors.InvalidSecret(body.id)
138
+ await Storage.remove(["share", body.id])
139
+ const groups = await Promise.all([
140
+ Storage.list({ prefix: ["share_snapshot", body.id] }),
141
+ Storage.list({ prefix: ["share_compaction", body.id] }),
142
+ Storage.list({ prefix: ["share_event", body.id] }),
143
+ Storage.list({ prefix: ["share_data", body.id] }),
144
+ ])
145
+ for (const item of groups.flat()) {
146
+ await Storage.remove(item)
147
+ }
148
+ })
149
+
150
+ export const removeAdmin = fn(Info.pick({ id: true }), async (body) => {
151
+ const share = await get(body.id)
152
+ if (!share) throw new Errors.NotFound(body.id)
153
+ await remove({ id: share.id, secret: share.secret })
154
+ })
155
+
156
+ export const sync = fn(
157
+ z.object({
158
+ share: Info.pick({ id: true, secret: true }),
159
+ data: Data.array(),
160
+ }),
161
+ async (input) => {
162
+ const share = await get(input.share.id)
163
+ if (!share) throw new Errors.NotFound(input.share.id)
164
+ if (share.secret !== input.share.secret) throw new Errors.InvalidSecret(input.share.id)
165
+ const data = (await readSnapshot(input.share.id)) ?? (await legacy(input.share.id))
166
+ await writeSnapshot(input.share.id, merge(data, input.data))
167
+ },
168
+ )
169
+
170
+ export async function data(shareID: string) {
171
+ return (await readSnapshot(shareID)) ?? legacy(shareID)
172
+ }
173
+
174
+ export const syncOld = fn(
175
+ z.object({
176
+ share: Info.pick({ id: true, secret: true }),
177
+ data: Data.array(),
178
+ }),
179
+ async (input) => {
180
+ const share = await get(input.share.id)
181
+ if (!share) throw new Errors.NotFound(input.share.id)
182
+ if (share.secret !== input.share.secret) throw new Errors.InvalidSecret(input.share.id)
183
+ const promises = []
184
+ for (const item of input.data) {
185
+ promises.push(
186
+ iife(async () => {
187
+ switch (item.type) {
188
+ case "session":
189
+ await Storage.write(["share_data", input.share.id, "session"], item.data)
190
+ break
191
+ case "message": {
192
+ const data = item.data as Message
193
+ await Storage.write(["share_data", input.share.id, "message", data.id], item.data)
194
+ break
195
+ }
196
+ case "part": {
197
+ const data = item.data as Part
198
+ await Storage.write(["share_data", input.share.id, "part", data.messageID, data.id], item.data)
199
+ break
200
+ }
201
+ case "session_diff":
202
+ await Storage.write(["share_data", input.share.id, "session_diff"], item.data)
203
+ break
204
+ case "model":
205
+ await Storage.write(["share_data", input.share.id, "model"], item.data)
206
+ break
207
+ }
208
+ }),
209
+ )
210
+ }
211
+ await Promise.all(promises)
212
+ },
213
+ )
214
+
215
+ export const Errors = {
216
+ NotFound: class extends Error {
217
+ constructor(public id: string) {
218
+ super(`Share not found: ${id}`)
219
+ }
220
+ },
221
+ InvalidSecret: class extends Error {
222
+ constructor(public id: string) {
223
+ super(`Share secret invalid: ${id}`)
224
+ }
225
+ },
226
+ AlreadyExists: class extends Error {
227
+ constructor(public id: string) {
228
+ super(`Share already exists: ${id}`)
229
+ }
230
+ },
231
+ }
232
+ }
@@ -0,0 +1,129 @@
1
+ import { AwsClient } from "aws4fetch"
2
+ import { lazy } from "@neurocode-ai/core/util/lazy"
3
+
4
+ export namespace Storage {
5
+ export interface Adapter {
6
+ read(path: string): Promise<string | undefined>
7
+ write(path: string, value: string): Promise<void>
8
+ remove(path: string): Promise<void>
9
+ list(options?: { prefix?: string; limit?: number; after?: string; before?: string }): Promise<string[]>
10
+ }
11
+
12
+ function createAdapter(client: AwsClient, endpoint: string, bucket: string): Adapter {
13
+ const base = `${endpoint}/${bucket}`
14
+ return {
15
+ async read(path: string): Promise<string | undefined> {
16
+ const response = await client.fetch(`${base}/${path}`)
17
+ if (response.status === 404) return undefined
18
+ if (!response.ok) throw new Error(`Failed to read ${path}: ${response.status}`)
19
+ return response.text()
20
+ },
21
+
22
+ async write(path: string, value: string): Promise<void> {
23
+ const response = await client.fetch(`${base}/${path}`, {
24
+ method: "PUT",
25
+ body: value,
26
+ headers: {
27
+ "Content-Type": "application/json",
28
+ },
29
+ })
30
+ if (!response.ok) throw new Error(`Failed to write ${path}: ${response.status}`)
31
+ },
32
+
33
+ async remove(path: string): Promise<void> {
34
+ const response = await client.fetch(`${base}/${path}`, {
35
+ method: "DELETE",
36
+ })
37
+ if (!response.ok) throw new Error(`Failed to remove ${path}: ${response.status}`)
38
+ },
39
+
40
+ async list(options?: { prefix?: string; limit?: number; after?: string; before?: string }): Promise<string[]> {
41
+ const prefix = options?.prefix || ""
42
+ const params = new URLSearchParams({ "list-type": "2", prefix })
43
+ if (options?.limit) params.set("max-keys", options.limit.toString())
44
+ if (options?.after) {
45
+ const afterPath = prefix + options.after + ".json"
46
+ params.set("start-after", afterPath)
47
+ }
48
+ const response = await client.fetch(`${base}?${params}`)
49
+ if (!response.ok) throw new Error(`Failed to list ${prefix}: ${response.status}`)
50
+ const xml = await response.text()
51
+ const keys: string[] = []
52
+ const regex = /<Key>([^<]+)<\/Key>/g
53
+ let match
54
+ while ((match = regex.exec(xml)) !== null) {
55
+ keys.push(match[1])
56
+ }
57
+ if (options?.before) {
58
+ const beforePath = prefix + options.before + ".json"
59
+ return keys.filter((key) => key < beforePath)
60
+ }
61
+ return keys
62
+ },
63
+ }
64
+ }
65
+
66
+ function s3(): Adapter {
67
+ const bucket = process.env.NEUROCODE_STORAGE_BUCKET!
68
+ const region = process.env.NEUROCODE_STORAGE_REGION || "us-east-1"
69
+ const client = new AwsClient({
70
+ region,
71
+ accessKeyId: process.env.NEUROCODE_STORAGE_ACCESS_KEY_ID!,
72
+ secretAccessKey: process.env.NEUROCODE_STORAGE_SECRET_ACCESS_KEY!,
73
+ })
74
+ return createAdapter(client, `https://s3.${region}.amazonaws.com`, bucket)
75
+ }
76
+
77
+ function r2() {
78
+ const accountId = process.env.NEUROCODE_STORAGE_ACCOUNT_ID!
79
+ const client = new AwsClient({
80
+ accessKeyId: process.env.NEUROCODE_STORAGE_ACCESS_KEY_ID!,
81
+ secretAccessKey: process.env.NEUROCODE_STORAGE_SECRET_ACCESS_KEY!,
82
+ })
83
+ return createAdapter(client, `https://${accountId}.r2.cloudflarestorage.com`, process.env.NEUROCODE_STORAGE_BUCKET!)
84
+ }
85
+
86
+ const adapter = lazy(() => {
87
+ const type = process.env.NEUROCODE_STORAGE_ADAPTER
88
+ if (type === "r2") return r2()
89
+ if (type === "s3") return s3()
90
+ throw new Error("No storage adapter configured")
91
+ })
92
+
93
+ function resolve(key: string[]) {
94
+ return key.join("/") + ".json"
95
+ }
96
+
97
+ export async function read<T>(key: string[]) {
98
+ const result = await adapter().read(resolve(key))
99
+ if (!result) return undefined
100
+ return JSON.parse(result) as T
101
+ }
102
+
103
+ export function write<T>(key: string[], value: T) {
104
+ return adapter().write(resolve(key), JSON.stringify(value))
105
+ }
106
+
107
+ export function remove(key: string[]) {
108
+ return adapter().remove(resolve(key))
109
+ }
110
+
111
+ export async function list(options?: { prefix?: string[]; limit?: number; after?: string; before?: string }) {
112
+ const p = options?.prefix ? options.prefix.join("/") + (options.prefix.length ? "/" : "") : ""
113
+ const result = await adapter().list({
114
+ prefix: p,
115
+ limit: options?.limit,
116
+ after: options?.after,
117
+ before: options?.before,
118
+ })
119
+ return result.map((x) => x.replace(/\.json$/, "").split("/"))
120
+ }
121
+
122
+ export async function update<T>(key: string[], fn: (draft: T) => void) {
123
+ const val = await read<T>(key)
124
+ if (!val) throw new Error("Not found")
125
+ fn(val)
126
+ await write(key, val)
127
+ return val
128
+ }
129
+ }
@@ -0,0 +1 @@
1
+ ../../ui/src/custom-elements.d.ts
@@ -0,0 +1,4 @@
1
+ // @refresh reload
2
+ import { mount, StartClient } from "@solidjs/start/client"
3
+
4
+ mount(() => <StartClient />, document.getElementById("app")!)
@@ -0,0 +1,38 @@
1
+ // @refresh reload
2
+ import { createHandler, StartServer } from "@solidjs/start/server"
3
+ import { getRequestEvent } from "solid-js/web"
4
+
5
+ export default createHandler(() => (
6
+ <StartServer
7
+ document={({ assets, children, scripts }) => {
8
+ const lang = (() => {
9
+ const event = getRequestEvent()
10
+ const header = event?.request.headers.get("accept-language")
11
+ if (!header) return "en"
12
+ for (const item of header.split(",")) {
13
+ const value = item.trim().split(";")[0]?.toLowerCase()
14
+ if (!value) continue
15
+ if (value.startsWith("zh")) return "zh"
16
+ if (value.startsWith("en")) return "en"
17
+ }
18
+ return "en"
19
+ })()
20
+
21
+ return (
22
+ <html lang={lang}>
23
+ <head>
24
+ <meta charset="utf-8" />
25
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
26
+ <title>OpenCode</title>
27
+ <meta name="theme-color" content="#F8F7F7" />
28
+ {assets}
29
+ </head>
30
+ <body class="antialiased overscroll-none text-12-regular">
31
+ <div id="app">{children}</div>
32
+ {scripts}
33
+ </body>
34
+ </html>
35
+ )
36
+ }}
37
+ />
38
+ ))
@@ -0,0 +1,5 @@
1
+ /// <reference types="@solidjs/start/env" />
2
+
3
+ export declare module "@solidjs/start/server" {
4
+ export type APIEvent = { request: Request }
5
+ }
@@ -0,0 +1,25 @@
1
+ import { A } from "@solidjs/router"
2
+
3
+ export default function NotFound() {
4
+ return (
5
+ <main class="text-center mx-auto text-gray-700 p-4">
6
+ <h1 class="max-6-xs text-6xl text-sky-700 font-thin uppercase my-16">Not Found</h1>
7
+ <p class="mt-8">
8
+ Visit{" "}
9
+ <a href="https://solidjs.com" target="_blank" class="text-sky-600 hover:underline">
10
+ solidjs.com
11
+ </a>{" "}
12
+ to learn how to build Solid apps.
13
+ </p>
14
+ <p class="my-4">
15
+ <A href="/" class="text-sky-600 hover:underline">
16
+ Home
17
+ </A>
18
+ {" - "}
19
+ <A href="/about" class="text-sky-600 hover:underline">
20
+ About Page
21
+ </A>
22
+ </p>
23
+ </main>
24
+ )
25
+ }