✨ Major Features: - Native session management without OpenCode binary - Provider routing: OpenCode Zen (free), Qwen OAuth, Z.AI - Streaming chat with tool execution loop - Mode detection API (/api/meta/mode) - MCP integration fix (resolved infinite loading) - NomadArch Native option in UI with comparison info 🆓 Free Models (No API Key): - GPT-5 Nano (400K context) - Grok Code Fast 1 (256K context) - GLM-4.7 (205K context) - Doubao Seed Code (256K context) - Big Pickle (200K context) 📦 New Files: - session-store.ts: Native session persistence - native-sessions.ts: REST API for sessions - lite-mode.ts: UI mode detection client - native-sessions.ts (UI): SolidJS store 🔧 Updated: - All installers: Optional binary download - All launchers: Mode detection display - Binary selector: Added NomadArch Native option - README: Binary-Free Mode documentation
99 lines
2.7 KiB
TypeScript
99 lines
2.7 KiB
TypeScript
import { createEffect, createSignal, createRoot } from "solid-js"
|
|
import type { LatestReleaseInfo, WorkspaceEventPayload } from "../../../server/src/api-types"
|
|
import { getServerMeta } from "../lib/server-meta"
|
|
import { serverEvents } from "../lib/server-events"
|
|
import { showToastNotification, ToastHandle } from "../lib/notifications"
|
|
import { getLogger } from "../lib/logger"
|
|
import { hasInstances, showFolderSelection } from "./ui"
|
|
|
|
const log = getLogger("actions")
|
|
|
|
const [availableRelease, setAvailableRelease] = createSignal<LatestReleaseInfo | null>(null)
|
|
|
|
let initialized = false
|
|
let visibilityEffectInitialized = false
|
|
let activeToast: ToastHandle | null = null
|
|
let activeToastVersion: string | null = null
|
|
|
|
function dismissActiveToast() {
|
|
if (activeToast) {
|
|
activeToast.dismiss()
|
|
activeToast = null
|
|
activeToastVersion = null
|
|
}
|
|
}
|
|
|
|
function ensureVisibilityEffect() {
|
|
if (visibilityEffectInitialized) {
|
|
return
|
|
}
|
|
visibilityEffectInitialized = true
|
|
|
|
// Use createRoot to properly scope this effect
|
|
createRoot(() => {
|
|
createEffect(() => {
|
|
const release = availableRelease()
|
|
const shouldShow = Boolean(release) && (!hasInstances() || showFolderSelection())
|
|
|
|
if (!shouldShow || !release) {
|
|
dismissActiveToast()
|
|
return
|
|
}
|
|
|
|
if (!activeToast || activeToastVersion !== release.version) {
|
|
dismissActiveToast()
|
|
activeToast = showToastNotification({
|
|
title: `NomadArch ${release.version}`,
|
|
message: release.channel === "dev" ? "Dev release build available." : "New stable build on GitHub.",
|
|
variant: "info",
|
|
duration: Number.POSITIVE_INFINITY,
|
|
position: "bottom-right",
|
|
action: {
|
|
label: "View release",
|
|
href: release.url,
|
|
},
|
|
})
|
|
activeToastVersion = release.version
|
|
}
|
|
})
|
|
})
|
|
}
|
|
|
|
export function initReleaseNotifications() {
|
|
if (initialized) {
|
|
return
|
|
}
|
|
initialized = true
|
|
|
|
ensureVisibilityEffect()
|
|
void refreshFromMeta()
|
|
|
|
serverEvents.on("app.releaseAvailable", (event) => {
|
|
const typedEvent = event as Extract<WorkspaceEventPayload, { type: "app.releaseAvailable" }>
|
|
applyRelease(typedEvent.release)
|
|
})
|
|
}
|
|
|
|
async function refreshFromMeta() {
|
|
try {
|
|
const meta = await getServerMeta(true)
|
|
if (meta.latestRelease) {
|
|
applyRelease(meta.latestRelease)
|
|
}
|
|
} catch (error) {
|
|
log.warn("Unable to load server metadata for release info", error)
|
|
}
|
|
}
|
|
|
|
function applyRelease(release: LatestReleaseInfo | null | undefined) {
|
|
if (!release) {
|
|
setAvailableRelease(null)
|
|
return
|
|
}
|
|
setAvailableRelease(release)
|
|
}
|
|
|
|
export function useAvailableRelease() {
|
|
return availableRelease
|
|
}
|