feat: infrance
Build Release EXE / build-windows-exe (release) Successful in 58s

This commit is contained in:
2026-06-08 20:28:06 -04:00
parent 6bd1e81a51
commit 00cf6f8747
20 changed files with 2789 additions and 180 deletions
+322 -44
View File
@@ -26,7 +26,10 @@ const ollamaDownloadButton = document.getElementById("ollama-download");
const ollamaInstallButton = document.getElementById("ollama-install");
const ollamaLaunchButton = document.getElementById("ollama-launch");
const ollamaPullButton = document.getElementById("ollama-pull");
const codexLoginButton = document.getElementById("codex-login");
const openaiModelsRefreshButton = document.getElementById("openai-models-refresh");
const providerModelSelect = document.getElementById("provider-model-select");
const modelReasoningEffortSelect = document.getElementById("model-reasoning-effort");
const ollamaStatusEl = document.getElementById("ollama-status");
const ollamaMessageEl = document.getElementById("ollama-message");
const updateCheckButton = document.getElementById("update-check");
@@ -57,6 +60,7 @@ const planForm = document.getElementById("plan-form");
const plansStatusEl = document.getElementById("plans-status");
const plansDashboardEl = document.getElementById("plans-dashboard");
const plansRailListEl = document.getElementById("plans-rail-list");
const providerScopedFields = Array.from(document.querySelectorAll("[data-provider-scope]"));
let ollamaOnline = true;
let latestUpdate = null;
@@ -599,6 +603,9 @@ const ollamaFieldIds = {
openai_base_url: "openai-base-url",
openai_api_key: "openai-api-key",
openai_model: "openai-model",
model_reasoning_effort: "model-reasoning-effort",
codex_command: "codex-command",
codex_model: "codex-model",
};
async function refreshConfig() {
@@ -636,6 +643,8 @@ function renderConfig(config) {
field.value = values[key] ?? "";
}
}
renderReasoningEffortOptions(["none", "minimal", "low", "medium", "high", "xhigh"], values.model_reasoning_effort || "medium");
updateProviderFieldVisibility(values.model_provider || "ollama");
configPathsEl.textContent = `App data: ${config.app_data_dir}\nConfig: ${config.config_path}\nLog: ${config.log_path}\nEdge profile: ${config.edge_profile_dir}`;
configStatusEl.textContent = "";
}
@@ -703,18 +712,21 @@ async function refreshOllamaStatus() {
function renderOllamaStatus(status) {
if (!ollamaStatusEl) return;
latestOllamaStatus = status;
const provider = status.provider === "openai" ? "OpenAI" : "Ollama";
updateProviderFieldVisibility(status.provider || "ollama");
const provider = providerDisplayName(status.provider);
const models = status.models?.length ? status.models.join(", ") : "None detected";
const ready = status.provider === "openai"
const isOpenAIProvider = status.provider === "openai";
const isCodexProvider = status.provider === "codex";
const ready = isOpenAIProvider
? Boolean(status.online && status.model_available)
: Boolean(status.installed && status.running && status.model_available);
const pillClass = ready ? "status-pill" : "status-pill warning";
const detailItems = [
ollamaStatusItem("Provider", provider),
ollamaStatusItem("Model", status.configured_model || ""),
ollamaStatusItem("URL", status.base_url || ""),
ollamaStatusItem(isCodexProvider ? "Command" : "URL", status.base_url || ""),
];
if (status.provider !== "openai") {
if (!isOpenAIProvider && !isCodexProvider) {
detailItems.splice(1, 0, ollamaStatusItem("Installed", status.installed ? "Yes" : "No"));
detailItems.splice(2, 0, ollamaStatusItem("Running", status.running ? "Yes" : "No"));
detailItems.push(ollamaStatusItem("Pulled", status.model_available ? "Yes" : "No"));
@@ -728,27 +740,32 @@ function renderOllamaStatus(status) {
<div class="ollama-status-grid">
${detailItems.join("")}
</div>
${ollamaStatusItem(status.provider === "openai" ? "Available Models" : "Installed Models", models)}
${ollamaStatusItem(isOpenAIProvider || isCodexProvider ? "Available Models" : "Installed Models", models)}
${status.detail ? ollamaStatusItem("Detail", status.detail) : ""}
`;
if (ollamaDownloadButton) ollamaDownloadButton.hidden = status.provider === "openai";
if (ollamaDownloadButton) ollamaDownloadButton.hidden = isOpenAIProvider || isCodexProvider;
if (ollamaInstallButton) {
ollamaInstallButton.hidden = status.provider === "openai" || !status.can_auto_install;
ollamaInstallButton.hidden = isOpenAIProvider || isCodexProvider || !status.can_auto_install;
ollamaInstallButton.disabled = Boolean(status.installed) || !status.can_auto_install;
}
if (ollamaLaunchButton) {
ollamaLaunchButton.hidden = status.provider === "openai";
ollamaLaunchButton.hidden = isOpenAIProvider || isCodexProvider;
ollamaLaunchButton.disabled = !status.installed || Boolean(status.running);
}
if (ollamaPullButton) {
ollamaPullButton.hidden = status.provider === "openai";
ollamaPullButton.hidden = isOpenAIProvider || isCodexProvider;
ollamaPullButton.disabled = !status.running || Boolean(status.model_available);
}
if (codexLoginButton) {
codexLoginButton.hidden = !isCodexProvider;
codexLoginButton.disabled = Boolean(status.online);
}
if (openaiModelsRefreshButton) {
openaiModelsRefreshButton.hidden = status.provider !== "openai";
openaiModelsRefreshButton.hidden = false;
openaiModelsRefreshButton.disabled = false;
}
renderProviderModelOptions(status.models || []);
renderProviderModelOptions(status.models || [], status);
renderReasoningEffortOptions(status.reasoning_efforts || [], status.configured_reasoning_effort || "medium");
updateOllamaAttention(status);
}
@@ -791,15 +808,18 @@ function setOllamaButtonAttention(button, action, active) {
function updateOllamaAttention(status = null) {
const currentStatus = status || latestOllamaStatus;
if (!currentStatus) return;
const ready = currentStatus.provider === "openai"
const isOpenAIProvider = currentStatus.provider === "openai";
const isCodexProvider = currentStatus.provider === "codex";
const ready = isOpenAIProvider
? Boolean(currentStatus.online && currentStatus.model_available)
: Boolean(currentStatus.installed && currentStatus.running && currentStatus.model_available);
ollamaToggle?.classList.toggle("attention-pulse", !ready);
setOllamaButtonAttention(ollamaDownloadButton, "download", currentStatus.provider !== "openai" && !currentStatus.installed);
setOllamaButtonAttention(ollamaInstallButton, "install", currentStatus.provider !== "openai" && !currentStatus.installed && currentStatus.can_auto_install);
setOllamaButtonAttention(ollamaLaunchButton, "launch", currentStatus.provider !== "openai" && currentStatus.installed && !currentStatus.running);
setOllamaButtonAttention(ollamaPullButton, "pull", currentStatus.provider !== "openai" && currentStatus.running && !currentStatus.model_available);
setOllamaButtonAttention(openaiModelsRefreshButton, "openai-models", currentStatus.provider === "openai" && !currentStatus.model_available);
setOllamaButtonAttention(ollamaDownloadButton, "download", !isOpenAIProvider && !isCodexProvider && !currentStatus.installed);
setOllamaButtonAttention(ollamaInstallButton, "install", !isOpenAIProvider && !isCodexProvider && !currentStatus.installed && currentStatus.can_auto_install);
setOllamaButtonAttention(ollamaLaunchButton, "launch", !isOpenAIProvider && !isCodexProvider && currentStatus.installed && !currentStatus.running);
setOllamaButtonAttention(ollamaPullButton, "pull", !isOpenAIProvider && !isCodexProvider && currentStatus.running && !currentStatus.model_available);
setOllamaButtonAttention(codexLoginButton, "codex-login", isCodexProvider && !currentStatus.online);
setOllamaButtonAttention(openaiModelsRefreshButton, "openai-models", isOpenAIProvider && !currentStatus.model_available);
if (ready) clickedOllamaActions.clear();
}
@@ -807,31 +827,137 @@ function configuredOllamaModel() {
return document.getElementById("ollama-model")?.value || "";
}
function renderProviderModelOptions(models) {
function updateProviderFieldVisibility(provider) {
for (const field of providerScopedFields) {
const scope = field.dataset.providerScope;
field.hidden = scope !== provider;
}
}
function renderProviderModelOptions(models, status = latestOllamaStatus) {
const datalist = document.getElementById("provider-models");
if (!datalist) return;
datalist.innerHTML = "";
if (datalist) datalist.innerHTML = "";
for (const model of models) {
if (datalist) {
const option = document.createElement("option");
option.value = model;
datalist.appendChild(option);
}
}
if (!providerModelSelect) return;
const provider = status?.provider || document.getElementById("model-provider")?.value || "ollama";
const configuredModel = configuredProviderModel(provider);
providerModelSelect.innerHTML = "";
const allModels = [...new Set([configuredModel, ...models].filter(Boolean))];
if (!allModels.length) {
const option = document.createElement("option");
option.value = "";
option.textContent = "No models detected";
providerModelSelect.appendChild(option);
providerModelSelect.disabled = true;
return;
}
providerModelSelect.disabled = false;
for (const model of allModels) {
const option = document.createElement("option");
option.value = model;
datalist.appendChild(option);
option.textContent = model;
if (model === configuredModel) option.selected = true;
providerModelSelect.appendChild(option);
}
}
async function refreshOpenAIModels() {
setOllamaMessage("Loading OpenAI models");
setOllamaMessage("Loading provider models");
try {
const response = await fetch("/api/openai/models");
const provider = document.getElementById("model-provider")?.value || latestOllamaStatus?.provider || "openai";
const response = await fetch(`/api/provider/models?provider=${encodeURIComponent(provider)}`);
const result = await response.json();
if (!response.ok) throw new Error(result.detail || `HTTP ${response.status}`);
renderProviderModelOptions(result.models || []);
setOllamaMessage(result.message || "Loaded OpenAI models");
renderProviderModelOptions(result.models || [], {
provider: result.provider || provider,
configured_model: configuredProviderModel(result.provider || provider),
});
setOllamaMessage(result.message || "Loaded provider models");
await refreshOllamaStatus();
} catch (error) {
setOllamaMessage(`OpenAI models failed: ${fetchErrorMessage(error)}`);
setOllamaMessage(`Provider model load failed: ${fetchErrorMessage(error)}`);
}
}
async function launchCodexLogin() {
markOllamaActionClicked("codex-login");
setOllamaMessage("Starting Codex sign-in");
try {
const response = await fetch("/api/codex/login", { method: "POST" });
const result = await response.json();
if (!response.ok) throw new Error(result.detail || `HTTP ${response.status}`);
if (result.auth_url) {
window.open(result.auth_url, "_blank", "noopener,noreferrer");
}
setOllamaMessage(result.message || "Opened Codex sign-in in your browser. Waiting for completion...");
await waitForCodexLogin();
} catch (error) {
setOllamaMessage(`Codex sign-in failed: ${fetchErrorMessage(error)}`);
}
}
async function waitForCodexLogin() {
for (let attempt = 0; attempt < 80; attempt += 1) {
await new Promise((resolve) => setTimeout(resolve, attempt < 8 ? 1500 : 3000));
await refreshOllamaStatus();
const provider = latestOllamaStatus?.provider || "";
if (provider === "codex" && latestOllamaStatus?.online) {
setOllamaMessage("Codex sign-in complete.");
return;
}
}
setOllamaMessage("Codex sign-in opened. If you completed it, click Load Provider Models or refresh provider status.");
}
function renderReasoningEffortOptions(efforts, configured) {
if (!modelReasoningEffortSelect) return;
const options = [...new Set([...(efforts || []), configured || "medium"].filter(Boolean))];
modelReasoningEffortSelect.innerHTML = "";
for (const effort of options) {
const option = document.createElement("option");
option.value = effort;
option.textContent = effort;
if (effort === configured) option.selected = true;
modelReasoningEffortSelect.appendChild(option);
}
}
function configuredProviderModel(provider) {
if (provider === "openai") return document.getElementById("openai-model")?.value || "";
if (provider === "codex") return document.getElementById("codex-model")?.value || "";
return document.getElementById("ollama-model")?.value || "";
}
function syncSelectedProviderModel() {
const provider = document.getElementById("model-provider")?.value || "ollama";
const selectedModel = providerModelSelect?.value || "";
if (!selectedModel) return;
if (provider === "openai") {
const field = document.getElementById("openai-model");
if (field) field.value = selectedModel;
return;
}
if (provider === "codex") {
const field = document.getElementById("codex-model");
if (field) field.value = selectedModel;
return;
}
const field = document.getElementById("ollama-model");
if (field) field.value = selectedModel;
}
function providerDisplayName(provider) {
if (provider === "openai") return "OpenAI";
if (provider === "codex") return "Codex";
return "Local Ollama";
}
async function checkForUpdate(promptUser = false) {
if (!updateStatusEl) return;
updateStatusEl.textContent = "Checking releases";
@@ -1282,34 +1408,81 @@ function renderPlansRail(plans) {
async function renderPlans(plans, openPlanId = null) {
plansDashboardEl.innerHTML = "";
if (!plans.length) {
plansDashboardEl.innerHTML = '<div class="pending-empty">No continual plans</div>';
plansDashboardEl.innerHTML = `
<section class="plans-overview">
<div>
<p class="eyebrow">Plan board</p>
<h3>No plans yet</h3>
<p class="plan-overview-copy">Create a buying watchlist or a custom follow-up routine to start tracking work over time.</p>
</div>
</section>
<div class="plan-empty-state">
<h4>Nothing is running</h4>
<p>Your continual plans will appear here with status, timing, and recent activity.</p>
</div>
`;
return;
}
const activeCount = plans.filter((plan) => plan.status === "active").length;
const attentionCount = plans.filter((plan) => plan.status === "needs_input" || plan.status === "paused").length;
const overview = document.createElement("section");
overview.className = "plans-overview";
overview.innerHTML = `
<div>
<p class="eyebrow">Plan board</p>
<h3>${plans.length} continual ${plans.length === 1 ? "plan" : "plans"}</h3>
<p class="plan-overview-copy">Monitor recurring work, keep candidate leads in view, and jump into details when something needs attention.</p>
</div>
<div class="plan-overview-stats">
<div class="plan-overview-stat">
<span class="plan-overview-stat-value">${activeCount}</span>
<span class="plan-overview-stat-label">active</span>
</div>
<div class="plan-overview-stat">
<span class="plan-overview-stat-value">${attentionCount}</span>
<span class="plan-overview-stat-label">needs eyes</span>
</div>
</div>
`;
plansDashboardEl.appendChild(overview);
for (const plan of plans) {
const card = document.createElement("article");
card.className = `plan-card${plan.status === "active" ? " active" : ""}`;
card.className = `plan-card plan-status-${slugifyPlanValue(plan.status)}${plan.status === "active" ? " active" : ""}`;
const heading = document.createElement("div");
heading.className = "plan-card-heading";
const title = document.createElement("h3");
title.textContent = plan.title || "Untitled plan";
const statusBadge = document.createElement("span");
statusBadge.className = `plan-status-badge plan-status-${slugifyPlanValue(plan.status)}`;
statusBadge.textContent = humanizePlanValue(plan.status || "unknown");
const meta = document.createElement("div");
meta.className = "plan-meta";
meta.textContent = plan.objective || "";
const pills = document.createElement("div");
pills.className = "plan-pill-row";
for (const value of [plan.status, plan.kind, plan.next_run_at ? `next ${formatShortDate(plan.next_run_at)}` : "not scheduled"]) {
for (const value of [plan.kind, plan.next_run_at ? `next ${formatShortDate(plan.next_run_at)}` : "not scheduled"]) {
const pill = document.createElement("span");
pill.className = "plan-pill";
pill.textContent = value;
pill.textContent = humanizePlanValue(value);
pills.appendChild(pill);
}
const metrics = document.createElement("div");
metrics.className = "plan-metrics";
metrics.append(
planMetric("Checklist", String((plan.items || []).length)),
planMetric("Cadence", summarizeCadence(plan.cadence)),
planMetric("Updated", formatShortDate(plan.updated_at || plan.created_at))
);
const controls = document.createElement("div");
controls.className = "plan-controls";
controls.append(
planButton("Details", () => loadPlanDetail(plan.id, card)),
planButton("Run", () => postPlanAction(plan.id, "run")),
planButton(plan.status === "active" ? "Pause" : "Resume", () => postPlanAction(plan.id, plan.status === "active" ? "pause" : "resume")),
planButton("Cancel", () => postPlanAction(plan.id, "cancel"), "secondary small-button")
planButton("Delete", () => deletePlan(plan.id), "secondary small-button")
);
card.append(title, meta, pills, controls);
heading.append(title, statusBadge);
card.append(heading, meta, pills, metrics, controls);
plansDashboardEl.appendChild(card);
if (openPlanId && plan.id === openPlanId) await loadPlanDetail(plan.id, card);
}
@@ -1330,41 +1503,113 @@ async function loadPlanDetail(planId, card) {
existing.remove();
return;
}
const loading = document.createElement("div");
loading.className = "plan-detail plan-detail-loading";
loading.textContent = "Loading plan details...";
card.appendChild(loading);
const response = await fetch(`/api/plans/${encodeURIComponent(planId)}`);
const result = await response.json();
const plan = result.plan;
loading.remove();
const detail = document.createElement("div");
detail.className = "plan-detail";
detail.append(
planSection("Checklist", (plan.items || []).map((item) => `${item.item_name}: ${item.acquired_quantity || 0}/${item.desired_quantity || 1}${item.max_unit_price ? `, max ${Number(item.max_unit_price).toLocaleString()} UEC` : ""} (${item.status})`)),
planSection("Best Candidates", bestCandidateLines(plan)),
planSection("Recent Events", (plan.events || []).slice(0, 5).map((event) => `${formatShortDate(event.created_at)} ${event.kind}: ${event.message}`))
planSection("Checklist", checklistLines(plan), "checklist"),
planSection("Best Candidates", bestCandidateLines(plan), "candidates"),
planSection("Recent Events", recentEventLines(plan), "events")
);
card.appendChild(detail);
}
function planSection(title, lines) {
function planSection(title, lines, sectionClass = "") {
const wrapper = document.createElement("section");
wrapper.className = `plan-section${sectionClass ? ` plan-section-${sectionClass}` : ""}`;
const heading = document.createElement("h4");
heading.textContent = title;
const list = document.createElement("ul");
list.className = "plan-list";
const items = lines.length ? lines : ["Empty"];
const items = lines.length ? lines : [planListItemData("Empty", "Nothing to show right now.")];
for (const line of items) {
const item = document.createElement("li");
item.textContent = line;
if (typeof line === "string") {
item.textContent = line;
} else {
item.className = line.className || "";
const titleEl = document.createElement("div");
titleEl.className = "plan-list-title";
titleEl.textContent = line.title;
const bodyEl = document.createElement("div");
bodyEl.className = "plan-list-body";
bodyEl.textContent = line.body;
item.append(titleEl, bodyEl);
}
list.appendChild(item);
}
wrapper.append(heading, list);
return wrapper;
}
function planMetric(label, value) {
const metric = document.createElement("div");
metric.className = "plan-metric";
const metricLabel = document.createElement("span");
metricLabel.className = "plan-metric-label";
metricLabel.textContent = label;
const metricValue = document.createElement("span");
metricValue.className = "plan-metric-value";
metricValue.textContent = value;
metric.append(metricLabel, metricValue);
return metric;
}
function summarizeCadence(cadence) {
if (!cadence) return "manual";
return cadence.replace(/\s+/g, " ").trim();
}
function slugifyPlanValue(value) {
return String(value || "unknown")
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/(^-|-$)/g, "");
}
function humanizePlanValue(value) {
return String(value || "")
.replace(/_/g, " ")
.replace(/\b\w/g, (char) => char.toUpperCase());
}
function planListItemData(title, body, className = "") {
return { title, body, className };
}
function checklistLines(plan) {
return (plan.items || []).map((item) => {
const quantity = `${item.acquired_quantity || 0}/${item.desired_quantity || 1}`;
const price = item.max_unit_price ? `Max ${Number(item.max_unit_price).toLocaleString()} UEC` : "No price cap";
return planListItemData(item.item_name, `${quantity} acquired • ${price}${humanizePlanValue(item.status || "pending")}`);
});
}
function bestCandidateLines(plan) {
const byItem = new Map((plan.items || []).map((item) => [item.id, item.item_name]));
return (plan.candidates || [])
.filter((candidate) => candidate.status === "current" || candidate.status === "drafted")
.slice(0, 6)
.map((candidate) => `${byItem.get(candidate.plan_item_id) || "Item"}: ${candidate.title || candidate.listing_slug || candidate.listing_id} at ${Number(candidate.price || 0).toLocaleString()} ${candidate.currency || "UEC"} from ${candidate.seller || "unknown"} (${candidate.status})`);
.map((candidate) => {
const title = byItem.get(candidate.plan_item_id) || "Item";
const listing = candidate.title || candidate.listing_slug || candidate.listing_id || "Unnamed listing";
const price = `${Number(candidate.price || 0).toLocaleString()} ${candidate.currency || "UEC"}`;
const seller = candidate.seller || "unknown seller";
return planListItemData(title, `${listing}${price}${seller}${humanizePlanValue(candidate.status || "current")}`);
});
}
function recentEventLines(plan) {
return (plan.events || [])
.slice(0, 5)
.map((event) => planListItemData(`${formatShortDate(event.created_at)}${humanizePlanValue(event.kind || "event")}`, event.message || "No details."));
}
async function postPlanAction(planId, action) {
@@ -1382,6 +1627,22 @@ async function postPlanAction(planId, action) {
}
}
async function deletePlan(planId) {
if (!window.confirm("Delete this plan and its stored history?")) return;
plansStatusEl.textContent = "delete requested";
try {
const response = await fetch(`/api/plans/${encodeURIComponent(planId)}`, { method: "DELETE" });
const result = await response.json();
if (!response.ok) throw new Error(result.detail || `HTTP ${response.status}`);
plansStatusEl.textContent = result.summary || "Plan deleted";
await refreshPlans();
await refreshPending();
await refreshInbox();
} catch (error) {
plansStatusEl.textContent = `Plan delete failed: ${fetchErrorMessage(error)}`;
}
}
function formatShortDate(value) {
if (!value) return "";
const date = new Date(value);
@@ -1391,10 +1652,20 @@ function formatShortDate(value) {
async function checkHealth() {
try {
const response = await fetch("/api/health");
const result = await response.json();
const health = result.ollama || {};
const provider = health.provider === "openai" ? "OpenAI" : "Ollama";
let health = {};
try {
const response = await fetch("/api/health");
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const result = await response.json();
health = result.inference || result.ollama || {};
} catch (primaryError) {
const fallbackResponse = await fetch("/api/ollama/status");
if (!fallbackResponse.ok) throw primaryError;
health = await fallbackResponse.json();
}
const provider = providerDisplayName(health.provider);
const isOpenAIProvider = health.provider === "openai";
const isCodexProvider = health.provider === "codex";
ollamaOnline = Boolean(health.online);
if (!ollamaOnline) {
statusEl.textContent = "Offline";
@@ -1403,7 +1674,7 @@ async function checkHealth() {
return false;
}
if (health.model_available === false) {
const action = health.provider === "openai" ? "Load OpenAI Models." : "Install Model.";
const action = isOpenAIProvider ? "Load Provider Models." : isCodexProvider ? "Sign In to Codex." : "Install Model.";
setWarning(`${provider} needs the configured model "${health.model}". Open the model provider tab and use ${action}`);
ollamaToggle?.classList.add("attention-pulse");
} else {
@@ -1637,6 +1908,13 @@ ollamaPullButton?.addEventListener("click", () => {
markOllamaActionClicked("pull");
postOllamaAction("/api/ollama/pull", { body: { model: configuredOllamaModel() } });
});
codexLoginButton?.addEventListener("click", launchCodexLogin);
providerModelSelect?.addEventListener("change", syncSelectedProviderModel);
document.getElementById("model-provider")?.addEventListener("change", () => {
const provider = document.getElementById("model-provider")?.value || "ollama";
updateProviderFieldVisibility(provider);
renderProviderModelOptions(latestOllamaStatus?.models || [], { ...latestOllamaStatus, provider });
});
openaiModelsRefreshButton?.addEventListener("click", () => {
markOllamaActionClicked("openai-models");
refreshOpenAIModels();