fix(app): more startup efficiency (#18985)
parent
5e684c6e80
commit
98b3340cee
|
|
@ -572,6 +572,7 @@ export const PromptInput: Component<PromptInputProps> = (props) => {
|
||||||
const open = recent()
|
const open = recent()
|
||||||
const seen = new Set(open)
|
const seen = new Set(open)
|
||||||
const pinned: AtOption[] = open.map((path) => ({ type: "file", path, display: path, recent: true }))
|
const pinned: AtOption[] = open.map((path) => ({ type: "file", path, display: path, recent: true }))
|
||||||
|
if (!query.trim()) return [...agents, ...pinned]
|
||||||
const paths = await files.searchFilesAndDirectories(query)
|
const paths = await files.searchFilesAndDirectories(query)
|
||||||
const fileOptions: AtOption[] = paths
|
const fileOptions: AtOption[] = paths
|
||||||
.filter((path) => !seen.has(path))
|
.filter((path) => !seen.has(path))
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,47 @@ type GlobalStore = {
|
||||||
reload: undefined | "pending" | "complete"
|
reload: undefined | "pending" | "complete"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function waitForPaint() {
|
||||||
|
return new Promise<void>((resolve) => {
|
||||||
|
let done = false
|
||||||
|
const finish = () => {
|
||||||
|
if (done) return
|
||||||
|
done = true
|
||||||
|
resolve()
|
||||||
|
}
|
||||||
|
const timer = setTimeout(finish, 50)
|
||||||
|
if (typeof requestAnimationFrame !== "function") return
|
||||||
|
requestAnimationFrame(() => {
|
||||||
|
clearTimeout(timer)
|
||||||
|
finish()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function errors(list: PromiseSettledResult<unknown>[]) {
|
||||||
|
return list.filter((item): item is PromiseRejectedResult => item.status === "rejected").map((item) => item.reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
function runAll(list: Array<() => Promise<unknown>>) {
|
||||||
|
return Promise.allSettled(list.map((item) => item()))
|
||||||
|
}
|
||||||
|
|
||||||
|
function showErrors(input: {
|
||||||
|
errors: unknown[]
|
||||||
|
title: string
|
||||||
|
translate: (key: string, vars?: Record<string, string | number>) => string
|
||||||
|
formatMoreCount: (count: number) => string
|
||||||
|
}) {
|
||||||
|
if (input.errors.length === 0) return
|
||||||
|
const message = formatServerError(input.errors[0], input.translate)
|
||||||
|
const more = input.errors.length > 1 ? input.formatMoreCount(input.errors.length - 1) : ""
|
||||||
|
showToast({
|
||||||
|
variant: "error",
|
||||||
|
title: input.title,
|
||||||
|
description: message + more,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export async function bootstrapGlobal(input: {
|
export async function bootstrapGlobal(input: {
|
||||||
globalSDK: OpencodeClient
|
globalSDK: OpencodeClient
|
||||||
requestFailedTitle: string
|
requestFailedTitle: string
|
||||||
|
|
@ -38,45 +79,54 @@ export async function bootstrapGlobal(input: {
|
||||||
formatMoreCount: (count: number) => string
|
formatMoreCount: (count: number) => string
|
||||||
setGlobalStore: SetStoreFunction<GlobalStore>
|
setGlobalStore: SetStoreFunction<GlobalStore>
|
||||||
}) {
|
}) {
|
||||||
const tasks = [
|
const fast = [
|
||||||
retry(() =>
|
() =>
|
||||||
input.globalSDK.path.get().then((x) => {
|
retry(() =>
|
||||||
input.setGlobalStore("path", x.data!)
|
input.globalSDK.path.get().then((x) => {
|
||||||
}),
|
input.setGlobalStore("path", x.data!)
|
||||||
),
|
}),
|
||||||
retry(() =>
|
),
|
||||||
input.globalSDK.global.config.get().then((x) => {
|
() =>
|
||||||
input.setGlobalStore("config", x.data!)
|
retry(() =>
|
||||||
}),
|
input.globalSDK.global.config.get().then((x) => {
|
||||||
),
|
input.setGlobalStore("config", x.data!)
|
||||||
retry(() =>
|
}),
|
||||||
input.globalSDK.project.list().then((x) => {
|
),
|
||||||
const projects = (x.data ?? [])
|
() =>
|
||||||
.filter((p) => !!p?.id)
|
retry(() =>
|
||||||
.filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
|
input.globalSDK.provider.list().then((x) => {
|
||||||
.slice()
|
input.setGlobalStore("provider", normalizeProviderList(x.data!))
|
||||||
.sort((a, b) => cmp(a.id, b.id))
|
}),
|
||||||
input.setGlobalStore("project", projects)
|
),
|
||||||
}),
|
|
||||||
),
|
|
||||||
retry(() =>
|
|
||||||
input.globalSDK.provider.list().then((x) => {
|
|
||||||
input.setGlobalStore("provider", normalizeProviderList(x.data!))
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
||||||
const results = await Promise.allSettled(tasks)
|
const slow = [
|
||||||
const errors = results.filter((r): r is PromiseRejectedResult => r.status === "rejected").map((r) => r.reason)
|
() =>
|
||||||
if (errors.length) {
|
retry(() =>
|
||||||
const message = formatServerError(errors[0], input.translate)
|
input.globalSDK.project.list().then((x) => {
|
||||||
const more = errors.length > 1 ? input.formatMoreCount(errors.length - 1) : ""
|
const projects = (x.data ?? [])
|
||||||
showToast({
|
.filter((p) => !!p?.id)
|
||||||
variant: "error",
|
.filter((p) => !!p.worktree && !p.worktree.includes("opencode-test"))
|
||||||
title: input.requestFailedTitle,
|
.slice()
|
||||||
description: message + more,
|
.sort((a, b) => cmp(a.id, b.id))
|
||||||
})
|
input.setGlobalStore("project", projects)
|
||||||
}
|
}),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
showErrors({
|
||||||
|
errors: errors(await runAll(fast)),
|
||||||
|
title: input.requestFailedTitle,
|
||||||
|
translate: input.translate,
|
||||||
|
formatMoreCount: input.formatMoreCount,
|
||||||
|
})
|
||||||
|
await waitForPaint()
|
||||||
|
showErrors({
|
||||||
|
errors: errors(await runAll(slow)),
|
||||||
|
title: input.requestFailedTitle,
|
||||||
|
translate: input.translate,
|
||||||
|
formatMoreCount: input.formatMoreCount,
|
||||||
|
})
|
||||||
input.setGlobalStore("ready", true)
|
input.setGlobalStore("ready", true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -119,95 +169,113 @@ export async function bootstrapDirectory(input: {
|
||||||
}
|
}
|
||||||
if (loading) input.setStore("status", "partial")
|
if (loading) input.setStore("status", "partial")
|
||||||
|
|
||||||
const results = await Promise.allSettled([
|
const fast = [
|
||||||
seededProject
|
() =>
|
||||||
? Promise.resolve()
|
seededProject
|
||||||
: retry(() => input.sdk.project.current()).then((x) => input.setStore("project", x.data!.id)),
|
? Promise.resolve()
|
||||||
retry(() =>
|
: retry(() => input.sdk.project.current()).then((x) => input.setStore("project", x.data!.id)),
|
||||||
input.sdk.provider.list().then((x) => {
|
() => retry(() => input.sdk.app.agents().then((x) => input.setStore("agent", x.data ?? []))),
|
||||||
input.setStore("provider", normalizeProviderList(x.data!))
|
() => retry(() => input.sdk.config.get().then((x) => input.setStore("config", x.data!))),
|
||||||
}),
|
() =>
|
||||||
),
|
retry(() =>
|
||||||
retry(() => input.sdk.app.agents().then((x) => input.setStore("agent", x.data ?? []))),
|
input.sdk.path.get().then((x) => {
|
||||||
retry(() => input.sdk.config.get().then((x) => input.setStore("config", x.data!))),
|
input.setStore("path", x.data!)
|
||||||
retry(() =>
|
const next = projectID(x.data?.directory ?? input.directory, input.global.project)
|
||||||
input.sdk.path.get().then((x) => {
|
if (next) input.setStore("project", next)
|
||||||
input.setStore("path", x.data!)
|
}),
|
||||||
const next = projectID(x.data?.directory ?? input.directory, input.global.project)
|
),
|
||||||
if (next) input.setStore("project", next)
|
() => retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))),
|
||||||
}),
|
() =>
|
||||||
),
|
retry(() =>
|
||||||
retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? []))),
|
input.sdk.vcs.get().then((x) => {
|
||||||
retry(() => input.sdk.session.status().then((x) => input.setStore("session_status", x.data!))),
|
const next = x.data ?? input.store.vcs
|
||||||
input.loadSessions(input.directory),
|
input.setStore("vcs", next)
|
||||||
retry(() => input.sdk.mcp.status().then((x) => input.setStore("mcp", x.data!))),
|
if (next?.branch) input.vcsCache.setStore("value", next)
|
||||||
retry(() => input.sdk.lsp.status().then((x) => input.setStore("lsp", x.data!))),
|
}),
|
||||||
retry(() =>
|
),
|
||||||
input.sdk.vcs.get().then((x) => {
|
() => retry(() => input.sdk.command.list().then((x) => input.setStore("command", x.data ?? []))),
|
||||||
const next = x.data ?? input.store.vcs
|
() =>
|
||||||
input.setStore("vcs", next)
|
retry(() =>
|
||||||
if (next?.branch) input.vcsCache.setStore("value", next)
|
input.sdk.permission.list().then((x) => {
|
||||||
}),
|
const grouped = groupBySession(
|
||||||
),
|
(x.data ?? []).filter((perm): perm is PermissionRequest => !!perm?.id && !!perm.sessionID),
|
||||||
retry(() =>
|
)
|
||||||
input.sdk.permission.list().then((x) => {
|
batch(() => {
|
||||||
const grouped = groupBySession(
|
for (const sessionID of Object.keys(input.store.permission)) {
|
||||||
(x.data ?? []).filter((perm): perm is PermissionRequest => !!perm?.id && !!perm.sessionID),
|
if (grouped[sessionID]) continue
|
||||||
)
|
input.setStore("permission", sessionID, [])
|
||||||
batch(() => {
|
}
|
||||||
for (const sessionID of Object.keys(input.store.permission)) {
|
for (const [sessionID, permissions] of Object.entries(grouped)) {
|
||||||
if (grouped[sessionID]) continue
|
input.setStore(
|
||||||
input.setStore("permission", sessionID, [])
|
"permission",
|
||||||
}
|
sessionID,
|
||||||
for (const [sessionID, permissions] of Object.entries(grouped)) {
|
reconcile(
|
||||||
input.setStore(
|
permissions.filter((p) => !!p?.id).sort((a, b) => cmp(a.id, b.id)),
|
||||||
"permission",
|
{ key: "id" },
|
||||||
sessionID,
|
),
|
||||||
reconcile(
|
)
|
||||||
permissions.filter((p) => !!p?.id).sort((a, b) => cmp(a.id, b.id)),
|
}
|
||||||
{ key: "id" },
|
})
|
||||||
),
|
}),
|
||||||
)
|
),
|
||||||
}
|
() =>
|
||||||
})
|
retry(() =>
|
||||||
}),
|
input.sdk.question.list().then((x) => {
|
||||||
),
|
const grouped = groupBySession((x.data ?? []).filter((q): q is QuestionRequest => !!q?.id && !!q.sessionID))
|
||||||
retry(() =>
|
batch(() => {
|
||||||
input.sdk.question.list().then((x) => {
|
for (const sessionID of Object.keys(input.store.question)) {
|
||||||
const grouped = groupBySession((x.data ?? []).filter((q): q is QuestionRequest => !!q?.id && !!q.sessionID))
|
if (grouped[sessionID]) continue
|
||||||
batch(() => {
|
input.setStore("question", sessionID, [])
|
||||||
for (const sessionID of Object.keys(input.store.question)) {
|
}
|
||||||
if (grouped[sessionID]) continue
|
for (const [sessionID, questions] of Object.entries(grouped)) {
|
||||||
input.setStore("question", sessionID, [])
|
input.setStore(
|
||||||
}
|
"question",
|
||||||
for (const [sessionID, questions] of Object.entries(grouped)) {
|
sessionID,
|
||||||
input.setStore(
|
reconcile(
|
||||||
"question",
|
questions.filter((q) => !!q?.id).sort((a, b) => cmp(a.id, b.id)),
|
||||||
sessionID,
|
{ key: "id" },
|
||||||
reconcile(
|
),
|
||||||
questions.filter((q) => !!q?.id).sort((a, b) => cmp(a.id, b.id)),
|
)
|
||||||
{ key: "id" },
|
}
|
||||||
),
|
})
|
||||||
)
|
}),
|
||||||
}
|
),
|
||||||
})
|
]
|
||||||
}),
|
|
||||||
),
|
|
||||||
])
|
|
||||||
|
|
||||||
const errors = results
|
const slow = [
|
||||||
.filter((item): item is PromiseRejectedResult => item.status === "rejected")
|
() =>
|
||||||
.map((item) => item.reason)
|
retry(() =>
|
||||||
if (errors.length > 0) {
|
input.sdk.provider.list().then((x) => {
|
||||||
console.error("Failed to bootstrap instance", errors[0])
|
input.setStore("provider", normalizeProviderList(x.data!))
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
() => Promise.resolve(input.loadSessions(input.directory)),
|
||||||
|
() => retry(() => input.sdk.mcp.status().then((x) => input.setStore("mcp", x.data!))),
|
||||||
|
() => retry(() => input.sdk.lsp.status().then((x) => input.setStore("lsp", x.data!))),
|
||||||
|
]
|
||||||
|
|
||||||
|
const errs = errors(await runAll(fast))
|
||||||
|
if (errs.length > 0) {
|
||||||
|
console.error("Failed to bootstrap instance", errs[0])
|
||||||
const project = getFilename(input.directory)
|
const project = getFilename(input.directory)
|
||||||
showToast({
|
showToast({
|
||||||
variant: "error",
|
variant: "error",
|
||||||
title: input.translate("toast.project.reloadFailed.title", { project }),
|
title: input.translate("toast.project.reloadFailed.title", { project }),
|
||||||
description: formatServerError(errors[0], input.translate),
|
description: formatServerError(errs[0], input.translate),
|
||||||
})
|
})
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (loading) input.setStore("status", "complete")
|
await waitForPaint()
|
||||||
|
const slowErrs = errors(await runAll(slow))
|
||||||
|
if (slowErrs.length > 0) {
|
||||||
|
console.error("Failed to finish bootstrap instance", slowErrs[0])
|
||||||
|
const project = getFilename(input.directory)
|
||||||
|
showToast({
|
||||||
|
variant: "error",
|
||||||
|
title: input.translate("toast.project.reloadFailed.title", { project }),
|
||||||
|
description: formatServerError(slowErrs[0], input.translate),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading && errs.length === 0 && slowErrs.length === 0) input.setStore("status", "complete")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -118,8 +118,11 @@ export const { use: useSettings, provider: SettingsProvider } = createSimpleCont
|
||||||
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
if (typeof document === "undefined") return
|
if (typeof document === "undefined") return
|
||||||
void loadFont().then((x) => x.ensureMonoFont(store.appearance?.font))
|
const id = store.appearance?.font ?? defaultSettings.appearance.font
|
||||||
document.documentElement.style.setProperty("--font-family-mono", monoFontFamily(store.appearance?.font))
|
if (id !== defaultSettings.appearance.font) {
|
||||||
|
void loadFont().then((x) => x.ensureMonoFont(id))
|
||||||
|
}
|
||||||
|
document.documentElement.style.setProperty("--font-family-mono", monoFontFamily(id))
|
||||||
})
|
})
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
||||||
|
|
@ -180,7 +180,8 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||||
return globalSync.child(directory)
|
return globalSync.child(directory)
|
||||||
}
|
}
|
||||||
const absolute = (path: string) => (current()[0].path.directory + "/" + path).replace("//", "/")
|
const absolute = (path: string) => (current()[0].path.directory + "/" + path).replace("//", "/")
|
||||||
const messagePageSize = 200
|
const initialMessagePageSize = 80
|
||||||
|
const historyMessagePageSize = 200
|
||||||
const inflight = new Map<string, Promise<void>>()
|
const inflight = new Map<string, Promise<void>>()
|
||||||
const inflightDiff = new Map<string, Promise<void>>()
|
const inflightDiff = new Map<string, Promise<void>>()
|
||||||
const inflightTodo = new Map<string, Promise<void>>()
|
const inflightTodo = new Map<string, Promise<void>>()
|
||||||
|
|
@ -463,7 +464,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||||
const cached = store.message[sessionID] !== undefined && meta.limit[key] !== undefined
|
const cached = store.message[sessionID] !== undefined && meta.limit[key] !== undefined
|
||||||
if (cached && hasSession && !opts?.force) return
|
if (cached && hasSession && !opts?.force) return
|
||||||
|
|
||||||
const limit = meta.limit[key] ?? messagePageSize
|
const limit = meta.limit[key] ?? initialMessagePageSize
|
||||||
const sessionReq =
|
const sessionReq =
|
||||||
hasSession && !opts?.force
|
hasSession && !opts?.force
|
||||||
? Promise.resolve()
|
? Promise.resolve()
|
||||||
|
|
@ -560,7 +561,7 @@ export const { use: useSync, provider: SyncProvider } = createSimpleContext({
|
||||||
const [, setStore] = globalSync.child(directory)
|
const [, setStore] = globalSync.child(directory)
|
||||||
touch(directory, setStore, sessionID)
|
touch(directory, setStore, sessionID)
|
||||||
const key = keyFor(directory, sessionID)
|
const key = keyFor(directory, sessionID)
|
||||||
const step = count ?? messagePageSize
|
const step = count ?? historyMessagePageSize
|
||||||
if (meta.loading[key]) return
|
if (meta.loading[key]) return
|
||||||
if (meta.complete[key]) return
|
if (meta.complete[key]) return
|
||||||
const before = meta.cursor[key]
|
const before = meta.cursor[key]
|
||||||
|
|
|
||||||
|
|
@ -113,6 +113,14 @@ export default function Home() {
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</Match>
|
</Match>
|
||||||
|
<Match when={!sync.ready}>
|
||||||
|
<div class="mt-30 mx-auto flex flex-col items-center gap-3">
|
||||||
|
<div class="text-12-regular text-text-weak">{language.t("common.loading")}</div>
|
||||||
|
<Button class="px-3" onClick={chooseProject}>
|
||||||
|
{language.t("command.project.open")}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Match>
|
||||||
<Match when={true}>
|
<Match when={true}>
|
||||||
<div class="mt-30 mx-auto flex flex-col items-center gap-3">
|
<div class="mt-30 mx-auto flex flex-col items-center gap-3">
|
||||||
<Icon name="folder-add-left" size="large" />
|
<Icon name="folder-add-left" size="large" />
|
||||||
|
|
|
||||||
|
|
@ -1179,8 +1179,6 @@ export default function Page() {
|
||||||
on(
|
on(
|
||||||
() => sdk.directory,
|
() => sdk.directory,
|
||||||
() => {
|
() => {
|
||||||
void file.tree.list("")
|
|
||||||
|
|
||||||
const tab = activeFileTab()
|
const tab = activeFileTab()
|
||||||
if (!tab) return
|
if (!tab) return
|
||||||
const path = file.pathFromTab(tab)
|
const path = file.pathFromTab(tab)
|
||||||
|
|
@ -1635,6 +1633,9 @@ export default function Page() {
|
||||||
sessionID: () => params.id,
|
sessionID: () => params.id,
|
||||||
messagesReady,
|
messagesReady,
|
||||||
visibleUserMessages,
|
visibleUserMessages,
|
||||||
|
historyMore,
|
||||||
|
historyLoading,
|
||||||
|
loadMore: (sessionID) => sync.session.history.loadMore(sessionID),
|
||||||
turnStart: historyWindow.turnStart,
|
turnStart: historyWindow.turnStart,
|
||||||
currentMessageId: () => store.messageId,
|
currentMessageId: () => store.messageId,
|
||||||
pendingMessage: () => ui.pendingMessage,
|
pendingMessage: () => ui.pendingMessage,
|
||||||
|
|
@ -1706,7 +1707,7 @@ export default function Page() {
|
||||||
<div class="flex-1 min-h-0 overflow-hidden">
|
<div class="flex-1 min-h-0 overflow-hidden">
|
||||||
<Switch>
|
<Switch>
|
||||||
<Match when={params.id}>
|
<Match when={params.id}>
|
||||||
<Show when={lastUserMessage()}>
|
<Show when={messagesReady()}>
|
||||||
<MessageTimeline
|
<MessageTimeline
|
||||||
mobileChanges={mobileChanges()}
|
mobileChanges={mobileChanges()}
|
||||||
mobileFallback={reviewContent({
|
mobileFallback={reviewContent({
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,9 @@ export const useSessionHashScroll = (input: {
|
||||||
sessionID: () => string | undefined
|
sessionID: () => string | undefined
|
||||||
messagesReady: () => boolean
|
messagesReady: () => boolean
|
||||||
visibleUserMessages: () => UserMessage[]
|
visibleUserMessages: () => UserMessage[]
|
||||||
|
historyMore: () => boolean
|
||||||
|
historyLoading: () => boolean
|
||||||
|
loadMore: (sessionID: string) => Promise<void>
|
||||||
turnStart: () => number
|
turnStart: () => number
|
||||||
currentMessageId: () => string | undefined
|
currentMessageId: () => string | undefined
|
||||||
pendingMessage: () => string | undefined
|
pendingMessage: () => string | undefined
|
||||||
|
|
@ -181,6 +184,21 @@ export const useSessionHashScroll = (input: {
|
||||||
queue(() => scrollToMessage(msg, "auto"))
|
queue(() => scrollToMessage(msg, "auto"))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
createEffect(() => {
|
||||||
|
const sessionID = input.sessionID()
|
||||||
|
if (!sessionID || !input.messagesReady()) return
|
||||||
|
|
||||||
|
visibleUserMessages()
|
||||||
|
|
||||||
|
let targetId = input.pendingMessage()
|
||||||
|
if (!targetId && !clearing) targetId = messageIdFromHash(location.hash)
|
||||||
|
if (!targetId) return
|
||||||
|
if (messageById().has(targetId)) return
|
||||||
|
if (!input.historyMore() || input.historyLoading()) return
|
||||||
|
|
||||||
|
void input.loadMore(sessionID)
|
||||||
|
})
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (typeof window !== "undefined" && "scrollRestoration" in window.history) {
|
if (typeof window !== "undefined" && "scrollRestoration" in window.history) {
|
||||||
window.history.scrollRestoration = "manual"
|
window.history.scrollRestoration = "manual"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
|
import { readFileSync } from "node:fs"
|
||||||
import solidPlugin from "vite-plugin-solid"
|
import solidPlugin from "vite-plugin-solid"
|
||||||
import tailwindcss from "@tailwindcss/vite"
|
import tailwindcss from "@tailwindcss/vite"
|
||||||
import { fileURLToPath } from "url"
|
import { fileURLToPath } from "url"
|
||||||
|
|
||||||
|
const theme = fileURLToPath(new URL("./public/oc-theme-preload.js", import.meta.url))
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {import("vite").PluginOption}
|
* @type {import("vite").PluginOption}
|
||||||
*/
|
*/
|
||||||
|
|
@ -21,6 +24,15 @@ export default [
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "opencode-desktop:theme-preload",
|
||||||
|
transformIndexHtml(html) {
|
||||||
|
return html.replace(
|
||||||
|
'<script id="oc-theme-preload-script" src="/oc-theme-preload.js"></script>',
|
||||||
|
`<script id="oc-theme-preload-script">${readFileSync(theme, "utf8")}</script>`,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
solidPlugin(),
|
solidPlugin(),
|
||||||
]
|
]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue