@sean.holung/minicode 0.3.6 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/scripts/run-benchmarks.js +1 -0
- package/dist/src/agent/config.js +27 -0
- package/dist/src/agent/editable-config.js +6 -0
- package/dist/src/model-utils.js +18 -1
- package/dist/src/serve/agent-bridge.js +85 -14
- package/dist/src/serve/mcp-server.js +19 -13
- package/dist/src/serve/server.js +166 -3
- package/dist/src/session/session-store.js +18 -0
- package/dist/src/shared/symbol-search.js +156 -0
- package/dist/src/tools/search-code-map.js +27 -35
- package/dist/src/web/app.js +662 -113
- package/dist/src/web/index.html +128 -8
- package/dist/src/web/style.css +189 -7
- package/dist/tests/agent.test.js +16 -0
- package/dist/tests/config-api.test.js +5 -0
- package/dist/tests/config-integration.test.js +91 -1
- package/dist/tests/config.test.js +9 -0
- package/dist/tests/file-tools.test.js +12 -0
- package/dist/tests/graph-onboarding.test.js +20 -0
- package/dist/tests/mcp-and-plugin.test.js +3 -0
- package/dist/tests/model-client-openai.test.js +41 -0
- package/dist/tests/model-dropdown-ui.test.js +23 -0
- package/dist/tests/model-utils.test.js +26 -1
- package/dist/tests/search-code-map.test.js +9 -0
- package/dist/tests/serve.integration.test.js +189 -0
- package/dist/tests/session-store.test.js +15 -1
- package/dist/tests/settings-ui.test.js +11 -0
- package/dist/tests/setup-overlay-state.test.js +49 -0
- package/dist/tests/system-prompt.test.js +1 -0
- package/dist/tests/test-utils.js +1 -0
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.js +10 -1
- package/node_modules/@minicode/agent-sdk/dist/src/agent/agent.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/agent/types.d.ts +1 -0
- package/node_modules/@minicode/agent-sdk/dist/src/agent/types.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/model/client.d.ts +8 -1
- package/node_modules/@minicode/agent-sdk/dist/src/model/client.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/model/client.js +164 -27
- package/node_modules/@minicode/agent-sdk/dist/src/model/client.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/tools/run-command.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/src/tools/run-command.js +60 -6
- package/node_modules/@minicode/agent-sdk/dist/src/tools/run-command.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/model-client-openai.test.js +87 -0
- package/node_modules/@minicode/agent-sdk/dist/tests/model-client-openai.test.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/test-utils.d.ts.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tests/test-utils.js +1 -0
- package/node_modules/@minicode/agent-sdk/dist/tests/test-utils.js.map +1 -1
- package/node_modules/@minicode/agent-sdk/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
package/dist/src/web/app.js
CHANGED
|
@@ -2,6 +2,22 @@ var __defProp = Object.defineProperty;
|
|
|
2
2
|
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
3
3
|
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
4
4
|
|
|
5
|
+
// src/web/modal-state.ts
|
|
6
|
+
function syncBodyModalOpenState() {
|
|
7
|
+
const anyModalOpen = [...document.querySelectorAll(".modal")].some((modal) => !modal.classList.contains("hidden"));
|
|
8
|
+
document.body.classList.toggle("modal-open", anyModalOpen);
|
|
9
|
+
}
|
|
10
|
+
function openModal(modal) {
|
|
11
|
+
modal.classList.remove("hidden");
|
|
12
|
+
modal.setAttribute("aria-hidden", "false");
|
|
13
|
+
syncBodyModalOpenState();
|
|
14
|
+
}
|
|
15
|
+
function closeModal(modal) {
|
|
16
|
+
modal.classList.add("hidden");
|
|
17
|
+
modal.setAttribute("aria-hidden", "true");
|
|
18
|
+
syncBodyModalOpenState();
|
|
19
|
+
}
|
|
20
|
+
|
|
5
21
|
// node_modules/marked/lib/marked.esm.js
|
|
6
22
|
function M() {
|
|
7
23
|
return { async: false, breaks: false, extensions: null, gfm: true, hooks: null, pedantic: false, renderer: null, silent: false, tokenizer: null, walkTokens: null };
|
|
@@ -1660,6 +1676,9 @@ var analysisReport = null;
|
|
|
1660
1676
|
var activeAnalysisFindingId = null;
|
|
1661
1677
|
var activeAnalysisFilter = "all";
|
|
1662
1678
|
var analysisExplanationCache = /* @__PURE__ */ new Map();
|
|
1679
|
+
var filePreviewModalInitialized = false;
|
|
1680
|
+
var latestFilePreviewRequestId = 0;
|
|
1681
|
+
var pendingGraphRefreshTimer = null;
|
|
1663
1682
|
var LAYOUT_OPTIONS = {
|
|
1664
1683
|
name: "cose",
|
|
1665
1684
|
nodeRepulsion: function() {
|
|
@@ -1685,39 +1704,9 @@ async function initGraph() {
|
|
|
1685
1704
|
initialized = true;
|
|
1686
1705
|
const cyEl = document.getElementById("cy");
|
|
1687
1706
|
const detailEl = document.getElementById("symbol-detail");
|
|
1707
|
+
setupFilePreviewModal();
|
|
1708
|
+
cyEl.innerHTML = "";
|
|
1688
1709
|
try {
|
|
1689
|
-
const [graphRes, symbolsRes, focusRes] = await Promise.all([
|
|
1690
|
-
fetch("/api/graph"),
|
|
1691
|
-
fetch("/api/symbols"),
|
|
1692
|
-
fetch("/api/focus")
|
|
1693
|
-
]);
|
|
1694
|
-
if (!graphRes.ok || !symbolsRes.ok) {
|
|
1695
|
-
cyEl.innerHTML = '<div class="graph-empty">No index available. Run minicode with a project to generate the code graph.</div>';
|
|
1696
|
-
return;
|
|
1697
|
-
}
|
|
1698
|
-
const graphData = await graphRes.json();
|
|
1699
|
-
const focusData = focusRes.ok ? await focusRes.json() : { pinned: [] };
|
|
1700
|
-
if (!graphData.nodes || graphData.nodes.length === 0) {
|
|
1701
|
-
cyEl.innerHTML = '<div class="graph-empty">No index available. Run minicode with a project to generate the code graph.</div>';
|
|
1702
|
-
return;
|
|
1703
|
-
}
|
|
1704
|
-
for (const node of graphData.nodes) {
|
|
1705
|
-
const id = node.qualifiedName || node.id || node.name || "";
|
|
1706
|
-
graphNodes.set(id, node);
|
|
1707
|
-
}
|
|
1708
|
-
graphEdges = (graphData.edges || []).map((e) => ({
|
|
1709
|
-
source: e.source || e.from || "",
|
|
1710
|
-
target: e.target || e.to || "",
|
|
1711
|
-
kind: (e.kind || e.type || "references").toLowerCase()
|
|
1712
|
-
}));
|
|
1713
|
-
edgeIndex = buildGraphEdgeIndex(graphEdges);
|
|
1714
|
-
fileToSymbolIds = buildGraphFileIndex(graphNodes);
|
|
1715
|
-
allSymbolNames = Array.from(graphNodes.keys()).sort();
|
|
1716
|
-
const pinned = focusData.pinned || [];
|
|
1717
|
-
for (const f of pinned) {
|
|
1718
|
-
const name = typeof f === "string" ? f : f.name || f.qualifiedName;
|
|
1719
|
-
if (name) pinnedNames.add(name);
|
|
1720
|
-
}
|
|
1721
1710
|
cy = cytoscape({
|
|
1722
1711
|
container: cyEl,
|
|
1723
1712
|
elements: [],
|
|
@@ -1727,20 +1716,64 @@ async function initGraph() {
|
|
|
1727
1716
|
});
|
|
1728
1717
|
setupInteractions(cy, detailEl);
|
|
1729
1718
|
setupToolbar();
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
runLayout();
|
|
1735
|
-
} else {
|
|
1736
|
-
showOnboardingHint(cyEl);
|
|
1719
|
+
const loaded = await loadGraphSnapshot(false);
|
|
1720
|
+
if (!loaded) {
|
|
1721
|
+
showOnboardingHint(cyEl, "No project index is available yet. Refresh after files are available, or restart minicode serve.");
|
|
1722
|
+
return;
|
|
1737
1723
|
}
|
|
1724
|
+
seedPinnedSymbolsOrOnboarding(
|
|
1725
|
+
graphNodes.size === 0 ? "No JavaScript or TypeScript symbols are indexed yet. Create a file, then refresh the graph." : void 0
|
|
1726
|
+
);
|
|
1738
1727
|
} catch (err) {
|
|
1739
1728
|
console.error("Graph init failed:", err);
|
|
1740
1729
|
const msg = err instanceof Error ? err.message : String(err);
|
|
1741
1730
|
cyEl.innerHTML = `<div class="graph-empty">Failed to load graph: ${msg}</div>`;
|
|
1742
1731
|
}
|
|
1743
1732
|
}
|
|
1733
|
+
async function refreshGraphData(options = {}) {
|
|
1734
|
+
if (!initialized || !cy) return;
|
|
1735
|
+
const {
|
|
1736
|
+
refreshIndex = false,
|
|
1737
|
+
preserveVisible = true,
|
|
1738
|
+
showFeedback = false
|
|
1739
|
+
} = options;
|
|
1740
|
+
const refreshBtn = document.getElementById("graph-refresh");
|
|
1741
|
+
const visibleIds = preserveVisible ? cy.nodes().map((node) => node.id()) : [];
|
|
1742
|
+
if (showFeedback && refreshBtn) {
|
|
1743
|
+
refreshBtn.disabled = true;
|
|
1744
|
+
refreshBtn.textContent = "Refreshing...";
|
|
1745
|
+
}
|
|
1746
|
+
try {
|
|
1747
|
+
const loaded = await loadGraphSnapshot(refreshIndex);
|
|
1748
|
+
if (!loaded) {
|
|
1749
|
+
throw new Error("No project index available");
|
|
1750
|
+
}
|
|
1751
|
+
renderGraphAfterDataRefresh(visibleIds, preserveVisible);
|
|
1752
|
+
} catch (err) {
|
|
1753
|
+
console.error("Graph refresh failed:", err);
|
|
1754
|
+
if (showFeedback) {
|
|
1755
|
+
const cyEl = document.getElementById("cy");
|
|
1756
|
+
if (cyEl && graphNodes.size === 0) {
|
|
1757
|
+
showOnboardingHint(cyEl, "Could not refresh the project index. Check the minicode serve logs, then try again.");
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
} finally {
|
|
1761
|
+
if (showFeedback && refreshBtn) {
|
|
1762
|
+
refreshBtn.disabled = false;
|
|
1763
|
+
refreshBtn.textContent = "Refresh";
|
|
1764
|
+
}
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
function scheduleGraphDataRefresh() {
|
|
1768
|
+
if (!initialized) return;
|
|
1769
|
+
if (pendingGraphRefreshTimer) {
|
|
1770
|
+
clearTimeout(pendingGraphRefreshTimer);
|
|
1771
|
+
}
|
|
1772
|
+
pendingGraphRefreshTimer = setTimeout(() => {
|
|
1773
|
+
pendingGraphRefreshTimer = null;
|
|
1774
|
+
void refreshGraphData({ preserveVisible: true });
|
|
1775
|
+
}, 250);
|
|
1776
|
+
}
|
|
1744
1777
|
function highlightAgentActivity(symbolName) {
|
|
1745
1778
|
if (!cy) return;
|
|
1746
1779
|
void focusResolvedSymbolsInGraph(symbolName, {
|
|
@@ -1754,11 +1787,106 @@ function highlightAgentActivity(symbolName) {
|
|
|
1754
1787
|
function resizeGraph() {
|
|
1755
1788
|
if (cy) cy.resize();
|
|
1756
1789
|
}
|
|
1757
|
-
function
|
|
1758
|
-
if (
|
|
1790
|
+
async function loadGraphSnapshot(refreshIndex) {
|
|
1791
|
+
if (refreshIndex) {
|
|
1792
|
+
const refreshRes = await fetch("/api/index/refresh", { method: "POST" });
|
|
1793
|
+
if (!refreshRes.ok) {
|
|
1794
|
+
return false;
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
const [graphRes, focusRes] = await Promise.all([
|
|
1798
|
+
fetch("/api/graph"),
|
|
1799
|
+
fetch("/api/focus")
|
|
1800
|
+
]);
|
|
1801
|
+
if (!graphRes.ok) {
|
|
1802
|
+
return false;
|
|
1803
|
+
}
|
|
1804
|
+
const graphData = await graphRes.json();
|
|
1805
|
+
const focusData = focusRes.ok ? await focusRes.json() : { pinned: [] };
|
|
1806
|
+
applyGraphSnapshot(graphData, focusData);
|
|
1807
|
+
return true;
|
|
1808
|
+
}
|
|
1809
|
+
function applyGraphSnapshot(graphData, focusData) {
|
|
1810
|
+
graphNodes.clear();
|
|
1811
|
+
for (const node of graphData.nodes || []) {
|
|
1812
|
+
const id = node.qualifiedName || node.id || node.name || "";
|
|
1813
|
+
if (id) {
|
|
1814
|
+
graphNodes.set(id, node);
|
|
1815
|
+
}
|
|
1816
|
+
}
|
|
1817
|
+
graphEdges = (graphData.edges || []).map((e) => ({
|
|
1818
|
+
source: e.source || e.from || "",
|
|
1819
|
+
target: e.target || e.to || "",
|
|
1820
|
+
kind: (e.kind || e.type || "references").toLowerCase()
|
|
1821
|
+
})).filter((edge) => edge.source && edge.target);
|
|
1822
|
+
edgeIndex = buildGraphEdgeIndex(graphEdges);
|
|
1823
|
+
fileToSymbolIds = buildGraphFileIndex(graphNodes);
|
|
1824
|
+
allSymbolNames = Array.from(graphNodes.keys()).sort();
|
|
1825
|
+
pinnedNames.clear();
|
|
1826
|
+
for (const pinned of focusData.pinned || []) {
|
|
1827
|
+
const name = typeof pinned === "string" ? pinned : pinned.name || pinned.qualifiedName;
|
|
1828
|
+
if (name) pinnedNames.add(name);
|
|
1829
|
+
}
|
|
1830
|
+
resetAnalysisForGraphRefresh();
|
|
1831
|
+
}
|
|
1832
|
+
function resetAnalysisForGraphRefresh() {
|
|
1833
|
+
analysisReport = null;
|
|
1834
|
+
activeAnalysisFindingId = null;
|
|
1835
|
+
activeAnalysisFilter = "all";
|
|
1836
|
+
analysisExplanationCache.clear();
|
|
1837
|
+
clearAnalysisGraphClasses();
|
|
1838
|
+
const panel = document.getElementById("analysis-panel");
|
|
1839
|
+
if (!panel || panel.classList.contains("hidden")) {
|
|
1840
|
+
return;
|
|
1841
|
+
}
|
|
1842
|
+
const { summary, findings } = getAnalysisPanelEls();
|
|
1843
|
+
summary.innerHTML = "";
|
|
1844
|
+
findings.innerHTML = '<div class="analysis-empty">Graph refreshed. Re-run analysis to inspect the latest dependency graph.</div>';
|
|
1845
|
+
setAnalysisStatus("Graph refreshed. Re-run analysis to inspect the latest snapshot.");
|
|
1846
|
+
}
|
|
1847
|
+
function renderGraphAfterDataRefresh(previousVisibleIds, preserveVisible) {
|
|
1848
|
+
if (!cy) return;
|
|
1849
|
+
cy.elements().remove();
|
|
1850
|
+
document.getElementById("symbol-detail")?.classList.add("hidden");
|
|
1851
|
+
const visibleIds = preserveVisible ? [...new Set(previousVisibleIds)].filter((id) => graphNodes.has(id)) : [];
|
|
1852
|
+
for (const id of visibleIds) {
|
|
1853
|
+
addNodeToGraph(id);
|
|
1854
|
+
}
|
|
1855
|
+
connectExistingNodes();
|
|
1856
|
+
if (cy.nodes().length > 0) {
|
|
1857
|
+
refreshAnalysisGraphState();
|
|
1858
|
+
runLayout();
|
|
1859
|
+
return;
|
|
1860
|
+
}
|
|
1861
|
+
seedPinnedSymbolsOrOnboarding(
|
|
1862
|
+
graphNodes.size === 0 ? "No JavaScript or TypeScript symbols are indexed yet. Create a file, then refresh the graph." : void 0
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1865
|
+
function seedPinnedSymbolsOrOnboarding(subtitle) {
|
|
1866
|
+
if (!cy) return;
|
|
1867
|
+
for (const name of pinnedNames) {
|
|
1868
|
+
addNodeNeighborhood(name, 1);
|
|
1869
|
+
}
|
|
1870
|
+
connectExistingNodes();
|
|
1871
|
+
if (cy.nodes().length > 0) {
|
|
1872
|
+
refreshAnalysisGraphState();
|
|
1873
|
+
runLayout();
|
|
1874
|
+
return;
|
|
1875
|
+
}
|
|
1876
|
+
const cyEl = document.getElementById("cy");
|
|
1877
|
+
if (cyEl) showOnboardingHint(cyEl, subtitle);
|
|
1878
|
+
refreshAnalysisGraphState();
|
|
1879
|
+
}
|
|
1880
|
+
function showOnboardingHint(container, subtitle = "Search for a symbol or file above to start exploring.<br/>Nodes expand on click to reveal connections.") {
|
|
1881
|
+
const existing = container.querySelector(".graph-onboarding");
|
|
1882
|
+
if (existing) {
|
|
1883
|
+
const subtitleEl = existing.querySelector(".graph-onboarding-subtitle");
|
|
1884
|
+
if (subtitleEl) subtitleEl.innerHTML = subtitle;
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1759
1887
|
const hint = document.createElement("div");
|
|
1760
1888
|
hint.className = "graph-onboarding";
|
|
1761
|
-
hint.innerHTML =
|
|
1889
|
+
hint.innerHTML = `<div class="graph-onboarding-icon">◆ — ◆</div><div class="graph-onboarding-title">Code dependency graph</div><div class="graph-onboarding-subtitle">${subtitle}</div>`;
|
|
1762
1890
|
container.appendChild(hint);
|
|
1763
1891
|
}
|
|
1764
1892
|
function removeOnboardingHint() {
|
|
@@ -1823,6 +1951,78 @@ function focusFileInGraph(filePath) {
|
|
|
1823
1951
|
refreshAnalysisGraphState();
|
|
1824
1952
|
runLayout();
|
|
1825
1953
|
}
|
|
1954
|
+
function getFilePreviewLanguage(filePath) {
|
|
1955
|
+
const ext = filePath.split(".").pop()?.toLowerCase() || "";
|
|
1956
|
+
const langMap = {
|
|
1957
|
+
ts: "typescript",
|
|
1958
|
+
tsx: "typescript",
|
|
1959
|
+
js: "javascript",
|
|
1960
|
+
jsx: "javascript",
|
|
1961
|
+
json: "json",
|
|
1962
|
+
md: "markdown",
|
|
1963
|
+
css: "css",
|
|
1964
|
+
html: "xml"
|
|
1965
|
+
};
|
|
1966
|
+
return langMap[ext] || "plaintext";
|
|
1967
|
+
}
|
|
1968
|
+
function closeFilePreview() {
|
|
1969
|
+
const modal = document.getElementById("file-preview-modal");
|
|
1970
|
+
if (!modal) return;
|
|
1971
|
+
closeModal(modal);
|
|
1972
|
+
}
|
|
1973
|
+
function setupFilePreviewModal() {
|
|
1974
|
+
if (filePreviewModalInitialized) return;
|
|
1975
|
+
filePreviewModalInitialized = true;
|
|
1976
|
+
const modal = document.getElementById("file-preview-modal");
|
|
1977
|
+
const backdrop = document.getElementById("file-preview-backdrop");
|
|
1978
|
+
const closeBtn = document.getElementById("file-preview-close");
|
|
1979
|
+
if (!modal || !backdrop || !closeBtn) {
|
|
1980
|
+
return;
|
|
1981
|
+
}
|
|
1982
|
+
backdrop.addEventListener("click", () => closeFilePreview());
|
|
1983
|
+
closeBtn.addEventListener("click", () => closeFilePreview());
|
|
1984
|
+
document.addEventListener("keydown", (event) => {
|
|
1985
|
+
if (event.key === "Escape" && !modal.classList.contains("hidden")) {
|
|
1986
|
+
closeFilePreview();
|
|
1987
|
+
}
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1990
|
+
async function openFilePreview(filePath) {
|
|
1991
|
+
const modal = document.getElementById("file-preview-modal");
|
|
1992
|
+
const pathEl = document.getElementById("file-preview-path");
|
|
1993
|
+
const codeEl = document.getElementById("file-preview-code");
|
|
1994
|
+
if (!modal || !pathEl || !codeEl) {
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
latestFilePreviewRequestId += 1;
|
|
1998
|
+
const requestId = latestFilePreviewRequestId;
|
|
1999
|
+
pathEl.textContent = filePath;
|
|
2000
|
+
codeEl.className = "file-preview-code";
|
|
2001
|
+
codeEl.textContent = "Loading...";
|
|
2002
|
+
openModal(modal);
|
|
2003
|
+
try {
|
|
2004
|
+
const res = await fetch(`/api/file-source?path=${encodeURIComponent(filePath)}`);
|
|
2005
|
+
if (!res.ok) {
|
|
2006
|
+
codeEl.textContent = "(file unavailable)";
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
const data = await res.json();
|
|
2010
|
+
if (requestId !== latestFilePreviewRequestId) {
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
pathEl.textContent = data.filePath;
|
|
2014
|
+
codeEl.className = `file-preview-code language-${getFilePreviewLanguage(data.filePath)}`;
|
|
2015
|
+
codeEl.textContent = data.source;
|
|
2016
|
+
if (typeof hljs !== "undefined") {
|
|
2017
|
+
hljs.highlightElement(codeEl);
|
|
2018
|
+
}
|
|
2019
|
+
} catch {
|
|
2020
|
+
if (requestId !== latestFilePreviewRequestId) {
|
|
2021
|
+
return;
|
|
2022
|
+
}
|
|
2023
|
+
codeEl.textContent = "(file unavailable)";
|
|
2024
|
+
}
|
|
2025
|
+
}
|
|
1826
2026
|
async function focusSymbolInGraph(symbolId, options = {}) {
|
|
1827
2027
|
await focusSymbolsInGraph([symbolId], options);
|
|
1828
2028
|
}
|
|
@@ -2349,7 +2549,7 @@ async function showDetail(node, detailEl) {
|
|
|
2349
2549
|
<span class="detail-name">${escapeHtml(data.label)}</span>
|
|
2350
2550
|
<span class="detail-kind-badge" style="background:${kindColor}20;color:${kindColor}">${kind}</span>
|
|
2351
2551
|
</div>
|
|
2352
|
-
<div class="detail-file">${
|
|
2552
|
+
<div class="detail-file">${data.file ? `<button type="button" class="detail-file-link" data-file="${escapeHtml(data.file)}">${escapeHtml(data.file)}${data.startLine ? ":" + data.startLine : ""}</button>` : "unknown"}</div>
|
|
2353
2553
|
`;
|
|
2354
2554
|
html += `<div class="detail-actions">`;
|
|
2355
2555
|
html += `<button class="detail-pin header-btn" data-name="${escapeHtml(data.qualifiedName)}">${isPinned ? "Unpin" : "Pin to focus"}</button>`;
|
|
@@ -2391,6 +2591,12 @@ async function showDetail(node, detailEl) {
|
|
|
2391
2591
|
const name = pinBtn.dataset.name || "";
|
|
2392
2592
|
await togglePin(name, node, pinBtn);
|
|
2393
2593
|
});
|
|
2594
|
+
const fileLink = detailEl.querySelector(".detail-file-link");
|
|
2595
|
+
fileLink?.addEventListener("click", () => {
|
|
2596
|
+
const filePath = fileLink.dataset.file || "";
|
|
2597
|
+
if (!filePath) return;
|
|
2598
|
+
void openFilePreview(filePath);
|
|
2599
|
+
});
|
|
2394
2600
|
const explainBtn = detailEl.querySelector(".detail-explain-btn");
|
|
2395
2601
|
explainBtn.addEventListener("click", () => {
|
|
2396
2602
|
const name = explainBtn.dataset.name || "";
|
|
@@ -2563,6 +2769,7 @@ async function togglePin(name, node, btnEl) {
|
|
|
2563
2769
|
function setupToolbar() {
|
|
2564
2770
|
const searchInput = document.getElementById("graph-search");
|
|
2565
2771
|
const analyzeBtn = document.getElementById("graph-analyze");
|
|
2772
|
+
const refreshBtn = document.getElementById("graph-refresh");
|
|
2566
2773
|
const fitBtn = document.getElementById("graph-fit");
|
|
2567
2774
|
const relayoutBtn = document.getElementById("graph-relayout");
|
|
2568
2775
|
const clearBtn = document.getElementById("graph-clear");
|
|
@@ -2574,7 +2781,6 @@ function setupToolbar() {
|
|
|
2574
2781
|
dropdown.className = "search-dropdown hidden";
|
|
2575
2782
|
searchInput.parentNode.style.position = "relative";
|
|
2576
2783
|
searchInput.parentNode.appendChild(dropdown);
|
|
2577
|
-
const rankedSymbols = allSymbolNames.slice().sort((a, b2) => compareGraphNodeIds(a, b2, graphNodes));
|
|
2578
2784
|
function showDropdownResults(results) {
|
|
2579
2785
|
if (results.length === 0) {
|
|
2580
2786
|
dropdown.classList.add("hidden");
|
|
@@ -2599,6 +2805,7 @@ function setupToolbar() {
|
|
|
2599
2805
|
dropdown.classList.add("hidden");
|
|
2600
2806
|
if (type === "file") {
|
|
2601
2807
|
focusFileInGraph(id);
|
|
2808
|
+
void openFilePreview(id);
|
|
2602
2809
|
return;
|
|
2603
2810
|
}
|
|
2604
2811
|
void focusSymbolInGraph(id, {
|
|
@@ -2612,6 +2819,7 @@ function setupToolbar() {
|
|
|
2612
2819
|
});
|
|
2613
2820
|
}
|
|
2614
2821
|
function updateDropdownResults() {
|
|
2822
|
+
const rankedSymbols = allSymbolNames.slice().sort((a, b2) => compareGraphNodeIds(a, b2, graphNodes));
|
|
2615
2823
|
const results = buildGraphSearchResults({
|
|
2616
2824
|
query: searchInput.value,
|
|
2617
2825
|
symbolIds: rankedSymbols,
|
|
@@ -2645,6 +2853,9 @@ function setupToolbar() {
|
|
|
2645
2853
|
cy.fit(40);
|
|
2646
2854
|
}
|
|
2647
2855
|
});
|
|
2856
|
+
refreshBtn.addEventListener("click", () => {
|
|
2857
|
+
void refreshGraphData({ refreshIndex: true, preserveVisible: true, showFeedback: true });
|
|
2858
|
+
});
|
|
2648
2859
|
analyzeBtn.addEventListener("click", () => {
|
|
2649
2860
|
void runStructuralAnalysis();
|
|
2650
2861
|
});
|
|
@@ -2671,6 +2882,24 @@ function setupToolbar() {
|
|
|
2671
2882
|
});
|
|
2672
2883
|
}
|
|
2673
2884
|
|
|
2885
|
+
// src/model-utils.ts
|
|
2886
|
+
function getModelDisplayName(model) {
|
|
2887
|
+
return (model.name ?? model.id).trim();
|
|
2888
|
+
}
|
|
2889
|
+
function normalizeModelSearchValue(value) {
|
|
2890
|
+
return value.trim().toLocaleLowerCase().split(/\s+/).filter(Boolean);
|
|
2891
|
+
}
|
|
2892
|
+
function filterModelsByQuery(models, query) {
|
|
2893
|
+
const tokens = normalizeModelSearchValue(query);
|
|
2894
|
+
if (tokens.length === 0) {
|
|
2895
|
+
return [...models];
|
|
2896
|
+
}
|
|
2897
|
+
return models.filter((model) => {
|
|
2898
|
+
const haystack = `${getModelDisplayName(model)} ${model.id}`.toLocaleLowerCase();
|
|
2899
|
+
return tokens.every((token) => haystack.includes(token));
|
|
2900
|
+
});
|
|
2901
|
+
}
|
|
2902
|
+
|
|
2674
2903
|
// src/web/request-tracker.ts
|
|
2675
2904
|
function createLatestRequestTracker() {
|
|
2676
2905
|
let latestToken = 0;
|
|
@@ -2685,6 +2914,25 @@ function createLatestRequestTracker() {
|
|
|
2685
2914
|
};
|
|
2686
2915
|
}
|
|
2687
2916
|
|
|
2917
|
+
// src/web/setup-overlay-state.ts
|
|
2918
|
+
var DEFAULT_SETUP_INTRO = "minicode needs a model provider to run. Configure one of the following:";
|
|
2919
|
+
function deriveSetupOverlayState(input) {
|
|
2920
|
+
const missingItems = input.missing ?? [];
|
|
2921
|
+
const configuredProvider = input.configuredProvider ?? null;
|
|
2922
|
+
const isOnlyModelMissing = missingItems.length === 1 && typeof missingItems[0] === "string" && missingItems[0].includes("MODEL");
|
|
2923
|
+
const hasConfiguredProvider = isOnlyModelMissing && configuredProvider !== null;
|
|
2924
|
+
const filteredMissingItems = hasConfiguredProvider ? missingItems : missingItems.filter((item) => !item.includes("MODEL"));
|
|
2925
|
+
const introText = configuredProvider === "openrouter" && isOnlyModelMissing ? "OpenRouter is already configured. Select a model to continue:" : configuredProvider === "openai-compatible" && isOnlyModelMissing ? "An OpenAI-compatible provider is already configured. Select a model to continue:" : configuredProvider === "anthropic" && isOnlyModelMissing ? "Anthropic is already configured. Select a model to continue:" : DEFAULT_SETUP_INTRO;
|
|
2926
|
+
return {
|
|
2927
|
+
introText,
|
|
2928
|
+
hideQuickConnects: hasConfiguredProvider,
|
|
2929
|
+
hideOpenRouterSpotlight: configuredProvider === "openrouter" && isOnlyModelMissing,
|
|
2930
|
+
missingItems: filteredMissingItems,
|
|
2931
|
+
showModelSelectionHint: hasConfiguredProvider,
|
|
2932
|
+
modelSelectionNote: configuredProvider === "openrouter" && isOnlyModelMissing ? 'If you are on the OpenRouter free tier, search "free" in the model dropdown to find supported free models.' : null
|
|
2933
|
+
};
|
|
2934
|
+
}
|
|
2935
|
+
|
|
2688
2936
|
// src/web/app.ts
|
|
2689
2937
|
var messagesEl = document.getElementById("messages");
|
|
2690
2938
|
var chatForm = document.getElementById("chat-form");
|
|
@@ -2695,6 +2943,7 @@ var statusBadge = document.getElementById("status-badge");
|
|
|
2695
2943
|
var modelInfo = document.getElementById("model-info");
|
|
2696
2944
|
var modelBtn = document.getElementById("model-btn");
|
|
2697
2945
|
var modelDropdown = document.getElementById("model-dropdown");
|
|
2946
|
+
var modelSearchInput = document.getElementById("model-search");
|
|
2698
2947
|
var modelList = document.getElementById("model-list");
|
|
2699
2948
|
var sessionBtn = document.getElementById("session-btn");
|
|
2700
2949
|
var sessionDropdown = document.getElementById("session-dropdown");
|
|
@@ -2715,17 +2964,36 @@ var openRouterConnectCloseBtn = document.getElementById("openrouter-connect-clos
|
|
|
2715
2964
|
var openRouterConnectCancelBtn = document.getElementById("openrouter-connect-cancel");
|
|
2716
2965
|
var openRouterConnectContinueBtn = document.getElementById("openrouter-connect-continue");
|
|
2717
2966
|
var openRouterPersistCheckbox = document.getElementById("openrouter-persist-checkbox");
|
|
2967
|
+
var openAiCompatibleConnectModal = document.getElementById("openai-compatible-connect-modal");
|
|
2968
|
+
var openAiCompatibleConnectBackdrop = document.getElementById("openai-compatible-connect-backdrop");
|
|
2969
|
+
var openAiCompatibleConnectCloseBtn = document.getElementById("openai-compatible-connect-close");
|
|
2970
|
+
var openAiCompatibleConnectCancelBtn = document.getElementById("openai-compatible-connect-cancel");
|
|
2971
|
+
var openAiCompatibleConnectContinueBtn = document.getElementById("openai-compatible-connect-continue");
|
|
2972
|
+
var openAiCompatiblePresetSelect = document.getElementById("openai-compatible-preset");
|
|
2973
|
+
var openAiCompatiblePresetHelp = document.getElementById("openai-compatible-preset-help");
|
|
2974
|
+
var openAiCompatibleBaseUrlInput = document.getElementById("openai-compatible-base-url");
|
|
2975
|
+
var openAiCompatibleApiKeyInput = document.getElementById("openai-compatible-api-key");
|
|
2976
|
+
var openAiCompatiblePersistCheckbox = document.getElementById("openai-compatible-persist-checkbox");
|
|
2977
|
+
var openAiCompatibleConnectStatus = document.getElementById("openai-compatible-connect-status");
|
|
2718
2978
|
var settingsPath = document.getElementById("settings-path");
|
|
2719
2979
|
var settingsList = document.getElementById("settings-list");
|
|
2720
2980
|
var settingsBanner = document.getElementById("settings-banner");
|
|
2721
2981
|
var settingsOpenRouterSession = document.getElementById("settings-openrouter-session");
|
|
2722
2982
|
var settingsOpenRouterSessionMeta = document.getElementById("settings-openrouter-session-meta");
|
|
2983
|
+
var settingsOpenAiCompatibleSession = document.getElementById("settings-openai-compatible-session");
|
|
2984
|
+
var settingsOpenAiCompatibleSessionMeta = document.getElementById("settings-openai-compatible-session-meta");
|
|
2723
2985
|
var settingsSaveBtn = document.getElementById("settings-save");
|
|
2724
2986
|
var settingsResetBtn = document.getElementById("settings-reset");
|
|
2725
2987
|
var disconnectOpenRouterBtn = document.getElementById("disconnect-openrouter-btn");
|
|
2988
|
+
var disconnectOpenAiCompatibleBtn = document.getElementById("disconnect-openai-compatible-btn");
|
|
2726
2989
|
var connectOpenRouterButtons = Array.from(
|
|
2727
2990
|
document.querySelectorAll("[data-openrouter-connect]")
|
|
2728
2991
|
);
|
|
2992
|
+
var connectOpenAiCompatibleButtons = Array.from(
|
|
2993
|
+
document.querySelectorAll("[data-openai-compatible-connect]")
|
|
2994
|
+
);
|
|
2995
|
+
var GRAPH_REFRESH_TOOL_NAMES = /* @__PURE__ */ new Set(["write_file", "edit_file", "run_command"]);
|
|
2996
|
+
var configOverlayQuickConnects = document.getElementById("config-overlay-quick-connects");
|
|
2729
2997
|
var configOverlaySpotlight = document.getElementById("config-overlay-spotlight");
|
|
2730
2998
|
var configOverlayIntro = document.getElementById("config-overlay-intro");
|
|
2731
2999
|
var configConnectStatus = document.getElementById("config-connect-status");
|
|
@@ -2737,10 +3005,33 @@ var settingsPayload = null;
|
|
|
2737
3005
|
var activeSavedSession = null;
|
|
2738
3006
|
var activeBaseUrl = "";
|
|
2739
3007
|
var sessionOpenRouterConnected = false;
|
|
3008
|
+
var sessionOpenAiCompatibleConnected = false;
|
|
2740
3009
|
var sessionRefreshTracker = createLatestRequestTracker();
|
|
2741
3010
|
var TOOL_RESULT_MAX = 500;
|
|
2742
3011
|
var OPENROUTER_PKCE_VERIFIER_KEY = "minicode:openrouter:pkce-verifier";
|
|
2743
3012
|
var OPENROUTER_PERSIST_TO_ENV_KEY = "minicode:openrouter:persist-to-env";
|
|
3013
|
+
var OPENAI_COMPATIBLE_PRESETS = {
|
|
3014
|
+
lmstudio: {
|
|
3015
|
+
baseUrl: "http://localhost:1234/v1",
|
|
3016
|
+
helpText: "LM Studio pre-fills the default local server endpoint at http://localhost:1234/v1.",
|
|
3017
|
+
apiKeyPlaceholder: "Leave blank for LM Studio unless local auth is enabled"
|
|
3018
|
+
},
|
|
3019
|
+
openai: {
|
|
3020
|
+
baseUrl: "https://api.openai.com/v1",
|
|
3021
|
+
helpText: "OpenAI uses https://api.openai.com/v1 and typically requires an API key.",
|
|
3022
|
+
apiKeyPlaceholder: "Enter your OpenAI API key"
|
|
3023
|
+
},
|
|
3024
|
+
ollama: {
|
|
3025
|
+
baseUrl: "http://localhost:11434/v1",
|
|
3026
|
+
helpText: "Ollama pre-fills the default local server endpoint at http://localhost:11434/v1.",
|
|
3027
|
+
apiKeyPlaceholder: "Leave blank for Ollama unless your proxy requires auth"
|
|
3028
|
+
},
|
|
3029
|
+
custom: {
|
|
3030
|
+
baseUrl: "",
|
|
3031
|
+
helpText: "Custom leaves the endpoint fully editable so you can point minicode at any OpenAI-compatible API.",
|
|
3032
|
+
apiKeyPlaceholder: "Add an API key only if this endpoint requires auth"
|
|
3033
|
+
}
|
|
3034
|
+
};
|
|
2744
3035
|
function connect() {
|
|
2745
3036
|
const protocol = location.protocol === "https:" ? "wss:" : "ws:";
|
|
2746
3037
|
ws = new WebSocket(`${protocol}//${location.host}`);
|
|
@@ -2788,6 +3079,51 @@ function clearConfigConnectStatus() {
|
|
|
2788
3079
|
configConnectStatus.textContent = "";
|
|
2789
3080
|
configConnectStatus.className = "config-connect-status hidden";
|
|
2790
3081
|
}
|
|
3082
|
+
function setOpenAiCompatibleConnectStatus(message, tone) {
|
|
3083
|
+
openAiCompatibleConnectStatus.textContent = message;
|
|
3084
|
+
openAiCompatibleConnectStatus.className = `config-connect-status ${tone}`;
|
|
3085
|
+
}
|
|
3086
|
+
function clearOpenAiCompatibleConnectStatus() {
|
|
3087
|
+
openAiCompatibleConnectStatus.textContent = "";
|
|
3088
|
+
openAiCompatibleConnectStatus.className = "config-connect-status hidden";
|
|
3089
|
+
}
|
|
3090
|
+
function normalizeBaseUrl(value) {
|
|
3091
|
+
return value.trim().replace(/\/+$/, "");
|
|
3092
|
+
}
|
|
3093
|
+
function inferOpenAiCompatiblePreset(baseUrl) {
|
|
3094
|
+
const normalizedBaseUrl = normalizeBaseUrl(baseUrl).toLowerCase();
|
|
3095
|
+
if (normalizedBaseUrl === OPENAI_COMPATIBLE_PRESETS.lmstudio.baseUrl) {
|
|
3096
|
+
return "lmstudio";
|
|
3097
|
+
}
|
|
3098
|
+
if (normalizedBaseUrl === OPENAI_COMPATIBLE_PRESETS.openai.baseUrl) {
|
|
3099
|
+
return "openai";
|
|
3100
|
+
}
|
|
3101
|
+
if (normalizedBaseUrl === OPENAI_COMPATIBLE_PRESETS.ollama.baseUrl) {
|
|
3102
|
+
return "ollama";
|
|
3103
|
+
}
|
|
3104
|
+
return "custom";
|
|
3105
|
+
}
|
|
3106
|
+
function applyOpenAiCompatiblePreset(preset, options = {}) {
|
|
3107
|
+
const presetConfig = OPENAI_COMPATIBLE_PRESETS[preset];
|
|
3108
|
+
openAiCompatiblePresetHelp.textContent = presetConfig.helpText;
|
|
3109
|
+
openAiCompatibleApiKeyInput.placeholder = presetConfig.apiKeyPlaceholder;
|
|
3110
|
+
if (preset === "custom" && options.preserveCustomValue) {
|
|
3111
|
+
return;
|
|
3112
|
+
}
|
|
3113
|
+
openAiCompatibleBaseUrlInput.value = presetConfig.baseUrl;
|
|
3114
|
+
}
|
|
3115
|
+
function isModalOpen(modal) {
|
|
3116
|
+
return !modal.classList.contains("hidden");
|
|
3117
|
+
}
|
|
3118
|
+
function isSettingsModalOpen() {
|
|
3119
|
+
return isModalOpen(settingsModal);
|
|
3120
|
+
}
|
|
3121
|
+
function isOpenRouterConnectModalOpen() {
|
|
3122
|
+
return isModalOpen(openRouterConnectModal);
|
|
3123
|
+
}
|
|
3124
|
+
function isOpenAiCompatibleConnectModalOpen() {
|
|
3125
|
+
return isModalOpen(openAiCompatibleConnectModal);
|
|
3126
|
+
}
|
|
2791
3127
|
function encodeBase64Url(bytes) {
|
|
2792
3128
|
let binary = "";
|
|
2793
3129
|
for (const byte of bytes) {
|
|
@@ -2860,6 +3196,7 @@ async function maybeHandleOpenRouterCallback() {
|
|
|
2860
3196
|
if (onlyModelMissing) {
|
|
2861
3197
|
modelDropdown.classList.remove("hidden");
|
|
2862
3198
|
sessionDropdown.classList.add("hidden");
|
|
3199
|
+
focusModelSearchInput();
|
|
2863
3200
|
}
|
|
2864
3201
|
} catch (error) {
|
|
2865
3202
|
const message = error instanceof Error ? error.message : "Failed to connect OpenRouter";
|
|
@@ -2895,6 +3232,76 @@ async function disconnectOpenRouter() {
|
|
|
2895
3232
|
disconnectOpenRouterBtn.disabled = false;
|
|
2896
3233
|
}
|
|
2897
3234
|
}
|
|
3235
|
+
async function connectOpenAiCompatible() {
|
|
3236
|
+
const baseUrl = normalizeBaseUrl(openAiCompatibleBaseUrlInput.value);
|
|
3237
|
+
const apiKey = openAiCompatibleApiKeyInput.value.trim();
|
|
3238
|
+
if (!baseUrl) {
|
|
3239
|
+
setOpenAiCompatibleConnectStatus("Endpoint is required.", "error");
|
|
3240
|
+
return;
|
|
3241
|
+
}
|
|
3242
|
+
clearOpenAiCompatibleConnectStatus();
|
|
3243
|
+
clearSettingsBanner();
|
|
3244
|
+
openAiCompatibleConnectContinueBtn.disabled = true;
|
|
3245
|
+
try {
|
|
3246
|
+
const res = await fetch("/api/openai-compatible/connect", {
|
|
3247
|
+
method: "POST",
|
|
3248
|
+
headers: { "Content-Type": "application/json" },
|
|
3249
|
+
body: JSON.stringify({
|
|
3250
|
+
baseUrl,
|
|
3251
|
+
apiKey,
|
|
3252
|
+
persistToEnv: openAiCompatiblePersistCheckbox.checked
|
|
3253
|
+
})
|
|
3254
|
+
});
|
|
3255
|
+
const body = await res.json();
|
|
3256
|
+
if (!res.ok) {
|
|
3257
|
+
throw new Error("error" in body ? body.error : `Failed to connect OpenAI-compatible provider (${res.status})`);
|
|
3258
|
+
}
|
|
3259
|
+
activeBaseUrl = body.baseUrl;
|
|
3260
|
+
addMessage(body.message, "thinking");
|
|
3261
|
+
const tone = body.persistWarning ? "info" : body.needsSetup ? "info" : "success";
|
|
3262
|
+
setConfigConnectStatus(body.message, tone);
|
|
3263
|
+
setSettingsBanner(body.message, tone === "success" ? "success" : "info");
|
|
3264
|
+
closeOpenAiCompatibleConnectModal();
|
|
3265
|
+
await fetchStatus();
|
|
3266
|
+
await refreshModelList();
|
|
3267
|
+
const onlyModelMissing = body.needsSetup && body.missing.length === 1 && body.missing[0]?.includes("MODEL");
|
|
3268
|
+
if (onlyModelMissing) {
|
|
3269
|
+
modelDropdown.classList.remove("hidden");
|
|
3270
|
+
sessionDropdown.classList.add("hidden");
|
|
3271
|
+
focusModelSearchInput();
|
|
3272
|
+
}
|
|
3273
|
+
} catch (error) {
|
|
3274
|
+
const message = error instanceof Error ? error.message : "Failed to connect OpenAI-compatible provider";
|
|
3275
|
+
setOpenAiCompatibleConnectStatus(message, "error");
|
|
3276
|
+
} finally {
|
|
3277
|
+
openAiCompatibleConnectContinueBtn.disabled = false;
|
|
3278
|
+
}
|
|
3279
|
+
}
|
|
3280
|
+
async function disconnectOpenAiCompatible() {
|
|
3281
|
+
disconnectOpenAiCompatibleBtn.disabled = true;
|
|
3282
|
+
clearSettingsBanner();
|
|
3283
|
+
try {
|
|
3284
|
+
const res = await fetch("/api/openai-compatible/disconnect", {
|
|
3285
|
+
method: "POST",
|
|
3286
|
+
headers: { "Content-Type": "application/json" }
|
|
3287
|
+
});
|
|
3288
|
+
const body = await res.json();
|
|
3289
|
+
if (!res.ok) {
|
|
3290
|
+
throw new Error("error" in body ? body.error : `Failed to disconnect OpenAI-compatible provider (${res.status})`);
|
|
3291
|
+
}
|
|
3292
|
+
activeBaseUrl = body.baseUrl;
|
|
3293
|
+
addMessage(body.message, "thinking");
|
|
3294
|
+
setSettingsBanner(body.message, body.disconnected ? "success" : "info");
|
|
3295
|
+
clearConfigConnectStatus();
|
|
3296
|
+
await fetchStatus();
|
|
3297
|
+
await refreshModelList();
|
|
3298
|
+
} catch (error) {
|
|
3299
|
+
const message = error instanceof Error ? error.message : "Failed to disconnect OpenAI-compatible provider";
|
|
3300
|
+
setSettingsBanner(message, "error");
|
|
3301
|
+
} finally {
|
|
3302
|
+
disconnectOpenAiCompatibleBtn.disabled = false;
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
2898
3305
|
async function fetchStatus() {
|
|
2899
3306
|
try {
|
|
2900
3307
|
const res = await fetch("/api/status");
|
|
@@ -2904,22 +3311,32 @@ async function fetchStatus() {
|
|
|
2904
3311
|
activeModel = data.model;
|
|
2905
3312
|
activeBaseUrl = data.baseUrl ?? "";
|
|
2906
3313
|
sessionOpenRouterConnected = data.sessionOpenRouterConnected ?? false;
|
|
2907
|
-
|
|
3314
|
+
sessionOpenAiCompatibleConnected = data.sessionOpenAiCompatibleConnected ?? false;
|
|
3315
|
+
renderSessionProviderControls();
|
|
2908
3316
|
if (data.needsSetup) {
|
|
2909
3317
|
configOverlay.classList.remove("hidden");
|
|
2910
3318
|
chatInput.disabled = true;
|
|
2911
3319
|
sendBtn.disabled = true;
|
|
2912
3320
|
const missingEl = document.getElementById("config-missing");
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
3321
|
+
const overlayState = deriveSetupOverlayState({
|
|
3322
|
+
configuredProvider: data.configuredProvider ?? null,
|
|
3323
|
+
missing: data.missing ?? []
|
|
3324
|
+
});
|
|
3325
|
+
if (configOverlayIntro) {
|
|
3326
|
+
configOverlayIntro.textContent = overlayState.introText;
|
|
3327
|
+
}
|
|
3328
|
+
configOverlayQuickConnects?.classList.toggle("hidden", overlayState.hideQuickConnects);
|
|
3329
|
+
configOverlaySpotlight?.classList.toggle("hidden", overlayState.hideOpenRouterSpotlight);
|
|
3330
|
+
if (missingEl) {
|
|
3331
|
+
if (overlayState.missingItems.length > 0) {
|
|
3332
|
+
const hint = overlayState.showModelSelectionHint ? ` \u2014 select one from the <strong>model dropdown</strong> above, or set it in config` : "";
|
|
3333
|
+
const note = overlayState.modelSelectionNote ? ` ${escapeHtml(overlayState.modelSelectionNote)}` : "";
|
|
3334
|
+
missingEl.innerHTML = `<strong>Missing:</strong> ${overlayState.missingItems.map(escapeHtml).join(", ")}${hint}${note}`;
|
|
3335
|
+
missingEl.classList.remove("hidden");
|
|
3336
|
+
} else {
|
|
3337
|
+
missingEl.classList.add("hidden");
|
|
3338
|
+
missingEl.innerHTML = "";
|
|
2918
3339
|
}
|
|
2919
|
-
configOverlaySpotlight?.classList.toggle("hidden", hasPersistedOpenRouter);
|
|
2920
|
-
const hint = isOnlyModelMissing ? ` \u2014 select one from the <strong>model dropdown</strong> above, or set it in config` : "";
|
|
2921
|
-
missingEl.innerHTML = `<strong>Missing:</strong> ${data.missing.map(escapeHtml).join(", ")}${hint}`;
|
|
2922
|
-
missingEl.classList.remove("hidden");
|
|
2923
3340
|
}
|
|
2924
3341
|
} else {
|
|
2925
3342
|
configOverlay.classList.add("hidden");
|
|
@@ -2931,8 +3348,9 @@ async function fetchStatus() {
|
|
|
2931
3348
|
missingEl.innerHTML = "";
|
|
2932
3349
|
}
|
|
2933
3350
|
if (configOverlayIntro) {
|
|
2934
|
-
configOverlayIntro.textContent =
|
|
3351
|
+
configOverlayIntro.textContent = DEFAULT_SETUP_INTRO;
|
|
2935
3352
|
}
|
|
3353
|
+
configOverlayQuickConnects?.classList.remove("hidden");
|
|
2936
3354
|
configOverlaySpotlight?.classList.remove("hidden");
|
|
2937
3355
|
}
|
|
2938
3356
|
} catch {
|
|
@@ -2981,6 +3399,9 @@ function handleServerMessage(msg) {
|
|
|
2981
3399
|
break;
|
|
2982
3400
|
case "tool_call_end":
|
|
2983
3401
|
finalizeToolCall(msg.name || "", msg.result || "", msg.elapsedMs || 0);
|
|
3402
|
+
if (GRAPH_REFRESH_TOOL_NAMES.has(msg.name || "")) {
|
|
3403
|
+
scheduleGraphDataRefresh();
|
|
3404
|
+
}
|
|
2984
3405
|
break;
|
|
2985
3406
|
case "turn_end":
|
|
2986
3407
|
if (hadToolCalls && msg.text) {
|
|
@@ -3166,19 +3587,13 @@ function addUsageInfo(usage) {
|
|
|
3166
3587
|
function scrollToBottom() {
|
|
3167
3588
|
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
3168
3589
|
}
|
|
3169
|
-
function
|
|
3590
|
+
function closeModelDropdown() {
|
|
3170
3591
|
modelDropdown.classList.add("hidden");
|
|
3171
|
-
|
|
3172
|
-
}
|
|
3173
|
-
function isSettingsModalOpen() {
|
|
3174
|
-
return !settingsModal.classList.contains("hidden");
|
|
3592
|
+
modelSearchInput.value = "";
|
|
3175
3593
|
}
|
|
3176
|
-
function
|
|
3177
|
-
|
|
3178
|
-
|
|
3179
|
-
function syncModalOpenState() {
|
|
3180
|
-
const anyModalOpen = isSettingsModalOpen() || isOpenRouterConnectModalOpen();
|
|
3181
|
-
document.body.classList.toggle("modal-open", anyModalOpen);
|
|
3594
|
+
function closeHeaderMenus() {
|
|
3595
|
+
closeModelDropdown();
|
|
3596
|
+
sessionDropdown.classList.add("hidden");
|
|
3182
3597
|
}
|
|
3183
3598
|
function formatSettingsValue(value) {
|
|
3184
3599
|
return value === null ? "(unset)" : String(value);
|
|
@@ -3191,16 +3606,31 @@ function clearSettingsBanner() {
|
|
|
3191
3606
|
settingsBanner.textContent = "";
|
|
3192
3607
|
settingsBanner.className = "settings-banner hidden";
|
|
3193
3608
|
}
|
|
3194
|
-
function
|
|
3609
|
+
function renderSessionProviderControls() {
|
|
3195
3610
|
if (sessionOpenRouterConnected) {
|
|
3196
3611
|
settingsOpenRouterSession.classList.remove("hidden");
|
|
3197
3612
|
settingsOpenRouterSessionMeta.textContent = activeBaseUrl ? `Endpoint: ${activeBaseUrl}. This session-only connection overrides your original provider settings until you disconnect or restart serve.` : "This session-only connection overrides your original provider settings until you disconnect or restart serve.";
|
|
3613
|
+
settingsOpenAiCompatibleSession.classList.add("hidden");
|
|
3614
|
+
settingsOpenAiCompatibleSessionMeta.textContent = "";
|
|
3615
|
+
disconnectOpenRouterBtn.disabled = false;
|
|
3616
|
+
disconnectOpenAiCompatibleBtn.disabled = false;
|
|
3617
|
+
return;
|
|
3618
|
+
}
|
|
3619
|
+
if (sessionOpenAiCompatibleConnected) {
|
|
3620
|
+
settingsOpenAiCompatibleSession.classList.remove("hidden");
|
|
3621
|
+
settingsOpenAiCompatibleSessionMeta.textContent = activeBaseUrl ? `Endpoint: ${activeBaseUrl}. This session-only connection overrides your original provider settings until you disconnect or restart serve.` : "This session-only connection overrides your original provider settings until you disconnect or restart serve.";
|
|
3622
|
+
settingsOpenRouterSession.classList.add("hidden");
|
|
3623
|
+
settingsOpenRouterSessionMeta.textContent = "";
|
|
3198
3624
|
disconnectOpenRouterBtn.disabled = false;
|
|
3625
|
+
disconnectOpenAiCompatibleBtn.disabled = false;
|
|
3199
3626
|
return;
|
|
3200
3627
|
}
|
|
3201
3628
|
settingsOpenRouterSession.classList.add("hidden");
|
|
3202
3629
|
settingsOpenRouterSessionMeta.textContent = "";
|
|
3630
|
+
settingsOpenAiCompatibleSession.classList.add("hidden");
|
|
3631
|
+
settingsOpenAiCompatibleSessionMeta.textContent = "";
|
|
3203
3632
|
disconnectOpenRouterBtn.disabled = false;
|
|
3633
|
+
disconnectOpenAiCompatibleBtn.disabled = false;
|
|
3204
3634
|
}
|
|
3205
3635
|
function createSettingsControl(entry, inputId) {
|
|
3206
3636
|
const value = entry.persistedValue;
|
|
@@ -3394,30 +3824,43 @@ function updateSettingsActions() {
|
|
|
3394
3824
|
}
|
|
3395
3825
|
function openSettings() {
|
|
3396
3826
|
closeHeaderMenus();
|
|
3397
|
-
settingsModal
|
|
3398
|
-
settingsModal.setAttribute("aria-hidden", "false");
|
|
3399
|
-
syncModalOpenState();
|
|
3827
|
+
openModal(settingsModal);
|
|
3400
3828
|
void loadSettings();
|
|
3401
3829
|
}
|
|
3402
3830
|
function closeSettings() {
|
|
3403
|
-
settingsModal
|
|
3404
|
-
settingsModal.setAttribute("aria-hidden", "true");
|
|
3405
|
-
syncModalOpenState();
|
|
3831
|
+
closeModal(settingsModal);
|
|
3406
3832
|
clearSettingsBanner();
|
|
3407
3833
|
}
|
|
3408
3834
|
function openOpenRouterConnectModal() {
|
|
3409
3835
|
closeHeaderMenus();
|
|
3410
|
-
openRouterConnectModal
|
|
3411
|
-
openRouterConnectModal.setAttribute("aria-hidden", "false");
|
|
3836
|
+
openModal(openRouterConnectModal);
|
|
3412
3837
|
openRouterPersistCheckbox.checked = false;
|
|
3413
3838
|
openRouterConnectContinueBtn.disabled = false;
|
|
3414
|
-
syncModalOpenState();
|
|
3415
3839
|
}
|
|
3416
3840
|
function closeOpenRouterConnectModal() {
|
|
3417
|
-
openRouterConnectModal
|
|
3418
|
-
openRouterConnectModal.setAttribute("aria-hidden", "true");
|
|
3841
|
+
closeModal(openRouterConnectModal);
|
|
3419
3842
|
openRouterConnectContinueBtn.disabled = false;
|
|
3420
|
-
|
|
3843
|
+
}
|
|
3844
|
+
function openOpenAiCompatibleConnectModal() {
|
|
3845
|
+
closeHeaderMenus();
|
|
3846
|
+
openModal(openAiCompatibleConnectModal);
|
|
3847
|
+
const preset = inferOpenAiCompatiblePreset(activeBaseUrl);
|
|
3848
|
+
openAiCompatiblePresetSelect.value = preset;
|
|
3849
|
+
openAiCompatibleBaseUrlInput.value = preset === "custom" ? activeBaseUrl : OPENAI_COMPATIBLE_PRESETS[preset].baseUrl;
|
|
3850
|
+
openAiCompatibleApiKeyInput.value = "";
|
|
3851
|
+
openAiCompatiblePersistCheckbox.checked = false;
|
|
3852
|
+
openAiCompatibleConnectContinueBtn.disabled = false;
|
|
3853
|
+
applyOpenAiCompatiblePreset(preset, { preserveCustomValue: preset === "custom" });
|
|
3854
|
+
clearOpenAiCompatibleConnectStatus();
|
|
3855
|
+
requestAnimationFrame(() => {
|
|
3856
|
+
openAiCompatibleBaseUrlInput.focus();
|
|
3857
|
+
openAiCompatibleBaseUrlInput.select();
|
|
3858
|
+
});
|
|
3859
|
+
}
|
|
3860
|
+
function closeOpenAiCompatibleConnectModal() {
|
|
3861
|
+
closeModal(openAiCompatibleConnectModal);
|
|
3862
|
+
openAiCompatibleConnectContinueBtn.disabled = false;
|
|
3863
|
+
clearOpenAiCompatibleConnectStatus();
|
|
3421
3864
|
}
|
|
3422
3865
|
chatForm.addEventListener("submit", (e) => {
|
|
3423
3866
|
e.preventDefault();
|
|
@@ -3442,30 +3885,99 @@ chatInput.addEventListener("keydown", (e) => {
|
|
|
3442
3885
|
}
|
|
3443
3886
|
});
|
|
3444
3887
|
var activeModel = "";
|
|
3888
|
+
var availableModels = [];
|
|
3889
|
+
function focusModelSearchInput() {
|
|
3890
|
+
requestAnimationFrame(() => {
|
|
3891
|
+
modelSearchInput.focus();
|
|
3892
|
+
modelSearchInput.select();
|
|
3893
|
+
});
|
|
3894
|
+
}
|
|
3895
|
+
function renderModelList() {
|
|
3896
|
+
const filteredModels = filterModelsByQuery(availableModels, modelSearchInput.value);
|
|
3897
|
+
if (availableModels.length === 0) {
|
|
3898
|
+
modelList.innerHTML = '<div class="dropdown-empty">No models available</div>';
|
|
3899
|
+
return;
|
|
3900
|
+
}
|
|
3901
|
+
if (filteredModels.length === 0) {
|
|
3902
|
+
modelList.innerHTML = '<div class="dropdown-empty">No matching models</div>';
|
|
3903
|
+
return;
|
|
3904
|
+
}
|
|
3905
|
+
modelList.innerHTML = "";
|
|
3906
|
+
for (const model of filteredModels) {
|
|
3907
|
+
const el = document.createElement("button");
|
|
3908
|
+
el.type = "button";
|
|
3909
|
+
el.className = "model-item" + (model.id === activeModel ? " active" : "");
|
|
3910
|
+
el.title = model.id;
|
|
3911
|
+
const body = document.createElement("div");
|
|
3912
|
+
body.className = "model-item-body";
|
|
3913
|
+
const name = document.createElement("span");
|
|
3914
|
+
name.className = "model-item-name";
|
|
3915
|
+
name.textContent = getModelDisplayName(model);
|
|
3916
|
+
body.appendChild(name);
|
|
3917
|
+
if ((model.name ?? "").trim() && getModelDisplayName(model) !== model.id) {
|
|
3918
|
+
const subtitle = document.createElement("span");
|
|
3919
|
+
subtitle.className = "model-item-subtitle";
|
|
3920
|
+
subtitle.textContent = model.id;
|
|
3921
|
+
body.appendChild(subtitle);
|
|
3922
|
+
}
|
|
3923
|
+
el.appendChild(body);
|
|
3924
|
+
if (model.id === activeModel) {
|
|
3925
|
+
const badge = document.createElement("span");
|
|
3926
|
+
badge.className = "model-item-badge";
|
|
3927
|
+
badge.textContent = "Active";
|
|
3928
|
+
el.appendChild(badge);
|
|
3929
|
+
}
|
|
3930
|
+
el.addEventListener("click", () => {
|
|
3931
|
+
void switchModel(model.id);
|
|
3932
|
+
});
|
|
3933
|
+
modelList.appendChild(el);
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3445
3936
|
modelBtn.addEventListener("click", (e) => {
|
|
3446
3937
|
e.stopPropagation();
|
|
3447
3938
|
const isOpen = !modelDropdown.classList.contains("hidden");
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
refreshModelList();
|
|
3939
|
+
if (isOpen) {
|
|
3940
|
+
closeModelDropdown();
|
|
3941
|
+
return;
|
|
3452
3942
|
}
|
|
3943
|
+
closeHeaderMenus();
|
|
3944
|
+
modelDropdown.classList.remove("hidden");
|
|
3945
|
+
modelSearchInput.value = "";
|
|
3946
|
+
void refreshModelList({ focusSearch: true });
|
|
3453
3947
|
});
|
|
3454
3948
|
document.addEventListener("click", (e) => {
|
|
3455
3949
|
if (!modelDropdown.contains(e.target) && e.target !== modelBtn) {
|
|
3456
|
-
|
|
3950
|
+
closeModelDropdown();
|
|
3457
3951
|
}
|
|
3458
3952
|
});
|
|
3459
|
-
|
|
3953
|
+
modelSearchInput.addEventListener("input", () => {
|
|
3954
|
+
renderModelList();
|
|
3955
|
+
});
|
|
3956
|
+
modelSearchInput.addEventListener("keydown", (e) => {
|
|
3957
|
+
if (e.key === "Escape") {
|
|
3958
|
+
if (modelSearchInput.value) {
|
|
3959
|
+
modelSearchInput.value = "";
|
|
3960
|
+
renderModelList();
|
|
3961
|
+
return;
|
|
3962
|
+
}
|
|
3963
|
+
closeModelDropdown();
|
|
3964
|
+
}
|
|
3965
|
+
});
|
|
3966
|
+
modelSearchInput.addEventListener("click", (e) => {
|
|
3967
|
+
e.stopPropagation();
|
|
3968
|
+
});
|
|
3969
|
+
async function refreshModelList(options = {}) {
|
|
3460
3970
|
try {
|
|
3461
3971
|
const res = await fetch("/api/models");
|
|
3462
3972
|
const data = await res.json();
|
|
3463
3973
|
activeModel = data.activeModel;
|
|
3974
|
+
availableModels = data.models ?? [];
|
|
3464
3975
|
const hasActiveModel = !!activeModel && data.models.some((model) => model.id === activeModel);
|
|
3465
3976
|
if (!data.models || data.models.length === 0) {
|
|
3466
3977
|
modelInfo.textContent = "Select model";
|
|
3467
3978
|
modelInfo.classList.add("placeholder");
|
|
3468
|
-
|
|
3979
|
+
availableModels = [];
|
|
3980
|
+
renderModelList();
|
|
3469
3981
|
return;
|
|
3470
3982
|
}
|
|
3471
3983
|
if (hasActiveModel) {
|
|
@@ -3475,18 +3987,12 @@ async function refreshModelList() {
|
|
|
3475
3987
|
modelInfo.textContent = "Select model";
|
|
3476
3988
|
modelInfo.classList.add("placeholder");
|
|
3477
3989
|
}
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
el.className = "model-item" + (m2.id === activeModel ? " active" : "");
|
|
3482
|
-
el.textContent = m2.name ?? m2.id;
|
|
3483
|
-
el.title = m2.id;
|
|
3484
|
-
el.addEventListener("click", () => {
|
|
3485
|
-
void switchModel(m2.id);
|
|
3486
|
-
});
|
|
3487
|
-
modelList.appendChild(el);
|
|
3990
|
+
renderModelList();
|
|
3991
|
+
if (options.focusSearch && !modelDropdown.classList.contains("hidden")) {
|
|
3992
|
+
focusModelSearchInput();
|
|
3488
3993
|
}
|
|
3489
3994
|
} catch {
|
|
3995
|
+
availableModels = [];
|
|
3490
3996
|
modelList.innerHTML = '<div class="dropdown-empty">Failed to load models</div>';
|
|
3491
3997
|
}
|
|
3492
3998
|
}
|
|
@@ -3507,7 +4013,8 @@ async function switchModel(modelId) {
|
|
|
3507
4013
|
modelInfo.textContent = modelId || "Select model";
|
|
3508
4014
|
modelInfo.classList.toggle("placeholder", !modelId);
|
|
3509
4015
|
activeModel = modelId;
|
|
3510
|
-
|
|
4016
|
+
renderModelList();
|
|
4017
|
+
closeModelDropdown();
|
|
3511
4018
|
if (body.persistedToEnv) {
|
|
3512
4019
|
addMessage(`Model switched to: ${modelId}. Saved as the default in ~/.minicode/.env.`, "thinking");
|
|
3513
4020
|
} else {
|
|
@@ -3523,7 +4030,7 @@ sessionBtn.addEventListener("click", (e) => {
|
|
|
3523
4030
|
e.stopPropagation();
|
|
3524
4031
|
const isOpen = !sessionDropdown.classList.contains("hidden");
|
|
3525
4032
|
sessionDropdown.classList.toggle("hidden");
|
|
3526
|
-
|
|
4033
|
+
closeModelDropdown();
|
|
3527
4034
|
if (!isOpen) {
|
|
3528
4035
|
refreshSessionList();
|
|
3529
4036
|
}
|
|
@@ -3544,16 +4051,20 @@ saveBtn.addEventListener("click", async () => {
|
|
|
3544
4051
|
headers: { "Content-Type": "application/json" },
|
|
3545
4052
|
body: JSON.stringify({ label })
|
|
3546
4053
|
});
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
addMessage(
|
|
3551
|
-
`${isUpdatingCurrentSession ? "Session updated" : "Session saved"}: "${data.label}"`,
|
|
3552
|
-
"thinking"
|
|
3553
|
-
);
|
|
3554
|
-
await refreshSessionList();
|
|
4054
|
+
const body = await res.json();
|
|
4055
|
+
if (!res.ok) {
|
|
4056
|
+
throw new Error("error" in body ? body.error : `Failed to save session (${res.status})`);
|
|
3555
4057
|
}
|
|
3556
|
-
|
|
4058
|
+
const data = body;
|
|
4059
|
+
saveLabelInput.value = "";
|
|
4060
|
+
addMessage(
|
|
4061
|
+
`${isUpdatingCurrentSession ? "Session updated" : "Session saved"}: "${data.label}"`,
|
|
4062
|
+
"thinking"
|
|
4063
|
+
);
|
|
4064
|
+
await refreshSessionList();
|
|
4065
|
+
} catch (error) {
|
|
4066
|
+
const message = error instanceof Error ? error.message : "Failed to save session";
|
|
4067
|
+
addMessage(message, "error");
|
|
3557
4068
|
} finally {
|
|
3558
4069
|
saveBtn.removeAttribute("disabled");
|
|
3559
4070
|
}
|
|
@@ -3635,12 +4146,16 @@ sessionUpdateBtn.addEventListener("click", async () => {
|
|
|
3635
4146
|
headers: { "Content-Type": "application/json" },
|
|
3636
4147
|
body: JSON.stringify({ label: activeSavedSession.label })
|
|
3637
4148
|
});
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
await refreshSessionList();
|
|
4149
|
+
const body = await res.json();
|
|
4150
|
+
if (!res.ok) {
|
|
4151
|
+
throw new Error("error" in body ? body.error : `Failed to update session (${res.status})`);
|
|
3642
4152
|
}
|
|
3643
|
-
|
|
4153
|
+
const data = body;
|
|
4154
|
+
addMessage(`Session updated: "${data.label}"`, "thinking");
|
|
4155
|
+
await refreshSessionList();
|
|
4156
|
+
} catch (error) {
|
|
4157
|
+
const message = error instanceof Error ? error.message : "Failed to update session";
|
|
4158
|
+
addMessage(message, "error");
|
|
3644
4159
|
} finally {
|
|
3645
4160
|
sessionUpdateBtn.disabled = false;
|
|
3646
4161
|
}
|
|
@@ -3663,10 +4178,19 @@ openRouterConnectCloseBtn.addEventListener("click", () => {
|
|
|
3663
4178
|
openRouterConnectCancelBtn.addEventListener("click", () => {
|
|
3664
4179
|
closeOpenRouterConnectModal();
|
|
3665
4180
|
});
|
|
4181
|
+
openAiCompatibleConnectBackdrop.addEventListener("click", () => {
|
|
4182
|
+
closeOpenAiCompatibleConnectModal();
|
|
4183
|
+
});
|
|
4184
|
+
openAiCompatibleConnectCloseBtn.addEventListener("click", () => {
|
|
4185
|
+
closeOpenAiCompatibleConnectModal();
|
|
4186
|
+
});
|
|
4187
|
+
openAiCompatibleConnectCancelBtn.addEventListener("click", () => {
|
|
4188
|
+
closeOpenAiCompatibleConnectModal();
|
|
4189
|
+
});
|
|
3666
4190
|
settingsResetBtn.addEventListener("click", () => {
|
|
3667
4191
|
clearSettingsBanner();
|
|
3668
4192
|
renderSettings();
|
|
3669
|
-
|
|
4193
|
+
renderSessionProviderControls();
|
|
3670
4194
|
});
|
|
3671
4195
|
settingsSaveBtn.addEventListener("click", async () => {
|
|
3672
4196
|
if (!settingsPayload) {
|
|
@@ -3718,6 +4242,9 @@ settingsList.addEventListener("change", () => {
|
|
|
3718
4242
|
disconnectOpenRouterBtn.addEventListener("click", () => {
|
|
3719
4243
|
void disconnectOpenRouter();
|
|
3720
4244
|
});
|
|
4245
|
+
disconnectOpenAiCompatibleBtn.addEventListener("click", () => {
|
|
4246
|
+
void disconnectOpenAiCompatible();
|
|
4247
|
+
});
|
|
3721
4248
|
document.addEventListener("keydown", (event) => {
|
|
3722
4249
|
if (event.key !== "Escape") {
|
|
3723
4250
|
return;
|
|
@@ -3726,6 +4253,10 @@ document.addEventListener("keydown", (event) => {
|
|
|
3726
4253
|
closeOpenRouterConnectModal();
|
|
3727
4254
|
return;
|
|
3728
4255
|
}
|
|
4256
|
+
if (isOpenAiCompatibleConnectModalOpen()) {
|
|
4257
|
+
closeOpenAiCompatibleConnectModal();
|
|
4258
|
+
return;
|
|
4259
|
+
}
|
|
3729
4260
|
if (isSettingsModalOpen()) {
|
|
3730
4261
|
closeSettings();
|
|
3731
4262
|
}
|
|
@@ -3735,11 +4266,29 @@ for (const button of connectOpenRouterButtons) {
|
|
|
3735
4266
|
openOpenRouterConnectModal();
|
|
3736
4267
|
});
|
|
3737
4268
|
}
|
|
4269
|
+
for (const button of connectOpenAiCompatibleButtons) {
|
|
4270
|
+
button.addEventListener("click", () => {
|
|
4271
|
+
openOpenAiCompatibleConnectModal();
|
|
4272
|
+
});
|
|
4273
|
+
}
|
|
4274
|
+
openAiCompatiblePresetSelect.addEventListener("change", () => {
|
|
4275
|
+
applyOpenAiCompatiblePreset(openAiCompatiblePresetSelect.value);
|
|
4276
|
+
clearOpenAiCompatibleConnectStatus();
|
|
4277
|
+
});
|
|
4278
|
+
openAiCompatibleBaseUrlInput.addEventListener("input", () => {
|
|
4279
|
+
clearOpenAiCompatibleConnectStatus();
|
|
4280
|
+
});
|
|
4281
|
+
openAiCompatibleApiKeyInput.addEventListener("input", () => {
|
|
4282
|
+
clearOpenAiCompatibleConnectStatus();
|
|
4283
|
+
});
|
|
3738
4284
|
openRouterConnectContinueBtn.addEventListener("click", () => {
|
|
3739
4285
|
openRouterConnectContinueBtn.disabled = true;
|
|
3740
4286
|
closeOpenRouterConnectModal();
|
|
3741
4287
|
void startOpenRouterConnect(openRouterPersistCheckbox.checked);
|
|
3742
4288
|
});
|
|
4289
|
+
openAiCompatibleConnectContinueBtn.addEventListener("click", () => {
|
|
4290
|
+
void connectOpenAiCompatible();
|
|
4291
|
+
});
|
|
3743
4292
|
var chatPane = document.getElementById("chat-pane");
|
|
3744
4293
|
var divider = document.getElementById("pane-divider");
|
|
3745
4294
|
divider.addEventListener("mousedown", (e) => {
|