:)
This commit is contained in:
+90
-5
@@ -68,6 +68,74 @@ export interface OllamaHostModel {
|
||||
modifiedAt: string | null;
|
||||
}
|
||||
|
||||
export interface AdminSettings {
|
||||
registrationEnabled: boolean;
|
||||
accessRequestEnabled: boolean;
|
||||
ocrEnabled: boolean;
|
||||
reportsEnabled: boolean;
|
||||
usernameChangeEnabled: boolean;
|
||||
ollamaTimeoutS: number;
|
||||
ollamaDefaultWeight: number;
|
||||
recoveryLimitMax: number;
|
||||
recoveryLimitWindowMin: number;
|
||||
}
|
||||
|
||||
export interface AdminSettingsPatch {
|
||||
registrationEnabled?: boolean;
|
||||
accessRequestEnabled?: boolean;
|
||||
ocrEnabled?: boolean;
|
||||
reportsEnabled?: boolean;
|
||||
usernameChangeEnabled?: boolean;
|
||||
ollamaTimeoutS?: number;
|
||||
ollamaDefaultWeight?: number;
|
||||
recoveryLimitMax?: number;
|
||||
recoveryLimitWindowMin?: number;
|
||||
}
|
||||
|
||||
export interface OllamaNode {
|
||||
id: string;
|
||||
name: string;
|
||||
host: string;
|
||||
weight: number;
|
||||
enabled: boolean;
|
||||
created_at: number;
|
||||
updated_at: number;
|
||||
}
|
||||
|
||||
export interface EffectiveOllamaNode {
|
||||
id: string;
|
||||
name: string;
|
||||
host: string;
|
||||
weight: number;
|
||||
}
|
||||
|
||||
export interface OllamaNodeStatus {
|
||||
reachable: boolean;
|
||||
host: string;
|
||||
version: string | null;
|
||||
latencyMs: number | null;
|
||||
models: OllamaHostModel[];
|
||||
running: { name: string; sizeVram: number; size: number; expiresAt: string | null }[];
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export interface OllamaStatsBucket {
|
||||
t: number;
|
||||
req: number;
|
||||
err: number;
|
||||
avgMs: number | null;
|
||||
promptTok: number;
|
||||
evalTok: number;
|
||||
}
|
||||
|
||||
export interface OllamaNodeStats {
|
||||
window: string;
|
||||
from: number;
|
||||
to: number;
|
||||
totals: { req: number; err: number; avgMs: number | null; promptTok: number; evalTok: number; tokPerSec: number | null };
|
||||
buckets: OllamaStatsBucket[];
|
||||
}
|
||||
|
||||
export interface OllamaStatus {
|
||||
enabled: boolean;
|
||||
host: string;
|
||||
@@ -188,9 +256,9 @@ export const api = {
|
||||
adminPatch: (id: string, patch: { username?: string; displayName?: string; avatarUrl?: string; theme?: string; email?: string }) =>
|
||||
req<{ user: User }>(`/api/admin/users/${id}`, { method: "PATCH", body: JSON.stringify(patch) }),
|
||||
|
||||
adminGetSettings: () => req<{ settings: { registrationEnabled: boolean; accessRequestEnabled: boolean; ocrEnabled: boolean; reportsEnabled: boolean; usernameChangeEnabled: boolean } }>("/api/admin/settings"),
|
||||
adminPatchSettings: (patch: { registrationEnabled?: boolean; accessRequestEnabled?: boolean; ocrEnabled?: boolean; reportsEnabled?: boolean; usernameChangeEnabled?: boolean }) =>
|
||||
req<{ settings: { registrationEnabled: boolean; accessRequestEnabled: boolean; ocrEnabled: boolean; reportsEnabled: boolean; usernameChangeEnabled: boolean } }>("/api/admin/settings", { method: "PATCH", body: JSON.stringify(patch) }),
|
||||
adminGetSettings: () => req<{ settings: AdminSettings }>("/api/admin/settings"),
|
||||
adminPatchSettings: (patch: AdminSettingsPatch) =>
|
||||
req<{ settings: AdminSettings }>("/api/admin/settings", { method: "PATCH", body: JSON.stringify(patch) }),
|
||||
|
||||
/** Admin: SMTP credentials viewer (password included, blur it by default) + connectivity. */
|
||||
adminMailStatus: () =>
|
||||
@@ -281,11 +349,12 @@ export const api = {
|
||||
ollamaPull: async (
|
||||
name: string,
|
||||
onProgress: (p: { status?: string; digest?: string; total?: number; completed?: number; error?: string }) => void,
|
||||
opts: { nodeId?: string } = {},
|
||||
): Promise<void> => {
|
||||
const res = await fetch("/api/admin/ollama/pull", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json", Authorization: `Bearer ${getToken()}` },
|
||||
body: JSON.stringify({ name }),
|
||||
body: JSON.stringify({ name, ...(opts.nodeId ? { nodeId: opts.nodeId } : {}) }),
|
||||
});
|
||||
if (res.status === 401) {
|
||||
clearToken();
|
||||
@@ -313,7 +382,23 @@ export const api = {
|
||||
},
|
||||
|
||||
/** Admin: delete a local Ollama model (frees disk on the Ollama host). */
|
||||
ollamaDelete: (name: string) => req<{ ok: boolean }>(`/api/admin/ollama/models/${encodeURIComponent(name)}`, { method: "DELETE" }),
|
||||
ollamaDelete: (name: string, nodeId?: string) =>
|
||||
req<{ ok: boolean }>(`/api/admin/ollama/models/${encodeURIComponent(name)}${nodeId ? `?node=${encodeURIComponent(nodeId)}` : ""}`, { method: "DELETE" }),
|
||||
|
||||
/** Admin: Ollama node pool (extra hosts with firewall-like weights). */
|
||||
ollamaNodes: () => req<{ nodes: OllamaNode[]; effective: EffectiveOllamaNode[] }>("/api/admin/ollama/nodes"),
|
||||
ollamaNodeCreate: (n: { name: string; host: string; weight: number; enabled: boolean }) =>
|
||||
req<{ node: OllamaNode }>("/api/admin/ollama/nodes", { method: "POST", body: JSON.stringify(n) }),
|
||||
ollamaNodePatch: (id: string, patch: { name?: string; host?: string; weight?: number; enabled?: boolean }) =>
|
||||
req<{ node: OllamaNode }>(`/api/admin/ollama/nodes/${encodeURIComponent(id)}`, { method: "PATCH", body: JSON.stringify(patch) }),
|
||||
ollamaNodeDelete: (id: string) =>
|
||||
req<{ ok: boolean }>(`/api/admin/ollama/nodes/${encodeURIComponent(id)}`, { method: "DELETE" }),
|
||||
ollamaNodeStatus: (id: string) =>
|
||||
req<OllamaNodeStatus>(`/api/admin/ollama/nodes/${encodeURIComponent(id)}/status`),
|
||||
ollamaNodeProbe: (host: string) =>
|
||||
req<OllamaNodeStatus>(`/api/admin/ollama/probe?host=${encodeURIComponent(host)}`),
|
||||
ollamaNodeStats: (id: string, window: string) =>
|
||||
req<{ stats: OllamaNodeStats }>(`/api/admin/ollama/nodes/${encodeURIComponent(id)}/stats?window=${encodeURIComponent(window)}`),
|
||||
|
||||
/** Admin: Ollama connectivity probe (reachability, version, on-disk models). */
|
||||
ollamaStatus: () => req<OllamaStatus>("/api/admin/ollama/status"),
|
||||
|
||||
@@ -491,6 +491,66 @@ const en = {
|
||||
"live.newReport": "New content report ({reason})",
|
||||
"live.adminReply": "Admin replied to your ticket",
|
||||
"live.ticketStatus": "Ticket {status}",
|
||||
"nodes.title": "Ollama nodes",
|
||||
"nodes.desc": "Extra Ollama hosts. Each generation picks a node by weight (firewall-like priority, heaviest first) and fails over to the next one on error. The default node comes from OLLAMA_HOST.",
|
||||
"nodes.add": "Add node",
|
||||
"nodes.edit": "Edit node",
|
||||
"nodes.name": "Name",
|
||||
"nodes.namePh": "gpu-box-2",
|
||||
"nodes.host": "Host",
|
||||
"nodes.hostPh": "http://10.0.0.5:11434",
|
||||
"nodes.hostHint": "http(s) base URL, no path. Must be reachable from this server.",
|
||||
"nodes.weight": "Weight",
|
||||
"nodes.weightHint": "0 disables traffic, higher gets more generations.",
|
||||
"nodes.enabled": "Enabled",
|
||||
"nodes.share": "{pct}%",
|
||||
"nodes.off": "off",
|
||||
"nodes.test": "Test",
|
||||
"nodes.probing": "Probing…",
|
||||
"nodes.testOk": "Reachable",
|
||||
"nodes.testFail": "Unreachable",
|
||||
"nodes.deleteTitle": "Delete this node?",
|
||||
"nodes.deleteMsg": "Generations will no longer be routed to this host.",
|
||||
"nodes.invalid": "Check the name, host and weight.",
|
||||
"nodes.dupHost": "This host is already registered.",
|
||||
"nodes.detail": "Node details",
|
||||
"nodes.running": "Running now",
|
||||
"nodes.vram": "VRAM",
|
||||
"nodes.diskModels": "On disk ({n})",
|
||||
"nodes.version": "Version",
|
||||
"nodes.latency": "Latency",
|
||||
"nodes.timeout": "Generation timeout",
|
||||
"nodes.timeoutDesc": "Max time for one Ollama generation before failing over. Applies to every node.",
|
||||
"nodes.timeoutUnit": "seconds (5–3600)",
|
||||
"nodes.defWeight": "Default node weight",
|
||||
"nodes.defWeightDesc": "Traffic share of the OLLAMA_HOST node. 0 excludes it when custom nodes exist.",
|
||||
"nodes.stats": "Load",
|
||||
"nodes.req": "requests",
|
||||
"nodes.err": "errors",
|
||||
"nodes.avg": "avg latency",
|
||||
"nodes.tps": "tokens/s",
|
||||
"nodes.tok": "tokens",
|
||||
"nodes.emptyStats": "No generations in this window yet.",
|
||||
"nodes.cpuNote": "Ollama exposes no CPU/RAM API — charts come from app-side generation telemetry; per-model VRAM via /api/ps.",
|
||||
"catalog.title": "Model catalog (Hugging Face)",
|
||||
"catalog.desc": "GGUF models installable with one click via ollama pull hf.co/…. Needs Ollama with Hugging Face support and internet on the Ollama host.",
|
||||
"catalog.searchPh": "Search GGUF models…",
|
||||
"catalog.pull": "Pull",
|
||||
"catalog.pulling": "Pulling {name}…",
|
||||
"catalog.node": "Target node",
|
||||
"catalog.downloads": "downloads",
|
||||
"catalog.files": "Quantizations",
|
||||
"catalog.noRes": "No results.",
|
||||
"catalog.hint": "Pick a quantization, it pulls straight from Hugging Face.",
|
||||
"rec.title": "Password recovery limits",
|
||||
"rec.desc": "Dedicated envelope for key-recovery requests (separate from login).",
|
||||
"rec.max": "Max attempts",
|
||||
"rec.maxHint": "per window, per IP",
|
||||
"rec.window": "Window",
|
||||
"rec.windowHint": "minutes",
|
||||
"rec.save": "Save limits",
|
||||
"rec.saved": "Limits saved",
|
||||
"rec.invalid": "Check the values (attempts 1–100, window 1–1440 min).",
|
||||
|
||||
"msg.copy": "Copy message",
|
||||
"msg.search": "Search the web",
|
||||
@@ -1068,6 +1128,66 @@ const es: Dict = {
|
||||
"live.newReport": "New content report ({reason})",
|
||||
"live.adminReply": "Admin replied to your ticket",
|
||||
"live.ticketStatus": "Ticket {status}",
|
||||
"nodes.title": "Ollama nodes",
|
||||
"nodes.desc": "Extra Ollama hosts. Each generation picks a node by weight (firewall-like priority, heaviest first) and fails over to the next one on error. The default node comes from OLLAMA_HOST.",
|
||||
"nodes.add": "Add node",
|
||||
"nodes.edit": "Edit node",
|
||||
"nodes.name": "Name",
|
||||
"nodes.namePh": "gpu-box-2",
|
||||
"nodes.host": "Host",
|
||||
"nodes.hostPh": "http://10.0.0.5:11434",
|
||||
"nodes.hostHint": "http(s) base URL, no path. Must be reachable from this server.",
|
||||
"nodes.weight": "Weight",
|
||||
"nodes.weightHint": "0 disables traffic, higher gets more generations.",
|
||||
"nodes.enabled": "Enabled",
|
||||
"nodes.share": "{pct}%",
|
||||
"nodes.off": "off",
|
||||
"nodes.test": "Test",
|
||||
"nodes.probing": "Probing…",
|
||||
"nodes.testOk": "Reachable",
|
||||
"nodes.testFail": "Unreachable",
|
||||
"nodes.deleteTitle": "Delete this node?",
|
||||
"nodes.deleteMsg": "Generations will no longer be routed to this host.",
|
||||
"nodes.invalid": "Check the name, host and weight.",
|
||||
"nodes.dupHost": "This host is already registered.",
|
||||
"nodes.detail": "Node details",
|
||||
"nodes.running": "Running now",
|
||||
"nodes.vram": "VRAM",
|
||||
"nodes.diskModels": "On disk ({n})",
|
||||
"nodes.version": "Version",
|
||||
"nodes.latency": "Latency",
|
||||
"nodes.timeout": "Generation timeout",
|
||||
"nodes.timeoutDesc": "Max time for one Ollama generation before failing over. Applies to every node.",
|
||||
"nodes.timeoutUnit": "seconds (5–3600)",
|
||||
"nodes.defWeight": "Default node weight",
|
||||
"nodes.defWeightDesc": "Traffic share of the OLLAMA_HOST node. 0 excludes it when custom nodes exist.",
|
||||
"nodes.stats": "Load",
|
||||
"nodes.req": "requests",
|
||||
"nodes.err": "errors",
|
||||
"nodes.avg": "avg latency",
|
||||
"nodes.tps": "tokens/s",
|
||||
"nodes.tok": "tokens",
|
||||
"nodes.emptyStats": "No generations in this window yet.",
|
||||
"nodes.cpuNote": "Ollama exposes no CPU/RAM API — charts come from app-side generation telemetry; per-model VRAM via /api/ps.",
|
||||
"catalog.title": "Model catalog (Hugging Face)",
|
||||
"catalog.desc": "GGUF models installable with one click via ollama pull hf.co/…. Needs Ollama with Hugging Face support and internet on the Ollama host.",
|
||||
"catalog.searchPh": "Search GGUF models…",
|
||||
"catalog.pull": "Pull",
|
||||
"catalog.pulling": "Pulling {name}…",
|
||||
"catalog.node": "Target node",
|
||||
"catalog.downloads": "downloads",
|
||||
"catalog.files": "Quantizations",
|
||||
"catalog.noRes": "No results.",
|
||||
"catalog.hint": "Pick a quantization, it pulls straight from Hugging Face.",
|
||||
"rec.title": "Password recovery limits",
|
||||
"rec.desc": "Dedicated envelope for key-recovery requests (separate from login).",
|
||||
"rec.max": "Max attempts",
|
||||
"rec.maxHint": "per window, per IP",
|
||||
"rec.window": "Window",
|
||||
"rec.windowHint": "minutes",
|
||||
"rec.save": "Save limits",
|
||||
"rec.saved": "Limits saved",
|
||||
"rec.invalid": "Check the values (attempts 1–100, window 1–1440 min).",
|
||||
|
||||
"msg.copy": "Copiar mensaje",
|
||||
"msg.search": "Buscar en la web",
|
||||
@@ -1640,6 +1760,66 @@ const fr: Dict = {
|
||||
"live.newReport": "New content report ({reason})",
|
||||
"live.adminReply": "Admin replied to your ticket",
|
||||
"live.ticketStatus": "Ticket {status}",
|
||||
"nodes.title": "Ollama nodes",
|
||||
"nodes.desc": "Extra Ollama hosts. Each generation picks a node by weight (firewall-like priority, heaviest first) and fails over to the next one on error. The default node comes from OLLAMA_HOST.",
|
||||
"nodes.add": "Add node",
|
||||
"nodes.edit": "Edit node",
|
||||
"nodes.name": "Name",
|
||||
"nodes.namePh": "gpu-box-2",
|
||||
"nodes.host": "Host",
|
||||
"nodes.hostPh": "http://10.0.0.5:11434",
|
||||
"nodes.hostHint": "http(s) base URL, no path. Must be reachable from this server.",
|
||||
"nodes.weight": "Weight",
|
||||
"nodes.weightHint": "0 disables traffic, higher gets more generations.",
|
||||
"nodes.enabled": "Enabled",
|
||||
"nodes.share": "{pct}%",
|
||||
"nodes.off": "off",
|
||||
"nodes.test": "Test",
|
||||
"nodes.probing": "Probing…",
|
||||
"nodes.testOk": "Reachable",
|
||||
"nodes.testFail": "Unreachable",
|
||||
"nodes.deleteTitle": "Delete this node?",
|
||||
"nodes.deleteMsg": "Generations will no longer be routed to this host.",
|
||||
"nodes.invalid": "Check the name, host and weight.",
|
||||
"nodes.dupHost": "This host is already registered.",
|
||||
"nodes.detail": "Node details",
|
||||
"nodes.running": "Running now",
|
||||
"nodes.vram": "VRAM",
|
||||
"nodes.diskModels": "On disk ({n})",
|
||||
"nodes.version": "Version",
|
||||
"nodes.latency": "Latency",
|
||||
"nodes.timeout": "Generation timeout",
|
||||
"nodes.timeoutDesc": "Max time for one Ollama generation before failing over. Applies to every node.",
|
||||
"nodes.timeoutUnit": "seconds (5–3600)",
|
||||
"nodes.defWeight": "Default node weight",
|
||||
"nodes.defWeightDesc": "Traffic share of the OLLAMA_HOST node. 0 excludes it when custom nodes exist.",
|
||||
"nodes.stats": "Load",
|
||||
"nodes.req": "requests",
|
||||
"nodes.err": "errors",
|
||||
"nodes.avg": "avg latency",
|
||||
"nodes.tps": "tokens/s",
|
||||
"nodes.tok": "tokens",
|
||||
"nodes.emptyStats": "No generations in this window yet.",
|
||||
"nodes.cpuNote": "Ollama exposes no CPU/RAM API — charts come from app-side generation telemetry; per-model VRAM via /api/ps.",
|
||||
"catalog.title": "Model catalog (Hugging Face)",
|
||||
"catalog.desc": "GGUF models installable with one click via ollama pull hf.co/…. Needs Ollama with Hugging Face support and internet on the Ollama host.",
|
||||
"catalog.searchPh": "Search GGUF models…",
|
||||
"catalog.pull": "Pull",
|
||||
"catalog.pulling": "Pulling {name}…",
|
||||
"catalog.node": "Target node",
|
||||
"catalog.downloads": "downloads",
|
||||
"catalog.files": "Quantizations",
|
||||
"catalog.noRes": "No results.",
|
||||
"catalog.hint": "Pick a quantization, it pulls straight from Hugging Face.",
|
||||
"rec.title": "Password recovery limits",
|
||||
"rec.desc": "Dedicated envelope for key-recovery requests (separate from login).",
|
||||
"rec.max": "Max attempts",
|
||||
"rec.maxHint": "per window, per IP",
|
||||
"rec.window": "Window",
|
||||
"rec.windowHint": "minutes",
|
||||
"rec.save": "Save limits",
|
||||
"rec.saved": "Limits saved",
|
||||
"rec.invalid": "Check the values (attempts 1–100, window 1–1440 min).",
|
||||
|
||||
"msg.copy": "Copier le message",
|
||||
"msg.search": "Rechercher sur le web",
|
||||
@@ -2200,6 +2380,66 @@ const ru: Dict = {
|
||||
"live.newReport": "New content report ({reason})",
|
||||
"live.adminReply": "Admin replied to your ticket",
|
||||
"live.ticketStatus": "Ticket {status}",
|
||||
"nodes.title": "Ollama nodes",
|
||||
"nodes.desc": "Extra Ollama hosts. Each generation picks a node by weight (firewall-like priority, heaviest first) and fails over to the next one on error. The default node comes from OLLAMA_HOST.",
|
||||
"nodes.add": "Add node",
|
||||
"nodes.edit": "Edit node",
|
||||
"nodes.name": "Name",
|
||||
"nodes.namePh": "gpu-box-2",
|
||||
"nodes.host": "Host",
|
||||
"nodes.hostPh": "http://10.0.0.5:11434",
|
||||
"nodes.hostHint": "http(s) base URL, no path. Must be reachable from this server.",
|
||||
"nodes.weight": "Weight",
|
||||
"nodes.weightHint": "0 disables traffic, higher gets more generations.",
|
||||
"nodes.enabled": "Enabled",
|
||||
"nodes.share": "{pct}%",
|
||||
"nodes.off": "off",
|
||||
"nodes.test": "Test",
|
||||
"nodes.probing": "Probing…",
|
||||
"nodes.testOk": "Reachable",
|
||||
"nodes.testFail": "Unreachable",
|
||||
"nodes.deleteTitle": "Delete this node?",
|
||||
"nodes.deleteMsg": "Generations will no longer be routed to this host.",
|
||||
"nodes.invalid": "Check the name, host and weight.",
|
||||
"nodes.dupHost": "This host is already registered.",
|
||||
"nodes.detail": "Node details",
|
||||
"nodes.running": "Running now",
|
||||
"nodes.vram": "VRAM",
|
||||
"nodes.diskModels": "On disk ({n})",
|
||||
"nodes.version": "Version",
|
||||
"nodes.latency": "Latency",
|
||||
"nodes.timeout": "Generation timeout",
|
||||
"nodes.timeoutDesc": "Max time for one Ollama generation before failing over. Applies to every node.",
|
||||
"nodes.timeoutUnit": "seconds (5–3600)",
|
||||
"nodes.defWeight": "Default node weight",
|
||||
"nodes.defWeightDesc": "Traffic share of the OLLAMA_HOST node. 0 excludes it when custom nodes exist.",
|
||||
"nodes.stats": "Load",
|
||||
"nodes.req": "requests",
|
||||
"nodes.err": "errors",
|
||||
"nodes.avg": "avg latency",
|
||||
"nodes.tps": "tokens/s",
|
||||
"nodes.tok": "tokens",
|
||||
"nodes.emptyStats": "No generations in this window yet.",
|
||||
"nodes.cpuNote": "Ollama exposes no CPU/RAM API — charts come from app-side generation telemetry; per-model VRAM via /api/ps.",
|
||||
"catalog.title": "Model catalog (Hugging Face)",
|
||||
"catalog.desc": "GGUF models installable with one click via ollama pull hf.co/…. Needs Ollama with Hugging Face support and internet on the Ollama host.",
|
||||
"catalog.searchPh": "Search GGUF models…",
|
||||
"catalog.pull": "Pull",
|
||||
"catalog.pulling": "Pulling {name}…",
|
||||
"catalog.node": "Target node",
|
||||
"catalog.downloads": "downloads",
|
||||
"catalog.files": "Quantizations",
|
||||
"catalog.noRes": "No results.",
|
||||
"catalog.hint": "Pick a quantization, it pulls straight from Hugging Face.",
|
||||
"rec.title": "Password recovery limits",
|
||||
"rec.desc": "Dedicated envelope for key-recovery requests (separate from login).",
|
||||
"rec.max": "Max attempts",
|
||||
"rec.maxHint": "per window, per IP",
|
||||
"rec.window": "Window",
|
||||
"rec.windowHint": "minutes",
|
||||
"rec.save": "Save limits",
|
||||
"rec.saved": "Limits saved",
|
||||
"rec.invalid": "Check the values (attempts 1–100, window 1–1440 min).",
|
||||
|
||||
"msg.copy": "Скопировать сообщение",
|
||||
"msg.search": "Искать в интернете",
|
||||
@@ -2765,6 +3005,66 @@ const it: Dict = {
|
||||
"live.newReport": "New content report ({reason})",
|
||||
"live.adminReply": "Admin replied to your ticket",
|
||||
"live.ticketStatus": "Ticket {status}",
|
||||
"nodes.title": "Ollama nodes",
|
||||
"nodes.desc": "Extra Ollama hosts. Each generation picks a node by weight (firewall-like priority, heaviest first) and fails over to the next one on error. The default node comes from OLLAMA_HOST.",
|
||||
"nodes.add": "Add node",
|
||||
"nodes.edit": "Edit node",
|
||||
"nodes.name": "Name",
|
||||
"nodes.namePh": "gpu-box-2",
|
||||
"nodes.host": "Host",
|
||||
"nodes.hostPh": "http://10.0.0.5:11434",
|
||||
"nodes.hostHint": "http(s) base URL, no path. Must be reachable from this server.",
|
||||
"nodes.weight": "Weight",
|
||||
"nodes.weightHint": "0 disables traffic, higher gets more generations.",
|
||||
"nodes.enabled": "Enabled",
|
||||
"nodes.share": "{pct}%",
|
||||
"nodes.off": "off",
|
||||
"nodes.test": "Test",
|
||||
"nodes.probing": "Probing…",
|
||||
"nodes.testOk": "Reachable",
|
||||
"nodes.testFail": "Unreachable",
|
||||
"nodes.deleteTitle": "Delete this node?",
|
||||
"nodes.deleteMsg": "Generations will no longer be routed to this host.",
|
||||
"nodes.invalid": "Check the name, host and weight.",
|
||||
"nodes.dupHost": "This host is already registered.",
|
||||
"nodes.detail": "Node details",
|
||||
"nodes.running": "Running now",
|
||||
"nodes.vram": "VRAM",
|
||||
"nodes.diskModels": "On disk ({n})",
|
||||
"nodes.version": "Version",
|
||||
"nodes.latency": "Latency",
|
||||
"nodes.timeout": "Generation timeout",
|
||||
"nodes.timeoutDesc": "Max time for one Ollama generation before failing over. Applies to every node.",
|
||||
"nodes.timeoutUnit": "seconds (5–3600)",
|
||||
"nodes.defWeight": "Default node weight",
|
||||
"nodes.defWeightDesc": "Traffic share of the OLLAMA_HOST node. 0 excludes it when custom nodes exist.",
|
||||
"nodes.stats": "Load",
|
||||
"nodes.req": "requests",
|
||||
"nodes.err": "errors",
|
||||
"nodes.avg": "avg latency",
|
||||
"nodes.tps": "tokens/s",
|
||||
"nodes.tok": "tokens",
|
||||
"nodes.emptyStats": "No generations in this window yet.",
|
||||
"nodes.cpuNote": "Ollama exposes no CPU/RAM API — charts come from app-side generation telemetry; per-model VRAM via /api/ps.",
|
||||
"catalog.title": "Model catalog (Hugging Face)",
|
||||
"catalog.desc": "GGUF models installable with one click via ollama pull hf.co/…. Needs Ollama with Hugging Face support and internet on the Ollama host.",
|
||||
"catalog.searchPh": "Search GGUF models…",
|
||||
"catalog.pull": "Pull",
|
||||
"catalog.pulling": "Pulling {name}…",
|
||||
"catalog.node": "Target node",
|
||||
"catalog.downloads": "downloads",
|
||||
"catalog.files": "Quantizations",
|
||||
"catalog.noRes": "No results.",
|
||||
"catalog.hint": "Pick a quantization, it pulls straight from Hugging Face.",
|
||||
"rec.title": "Password recovery limits",
|
||||
"rec.desc": "Dedicated envelope for key-recovery requests (separate from login).",
|
||||
"rec.max": "Max attempts",
|
||||
"rec.maxHint": "per window, per IP",
|
||||
"rec.window": "Window",
|
||||
"rec.windowHint": "minutes",
|
||||
"rec.save": "Save limits",
|
||||
"rec.saved": "Limits saved",
|
||||
"rec.invalid": "Check the values (attempts 1–100, window 1–1440 min).",
|
||||
|
||||
"msg.copy": "Copia messaggio",
|
||||
"msg.search": "Cerca nel web",
|
||||
@@ -3321,6 +3621,66 @@ const pt: Dict = {
|
||||
"live.newReport": "New content report ({reason})",
|
||||
"live.adminReply": "Admin replied to your ticket",
|
||||
"live.ticketStatus": "Ticket {status}",
|
||||
"nodes.title": "Ollama nodes",
|
||||
"nodes.desc": "Extra Ollama hosts. Each generation picks a node by weight (firewall-like priority, heaviest first) and fails over to the next one on error. The default node comes from OLLAMA_HOST.",
|
||||
"nodes.add": "Add node",
|
||||
"nodes.edit": "Edit node",
|
||||
"nodes.name": "Name",
|
||||
"nodes.namePh": "gpu-box-2",
|
||||
"nodes.host": "Host",
|
||||
"nodes.hostPh": "http://10.0.0.5:11434",
|
||||
"nodes.hostHint": "http(s) base URL, no path. Must be reachable from this server.",
|
||||
"nodes.weight": "Weight",
|
||||
"nodes.weightHint": "0 disables traffic, higher gets more generations.",
|
||||
"nodes.enabled": "Enabled",
|
||||
"nodes.share": "{pct}%",
|
||||
"nodes.off": "off",
|
||||
"nodes.test": "Test",
|
||||
"nodes.probing": "Probing…",
|
||||
"nodes.testOk": "Reachable",
|
||||
"nodes.testFail": "Unreachable",
|
||||
"nodes.deleteTitle": "Delete this node?",
|
||||
"nodes.deleteMsg": "Generations will no longer be routed to this host.",
|
||||
"nodes.invalid": "Check the name, host and weight.",
|
||||
"nodes.dupHost": "This host is already registered.",
|
||||
"nodes.detail": "Node details",
|
||||
"nodes.running": "Running now",
|
||||
"nodes.vram": "VRAM",
|
||||
"nodes.diskModels": "On disk ({n})",
|
||||
"nodes.version": "Version",
|
||||
"nodes.latency": "Latency",
|
||||
"nodes.timeout": "Generation timeout",
|
||||
"nodes.timeoutDesc": "Max time for one Ollama generation before failing over. Applies to every node.",
|
||||
"nodes.timeoutUnit": "seconds (5–3600)",
|
||||
"nodes.defWeight": "Default node weight",
|
||||
"nodes.defWeightDesc": "Traffic share of the OLLAMA_HOST node. 0 excludes it when custom nodes exist.",
|
||||
"nodes.stats": "Load",
|
||||
"nodes.req": "requests",
|
||||
"nodes.err": "errors",
|
||||
"nodes.avg": "avg latency",
|
||||
"nodes.tps": "tokens/s",
|
||||
"nodes.tok": "tokens",
|
||||
"nodes.emptyStats": "No generations in this window yet.",
|
||||
"nodes.cpuNote": "Ollama exposes no CPU/RAM API — charts come from app-side generation telemetry; per-model VRAM via /api/ps.",
|
||||
"catalog.title": "Model catalog (Hugging Face)",
|
||||
"catalog.desc": "GGUF models installable with one click via ollama pull hf.co/…. Needs Ollama with Hugging Face support and internet on the Ollama host.",
|
||||
"catalog.searchPh": "Search GGUF models…",
|
||||
"catalog.pull": "Pull",
|
||||
"catalog.pulling": "Pulling {name}…",
|
||||
"catalog.node": "Target node",
|
||||
"catalog.downloads": "downloads",
|
||||
"catalog.files": "Quantizations",
|
||||
"catalog.noRes": "No results.",
|
||||
"catalog.hint": "Pick a quantization, it pulls straight from Hugging Face.",
|
||||
"rec.title": "Password recovery limits",
|
||||
"rec.desc": "Dedicated envelope for key-recovery requests (separate from login).",
|
||||
"rec.max": "Max attempts",
|
||||
"rec.maxHint": "per window, per IP",
|
||||
"rec.window": "Window",
|
||||
"rec.windowHint": "minutes",
|
||||
"rec.save": "Save limits",
|
||||
"rec.saved": "Limits saved",
|
||||
"rec.invalid": "Check the values (attempts 1–100, window 1–1440 min).",
|
||||
|
||||
"msg.copy": "Copiar mensagem",
|
||||
"msg.search": "Pesquisar na web",
|
||||
|
||||
@@ -23,6 +23,14 @@ export function chatIdFromPath(pathname: string): string | null {
|
||||
return m ? decodeURIComponent(m[1]!) : null;
|
||||
}
|
||||
|
||||
/** Admin section from /admin itself (null) or /admin/<tab>. */
|
||||
export function adminTabFromPath(pathname: string): string | null {
|
||||
const p = normalizePath(pathname);
|
||||
if (p === "/admin") return null;
|
||||
const m = p.match(/^\/admin\/([^/]+)$/);
|
||||
return m ? decodeURIComponent(m[1]!) : null;
|
||||
}
|
||||
|
||||
/** Client-side navigation without reload (same-origin path only). */
|
||||
export function navigate(to: string, replace = false): void {
|
||||
const dest = normalizePath(to);
|
||||
|
||||
Reference in New Issue
Block a user