mirror of
https://github.com/Nezumi-2711/9router.git
synced 2026-09-22 13:38:31 +00:00
Feat : qoder provider
This commit is contained in:
@@ -116,9 +116,11 @@ async function fetchQoderCatalogRaw(credentials, signal, proxyOptions = null) {
|
|||||||
if (!entry || typeof entry !== "object") continue;
|
if (!entry || typeof entry !== "object") continue;
|
||||||
const key = entry.key;
|
const key = entry.key;
|
||||||
if (!key) continue;
|
if (!key) continue;
|
||||||
if (entry.enable === false) continue;
|
|
||||||
|
|
||||||
|
// Always cache the config — chat needs model_config even for UI-hidden
|
||||||
|
// models (enable:false). Upstream still accepts chat for these keys.
|
||||||
rawConfigs.set(key, entry);
|
rawConfigs.set(key, entry);
|
||||||
|
if (entry.enable === false) continue;
|
||||||
|
|
||||||
const display = entry.display_name || key;
|
const display = entry.display_name || key;
|
||||||
const ctx = Number(entry.max_input_tokens) || 131_072;
|
const ctx = Number(entry.max_input_tokens) || 131_072;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Card } from "@/shared/components";
|
|||||||
// Derive simple connected/configured/not-installed status from API payload
|
// Derive simple connected/configured/not-installed status from API payload
|
||||||
function getStatus(status) {
|
function getStatus(status) {
|
||||||
if (!status) return { label: "Unknown", cls: "bg-gray-500/10 text-gray-500" };
|
if (!status) return { label: "Unknown", cls: "bg-gray-500/10 text-gray-500" };
|
||||||
if (!status.installed) return { label: "Not installed", cls: "bg-red-500/10 text-red-600 dark:text-red-400" };
|
if (!status.installed) return { label: "Not installed", cls: "bg-gray-500/10 text-gray-500" };
|
||||||
if (status.has9Router) return { label: "Connected", cls: "bg-green-500/10 text-green-600 dark:text-green-400" };
|
if (status.has9Router) return { label: "Connected", cls: "bg-green-500/10 text-green-600 dark:text-green-400" };
|
||||||
return { label: "Not configured", cls: "bg-yellow-500/10 text-yellow-600 dark:text-yellow-400" };
|
return { label: "Not configured", cls: "bg-yellow-500/10 text-yellow-600 dark:text-yellow-400" };
|
||||||
}
|
}
|
||||||
@@ -18,7 +18,7 @@ export default function ToolSummaryCard({ toolId, tool, status }) {
|
|||||||
<Link href={`/dashboard/cli-tools/${toolId}`} className="block">
|
<Link href={`/dashboard/cli-tools/${toolId}`} className="block">
|
||||||
<Card padding="sm" className="h-full overflow-hidden hover:border-primary/50 transition-colors cursor-pointer">
|
<Card padding="sm" className="h-full overflow-hidden hover:border-primary/50 transition-colors cursor-pointer">
|
||||||
<div className="flex h-full flex-col gap-2">
|
<div className="flex h-full flex-col gap-2">
|
||||||
<div className="flex items-start gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="size-8 flex items-center justify-center shrink-0">
|
<div className="size-8 flex items-center justify-center shrink-0">
|
||||||
{tool.image ? (
|
{tool.image ? (
|
||||||
<Image src={tool.image} alt={tool.name} width={32} height={32} className="size-8 object-contain rounded-lg" sizes="32px" onError={(e) => { e.target.style.display = "none"; }} />
|
<Image src={tool.image} alt={tool.name} width={32} height={32} className="size-8 object-contain rounded-lg" sizes="32px" onError={(e) => { e.target.style.display = "none"; }} />
|
||||||
@@ -28,11 +28,10 @@ export default function ToolSummaryCard({ toolId, tool, status }) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="min-w-0 flex-1">
|
<div className="min-w-0 flex-1">
|
||||||
<h3 className="font-medium text-sm truncate">{tool.name}</h3>
|
<h3 className="font-medium text-sm truncate">{tool.name}</h3>
|
||||||
<span className={`inline-block mt-0.5 px-1.5 py-0.5 text-[10px] font-medium rounded-full ${s.cls}`}>{s.label}</span>
|
<span className={`inline-block mt-1 px-1.5 py-0.5 text-[10px] font-medium rounded-full ${s.cls}`}>{s.label}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="material-symbols-outlined text-text-muted text-[18px] shrink-0">chevron_right</span>
|
<span className="material-symbols-outlined text-text-muted text-[18px] shrink-0">chevron_right</span>
|
||||||
</div>
|
</div>
|
||||||
<p className="text-xs text-text-muted line-clamp-2">{tool.description}</p>
|
|
||||||
</div>
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -374,6 +374,14 @@ export async function spawnQuickTunnel(localPort, onUrlUpdate) {
|
|||||||
child.on("exit", (code, signal) => {
|
child.on("exit", (code, signal) => {
|
||||||
cloudflaredProcess = null;
|
cloudflaredProcess = null;
|
||||||
clearPid();
|
clearPid();
|
||||||
|
// Deliberate kill (restart/disable) — exit silently, no error noise
|
||||||
|
if (intentionalKill) {
|
||||||
|
intentionalKill = false;
|
||||||
|
clearTimeout(timeout);
|
||||||
|
cleanup();
|
||||||
|
if (!resolved) { resolved = true; reject(new Error("cloudflared killed")); }
|
||||||
|
return;
|
||||||
|
}
|
||||||
console.log(`[Tunnel] cloudflared exit code=${code} signal=${signal}`);
|
console.log(`[Tunnel] cloudflared exit code=${code} signal=${signal}`);
|
||||||
if (!resolved) {
|
if (!resolved) {
|
||||||
resolved = true;
|
resolved = true;
|
||||||
@@ -389,7 +397,6 @@ export async function spawnQuickTunnel(localPort, onUrlUpdate) {
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (intentionalKill) { intentionalKill = false; cleanup(); return; }
|
|
||||||
if (unexpectedExitHandler) unexpectedExitHandler();
|
if (unexpectedExitHandler) unexpectedExitHandler();
|
||||||
cleanup();
|
cleanup();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -100,7 +100,10 @@ export async function enableTunnel(localPort = 20128) {
|
|||||||
console.log("[Tunnel] enable success");
|
console.log("[Tunnel] enable success");
|
||||||
return { success: true, tunnelUrl, shortId, publicUrl };
|
return { success: true, tunnelUrl, shortId, publicUrl };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(`[Tunnel] enable error: ${e.message}`);
|
// Suppress noise when spawn was deliberately killed (restart/disable superseded it)
|
||||||
|
if (!/cloudflared killed|tunnel cancelled/.test(e.message)) {
|
||||||
|
console.error(`[Tunnel] enable error: ${e.message}`);
|
||||||
|
}
|
||||||
throw e;
|
throw e;
|
||||||
} finally {
|
} finally {
|
||||||
svc.spawnInProgress = false;
|
svc.spawnInProgress = false;
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ export {
|
|||||||
export {
|
export {
|
||||||
isTailscaleInstalled,
|
isTailscaleInstalled,
|
||||||
isTailscaleRunning,
|
isTailscaleRunning,
|
||||||
|
isTailscaleRunningStrict,
|
||||||
isTailscaleLoggedIn,
|
isTailscaleLoggedIn,
|
||||||
installTailscale,
|
installTailscale,
|
||||||
startLogin,
|
startLogin,
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
isTunnelManuallyDisabled, isTunnelReconnecting, isTailscaleReconnecting,
|
isTunnelManuallyDisabled, isTunnelReconnecting, isTailscaleReconnecting,
|
||||||
getTunnelService, getTailscaleService, setTunnelUnexpectedExitCallback,
|
getTunnelService, getTailscaleService, setTunnelUnexpectedExitCallback,
|
||||||
killCloudflared, isCloudflaredRunning, ensureCloudflared,
|
killCloudflared, isCloudflaredRunning, ensureCloudflared,
|
||||||
isTailscaleRunning,
|
isTailscaleRunning, isTailscaleRunningStrict,
|
||||||
loadState,
|
loadState,
|
||||||
checkInternet,
|
checkInternet,
|
||||||
probeCloudflareAlive, probeTailscaleAlive,
|
probeCloudflareAlive, probeTailscaleAlive,
|
||||||
@@ -174,7 +174,9 @@ async function safeRestartTunnel(reason) {
|
|||||||
svc.lastRestartAt = Date.now();
|
svc.lastRestartAt = Date.now();
|
||||||
console.log("[Tunnel] restart success");
|
console.log("[Tunnel] restart success");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log("[Tunnel] restart failed:", err.message);
|
if (!/cloudflared killed|tunnel cancelled/.test(err.message)) {
|
||||||
|
console.log("[Tunnel] restart failed:", err.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,8 +187,10 @@ async function safeRestartTailscale(reason) {
|
|||||||
if (svc.cancelToken.cancelled) return;
|
if (svc.cancelToken.cancelled) return;
|
||||||
if (svc.spawnInProgress) return;
|
if (svc.spawnInProgress) return;
|
||||||
|
|
||||||
// Tailscale daemon is OS-level with built-in reconnect; trust it when running
|
// Tailscale daemon is OS-level with built-in reconnect; trust it when running.
|
||||||
if (isTailscaleRunning()) return;
|
// Startup uses strict probe — cached state is cold after process/dev reload.
|
||||||
|
const running = reason === "startup" ? isTailscaleRunningStrict() : isTailscaleRunning();
|
||||||
|
if (running) return;
|
||||||
|
|
||||||
const force = FORCE_RESTART_REASONS.test(reason);
|
const force = FORCE_RESTART_REASONS.test(reason);
|
||||||
if (!force && Date.now() - svc.lastRestartAt < RESTART_COOLDOWN_MS) {
|
if (!force && Date.now() - svc.lastRestartAt < RESTART_COOLDOWN_MS) {
|
||||||
|
|||||||
Reference in New Issue
Block a user