summaryrefslogtreecommitdiff
path: root/htroot
diff options
context:
space:
mode:
authorMichael Peter Christen <mc@yacy.net>2025-12-06 17:33:31 +0100
committerMichael Peter Christen <mc@yacy.net>2025-12-06 17:33:31 +0100
commited9ea238b1e7d4a89d77acea790b08b6f2b1ed49 (patch)
tree29e59c24d5d7f16ca497feeb367632cc2ebb22df /htroot
parent5f4575ad6b0d615b16e8dcbef409b81f8ab7ab43 (diff)
added build page for the AI lab
Diffstat (limited to 'htroot')
-rw-r--r--htroot/AILab.html423
-rw-r--r--htroot/LLMSelection_p.html160
-rw-r--r--htroot/env/grafics/AILab_Crawl.pngbin0 -> 245006 bytes
-rw-r--r--htroot/env/grafics/AILab_Inference.pngbin0 -> 89298 bytes
-rw-r--r--htroot/env/grafics/AILab_Matrix.pngbin0 -> 30390 bytes
-rw-r--r--htroot/env/grafics/AILab_RAG.pngbin0 -> 39340 bytes
-rw-r--r--htroot/env/grafics/AILab_shield.pngbin0 -> 62053 bytes
-rw-r--r--htroot/env/templates/submenuAI.template1
8 files changed, 551 insertions, 33 deletions
diff --git a/htroot/AILab.html b/htroot/AILab.html
new file mode 100644
index 000000000..09f007ba4
--- /dev/null
+++ b/htroot/AILab.html
@@ -0,0 +1,423 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>YaCy '#[clientname]#': AI Lab</title>
+ #%env/templates/metas.template%#
+ <style type="text/css">
+ .ailab-hero {
+ margin: 26px 0 20px 0;
+ padding: 24px 24px 18px 24px;
+ border: 1px solid #dce3f0;
+ border-radius: 6px;
+ background: linear-gradient(135deg, #f7fbff 0%, #e8f1ff 40%, #ffffff 100%);
+ box-shadow: 0 6px 18px rgba(0, 0, 0, 0.06);
+ }
+
+ .ailab-hero .eyebrow {
+ font-size: 0.85em;
+ text-transform: uppercase;
+ letter-spacing: 0.2em;
+ color: #2c3e50;
+ margin: 0 0 6px 0;
+ }
+
+ .ailab-hero h2 {
+ margin: 0 0 8px 0;
+ font-weight: 700;
+ color: #172b4d;
+ }
+
+ .ailab-hero p {
+ margin: 0 0 14px 0;
+ color: #233142;
+ }
+
+ .bs-callout {
+ padding: 20px;
+ margin: 0 0 6px 0;
+ border: 1px solid #e8ecf2;
+ border-left: 6px solid #97a6b9;
+ border-radius: 4px;
+ background: #ffffff;
+ box-shadow: 0 6px 14px rgba(0, 0, 0, 0.05);
+ transition: transform 0.15s ease, box-shadow 0.15s ease;
+ }
+
+ .bs-callout:hover {
+ transform: translateY(-2px);
+ box-shadow: 0 10px 20px rgba(0, 0, 0, 0.08);
+ }
+
+ .bs-callout-mandatory {
+ border-left-color: #f0ad4e;
+ }
+
+ .bs-callout-optional {
+ border-left-color: #5bc0de;
+ }
+
+ .quest-callout h3 {
+ margin-top: 0;
+ margin-bottom: 8px;
+ font-weight: 700;
+ color: #111827;
+ }
+
+ .quest-callout p {
+ margin: 0 0 10px 0;
+ color: #34495e;
+ }
+
+ .quest-body {
+ display: flex;
+ gap: 14px;
+ align-items: center;
+ flex-wrap: wrap;
+ }
+
+ .quest-visual img {
+ max-width: 64px;
+ width: 64px;
+ height: 64px;
+ border-radius: 6px;
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.08);
+ object-fit: contain;
+ }
+
+ .quest-actions .btn {
+ margin-bottom: 6px;
+ text-decoration: none;
+ min-width: 200px;
+ font-size: 14px;
+ }
+
+ .quest-actions .btn-primary,
+ .quest-actions .btn-info,
+ .quest-actions .btn-success,
+ .quest-actions .btn-default {
+ background-color: #5bc0de;
+ border-color: #46b8da;
+ color: #ffffff;
+ }
+
+ .quest-actions .btn-primary:hover,
+ .quest-actions .btn-info:hover,
+ .quest-actions .btn-success:hover,
+ .quest-actions .btn-default:hover {
+ background-color: #31b0d5;
+ border-color: #269abc;
+ color: #ffffff;
+ }
+
+ .quest-note {
+ font-size: 0.95em;
+ color: #4c566a;
+ }
+
+ .quest-note .count {
+ font-weight: 700;
+ }
+
+ .quest-meta {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ margin-bottom: 10px;
+ flex-wrap: wrap;
+ }
+
+ .quest-meta .meta-arrow {
+ display: inline-flex;
+ align-items: center;
+ font-weight: 700;
+ color: #5bc0de;
+ padding: 0 4px;
+ animation: arrowPulse 1.4s ease-in-out infinite;
+ }
+
+ .quest-meta .label {
+ display: inline-block;
+ padding: 4px 12px;
+ font-size: 0.85em;
+ font-weight: 700;
+ line-height: 1.4;
+ border-radius: 999px;
+ }
+
+ .status-pill {
+ display: inline-block;
+ padding: 4px 12px;
+ border-radius: 999px;
+ font-weight: 700;
+ font-size: 0.85em;
+ letter-spacing: 0.05em;
+ text-transform: uppercase;
+ background: #f0ad4e;
+ color: #ffffff;
+ }
+
+ @keyframes arrowPulse {
+ 0% { transform: translateX(0); opacity: 0.8; }
+ 50% { transform: translateX(3px); opacity: 1; }
+ 100% { transform: translateX(0); opacity: 0.8; }
+ }
+
+ .status-ready .status-pill {
+ background: #5cb85c;
+ }
+
+ .status-locked .status-pill {
+ background: #b0b7c3;
+ }
+
+ .status-beta .status-pill {
+ background: #5bc0de;
+ }
+
+ .status-pending .status-pill {
+ background: #f0ad4e;
+ }
+
+ .lab-progress {
+ margin-top: 10px;
+ }
+
+ .lab-progress .progress {
+ margin-bottom: 0;
+ }
+
+ @media (max-width: 767px) {
+ .quest-callout {
+ margin-bottom: 16px;
+ }
+ }
+
+ .quest-grid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 18px;
+ align-items: stretch;
+ }
+
+ .quest-grid .quest-callout {
+ flex: 1 1 48%;
+ min-width: 320px;
+ }
+
+ .quest-locked {
+ opacity: 0.55;
+ filter: grayscale(0.2);
+ pointer-events: none;
+ }
+ </style>
+ </head>
+ <body id="AILab" data-ailab-inference-configured="#[ailab_inference_configured]#">
+ #%env/templates/header.template%#
+ #%env/templates/submenuAI.template%#
+
+ <div class="container-fluid" style="padding-left:0;padding-right:0;">
+ <div class="ailab-hero">
+ <div class="eyebrow">AI Lab Build System</div>
+ <h2>Craft your AI toolkit</h2>
+ <p>Complete the quests below to unlock YaCy's AI sidekick: bind an inference engine, load production models, feed it with your index, then wire RAG and shields.</p>
+ <div class="lab-progress">
+ <div class="progress">
+ <div id="labProgressBar" class="progress-bar progress-bar-success" role="progressbar" aria-valuemin="0" aria-valuemax="100" aria-valuenow="0" style="width:0%">0 / 5 unlocked</div>
+ </div>
+ </div>
+ </div>
+
+ <div class="quest-grid">
+ <div class="bs-callout bs-callout-mandatory quest-callout status-pending" data-quest="inference" data-status="#[ailab_inference_status]#">
+ <div class="quest-meta">
+ <span class="label label-warning">Mandatory</span>
+ <span class="meta-arrow" aria-hidden="true">→</span>
+ <span class="status-pill">Needs setup</span>
+ </div>
+ <h3>Bind an inference engine</h3>
+ <p>Pick your host (Ollama, LM Studio, OpenAI-compatible) and give YaCy a place to send prompts.</p>
+ <div class="quest-body">
+ <div class="quest-visual">
+ <img src="env/grafics/AILab_Inference.png" alt="Inference engine setup" width="128" height="128" />
+ </div>
+ <div class="quest-actions">
+ <a class="btn btn-info btn-sm" href="LLMSelection_p.html">Open engine setup</a><br />
+ <span class="quest-note">Set hoststub, API keys, and defaults to unlock downloads.</span>
+ </div>
+ </div>
+ </div>
+
+ <div class="bs-callout bs-callout-mandatory quest-callout status-pending" data-quest="model" data-status="#[ailab_model_status]#">
+ <div class="quest-meta">
+ <span class="label label-warning">Mandatory</span>
+ <span class="meta-arrow" aria-hidden="true">→</span>
+ <span class="status-pill">Needs setup</span>
+ </div>
+ <h3>Populate the Production Models Matrix</h3>
+ <p>Assign models for chat, search, translation, and more. This is your loadout bench.</p>
+ <div class="quest-body">
+ <div class="quest-visual">
+ <img src="env/grafics/AILab_Matrix.png" alt="Model assignment preview" width="128" height="128" />
+ </div>
+ <div class="quest-actions">
+ <a class="btn btn-info btn-sm" href="LLMSelection_p.html#availableModels">Go to Production Models Matrix</a><br />
+ <span class="quest-note">Deploy at least one model, then assign capabilities (chat, search-query, tooling, vision).</span>
+ </div>
+ </div>
+ </div>
+
+ <div class="bs-callout bs-callout-optional quest-callout status-pending" data-quest="index" data-status="#[ailab_index_status]#">
+ <div class="quest-meta">
+ <span class="label label-info">Optional</span>
+ <span class="meta-arrow" aria-hidden="true">→</span>
+ <span class="status-pill">Needs setup</span>
+ </div>
+ <h3>Grow a search index</h3>
+ <p>Create a local index for grounding: crawl a site or import a pack to give your AI facts to cite.</p>
+ <div class="quest-body">
+ <div class="quest-visual">
+ <img src="env/grafics/AILab_Crawl.png" alt="Index creation" width="128" height="128" />
+ </div>
+ <div class="quest-actions">
+ <a class="btn btn-info btn-sm" href="CrawlStartSite.html">Start a crawl</a>
+ <a class="btn btn-info btn-sm" href="IndexPackDownloader_p.html">Import an index pack</a><br />
+ <span class="quest-note">Indexed documents: <span class="count">#[ailab_index_count]#</span> / <span class="count">#[ailab_index_needed]#</span> required to unlock (need at least 1000 documents).</span>
+ </div>
+ </div>
+ </div>
+
+ <div class="bs-callout bs-callout-optional quest-callout status-pending" data-quest="rag" data-status="#[ailab_rag_status]#">
+ <div class="quest-meta">
+ <span class="label label-info">Optional</span>
+ <span class="meta-arrow" aria-hidden="true">→</span>
+ <span class="status-pill">Needs setup</span>
+ </div>
+ <h3>Wire RAG retrieval</h3>
+ <p>Map which production models answer search-query and Q/A pairs so the RAG proxy can mix search with chat.</p>
+ <div class="quest-body">
+ <div class="quest-visual">
+ <img src="env/grafics/AILab_RAG.png" alt="RAG configuration" width="128" height="128" />
+ </div>
+ <div class="quest-actions">
+ <a class="btn btn-info btn-sm" href="LLMSelection_p.html#productionModelsTable">Configure RAG roles</a>
+ <a class="btn btn-info btn-sm" href="yacychat.html">Test in Chat</a><br />
+ <span class="quest-note">Set the search-query and qapairs columns to connect retrieval to your chat flow.</span>
+ </div>
+ </div>
+ </div>
+
+ <div class="bs-callout bs-callout-optional quest-callout status-pending" data-quest="shield" data-status="#[ailab_shield_status]#">
+ <div class="quest-meta">
+ <span class="label label-info">Optional</span>
+ <span class="meta-arrow" aria-hidden="true">→</span>
+ <span class="status-pill">Needs setup</span>
+ </div>
+ <h3>Define a shield</h3>
+ <p>Add guardrails: moderation prompts, content filters, or safety rules that wrap every request.</p>
+ <div class="quest-body">
+ <div class="quest-visual">
+ <img src="env/grafics/AILab_shield.png" alt="Shield definition" width="128" height="128" />
+ </div>
+ <div class="quest-actions">
+ <a class="btn btn-info btn-sm" href="ConfigProperties_p.html">Open shield settings</a><br />
+ <span class="quest-note">Store your shield directives (system prompts, stop words) as properties, then exercise them in chat.</span>
+ </div>
+ </div>
+ </div>
+ </div>
+ </div>
+
+ <script type="text/javascript">
+ //<![CDATA[
+ (function() {
+ const statusLabels = {
+ ready: "Unlocked",
+ complete: "Unlocked",
+ completed: "Unlocked",
+ beta: "Beta",
+ optional: "Optional",
+ pending: "Needs setup",
+ todo: "Needs setup"
+ };
+
+ const callouts = Array.prototype.slice.call(document.querySelectorAll(".quest-callout"));
+ let readyCount = 0;
+ const statusMap = {};
+ const indexNeeded = parseInt("#[ailab_index_needed]#", 10) || 1000;
+ const indexCount = parseInt("#[ailab_index_count]#", 10) || 0;
+
+ callouts.forEach(callout => {
+ const raw = (callout.getAttribute("data-status") || "").toLowerCase();
+ let status = statusLabels[raw] ? raw : "pending";
+ if (status === "complete" || status === "completed") {
+ status = "ready";
+ }
+ callout.classList.remove("status-ready", "status-pending", "status-beta");
+ callout.classList.add("status-" + status);
+
+ if (status === "ready") {
+ readyCount++;
+ }
+
+ const pill = callout.querySelector(".status-pill");
+ if (pill) {
+ // special display for index quest showing counts while locked/pending
+ const questName = callout.getAttribute("data-quest");
+ if (questName === "index" && status !== "ready") {
+ pill.textContent = indexCount + " / " + indexNeeded;
+ } else {
+ pill.textContent = statusLabels[status] || statusLabels.pending;
+ }
+ }
+
+ const q = callout.getAttribute("data-quest");
+ if (q) {
+ statusMap[q] = status;
+ }
+ });
+
+ // Gating: enforce build order
+ const questOrder = ["inference", "model", "index", "rag", "shield"];
+ let prerequisitesMet = true;
+ questOrder.forEach(function(name) {
+ const callout = document.querySelector('.quest-callout[data-quest="' + name + '"]');
+ if (!callout) return;
+ const s = statusMap[name] || "pending";
+
+ // Only gate by prior quests; index stays clickable even while filling up.
+ let locked = !prerequisitesMet;
+ if (name === "model") {
+ const inferenceConfigured = document.body.getAttribute("data-ailab-inference-configured") === "1";
+ locked = locked || !inferenceConfigured;
+ }
+
+ if (locked) {
+ callout.classList.add("quest-locked", "status-locked");
+ const pill = callout.querySelector(".status-pill");
+ if (pill) {
+ pill.textContent = name === "index" ? (indexCount + " / " + indexNeeded) : "Locked";
+ }
+ } else {
+ callout.classList.remove("quest-locked", "status-locked");
+ }
+
+ // Block subsequent quests until current one is ready (index must reach threshold to release RAG and later steps).
+ if (s !== "ready" || (name === "index" && indexCount < indexNeeded)) {
+ prerequisitesMet = false;
+ }
+ });
+
+ const progressBar = document.getElementById("labProgressBar");
+ if (progressBar && callouts.length > 0) {
+ const percent = Math.round((readyCount / callouts.length) * 100);
+ progressBar.style.width = percent + "%";
+ progressBar.setAttribute("aria-valuenow", percent.toString());
+ progressBar.textContent = readyCount + " / " + callouts.length + " unlocked";
+ }
+ })();
+ //]]>
+ </script>
+
+ #%env/templates/footer.template%#
+ </body>
+</html>
diff --git a/htroot/LLMSelection_p.html b/htroot/LLMSelection_p.html
index ae607fafa..0209aba8d 100644
--- a/htroot/LLMSelection_p.html
+++ b/htroot/LLMSelection_p.html
@@ -5,7 +5,7 @@
<title>YaCy '#[clientname]#': LLM Selection</title>
#%env/templates/metas.template%#
</head>
- <body id="IndexControl">
+ <body id="IndexControl" data-llm-service="#[llm_service]#" data-llm-hoststub="#[llm_hoststub]#" data-llm-apikey="#[llm_apikey]#">
#%env/templates/header.template%#
#%env/templates/submenuAI.template%#
<script>
@@ -17,6 +17,17 @@
event.preventDefault();
event.returnValue = "Model downloads are still running. Please wait until they finish.";
};
+ // Localization-friendly test strings and hints (translate/tune as needed)
+ const TEST_STRINGS = {
+ toolingEndpointPath: "/v1/chat/completions", // Endpoint used to probe tooling capability on OpenAI-compatible APIs
+ toolingSystemMessage: "You are a home assistant.", // System prompt for tooling capability test
+ toolingUserMessage: "Switch on the light", // User prompt for tooling capability test
+ visionSystemMessage: "you read out images", // System prompt for vision capability test
+ visionUserMessage: "what is in the image?", // User prompt for vision capability test
+ visionExpectedText: "42", // Expected mention in LLM response when reading the test image
+ visionTestImagePath: "env/grafics/llmtest.png" // Image used for the vision capability test
+ };
+
const PRODUCTION_MODEL_TOTAL_COLUMNS = 15;
const PRODUCTION_MODEL_MODEL_COLUMN_INDEX = 1;
const PRODUCTION_MODEL_USAGE_COLUMN_START = 5;
@@ -45,14 +56,7 @@
"vision"
];
const PRODUCTION_MODEL_SUBMIT_URL = "LLMSelection_p.html";
- const TOOLING_TEST_ENDPOINT_PATH = "/v1/chat/completions";
const TOOLING_EXPECTED_FUNCTION_NAME = "lightswitch";
- const TOOLING_TEST_SYSTEM_MESSAGE = "You are a home assistant.";
- const TOOLING_TEST_USER_MESSAGE = "Switch on the light";
- const VISION_TEST_SYSTEM_MESSAGE = "you read out images";
- const VISION_TEST_USER_MESSAGE = "what is in the image?";
- const VISION_TEST_EXPECTED_TEXT = "42";
- const VISION_TEST_IMAGE_PATH = "env/grafics/llmtest.png";
let cachedVisionTestImageBase64 = null;
let cachedVisionTestImagePromise = null;
@@ -152,7 +156,7 @@
}
}
- async function loadModelList() {
+ async function loadModelList(fromPreset = false) {
availableModels = [];
const service = document.getElementById("service").value;
const hoststub = document.getElementById("hoststub").value;
@@ -168,8 +172,12 @@
loadModelContainer.innerHTML = "";
loadModelContainer.style.display = "none";
}
+ persistInferenceSystem();
} catch (error) {
handleModelLoadError(service, error);
+ if (fromPreset) {
+ console.warn("Auto-load of model list failed for preset inference system.");
+ }
}
}
@@ -285,6 +293,27 @@
}
}
+ function applyPresetInference() {
+ const serviceSelect = document.getElementById("service");
+ const hoststubInput = document.getElementById("hoststub");
+ const apikeyInput = document.getElementById("apikey");
+ const body = document.body;
+ const presetService = (body.getAttribute("data-llm-service") || "").trim();
+ const presetHoststub = (body.getAttribute("data-llm-hoststub") || "").trim();
+ const presetApikey = (body.getAttribute("data-llm-apikey") || "").trim();
+ if (serviceSelect && presetService) {
+ serviceSelect.value = presetService;
+ }
+ setHoststub();
+ if (hoststubInput && presetHoststub) {
+ hoststubInput.value = presetHoststub;
+ }
+ if (apikeyInput && presetApikey) {
+ apikeyInput.disabled = false;
+ apikeyInput.value = presetApikey;
+ }
+ }
+
async function handleModelDelete(modelName, deleteButton) {
if (!modelName) return;
if (getProductionModelNames().has(modelName)) {
@@ -606,6 +635,23 @@
updateAvailableModelButtons();
}
+ function persistInferenceSystem() {
+ const hoststubInput = document.getElementById("hoststub");
+ const apikeyInput = document.getElementById("apikey");
+ const serviceSelect = document.getElementById("service");
+ const inference_system = {
+ service: serviceSelect ? serviceSelect.value : "",
+ hoststub: hoststubInput ? hoststubInput.value : "",
+ api_key: apikeyInput ? apikeyInput.value : ""
+ };
+ fetch(PRODUCTION_MODEL_SUBMIT_URL, {
+ method: "POST", headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ inference_system })
+ }).catch(err => {
+ console.error("Failed to persist inference system", err);
+ });
+ }
+
function ensureProductionRowUsageCells(row, defaultChecked) {
if (!row) return;
// ensure existence of checkboxes
@@ -669,6 +715,8 @@
updateUndeployButtonState(row);
}
});
+ } else {
+ ensureFeatureAssignedToAnotherModel(columnIndex, currentRow);
}
if (currentRow) {
@@ -691,6 +739,15 @@
undeployBtn.dataset.action = "undeploy";
styleActionButton(undeployBtn);
undeployBtn.addEventListener("click", () => {
+ // reassign any active features before removing this row
+ for (let col = PRODUCTION_MODEL_USAGE_COLUMN_START; col <= PRODUCTION_MODEL_USAGE_COLUMN_END; col += 1) {
+ const cell = row.cells[col];
+ if (!cell) continue;
+ const checkbox = cell.querySelector('input[type="checkbox"]');
+ if (checkbox && checkbox.checked) {
+ ensureFeatureAssignedToAnotherModel(col, row);
+ }
+ }
row.remove();
persistProductionModels();
});
@@ -702,18 +759,32 @@
if (!row) return;
const button = row.querySelector('button[data-action="undeploy"]');
if (!button) return;
- hasFeatureChecked = false;
- for (let col = PRODUCTION_MODEL_USAGE_COLUMN_START; col <= PRODUCTION_MODEL_USAGE_COLUMN_END; col += 1) {
- const cell = row.cells[col];
- if (!cell) continue;
- const checkbox = cell.querySelector('input[type="checkbox"]');
- if (checkbox && checkbox.checked) {
- hasFeatureChecked = true;
- break;
- }
+ button.disabled = false;
+ button.title = "Remove this model (features will be reassigned if possible).";
+ }
+
+ function ensureFeatureAssignedToAnotherModel(columnIndex, sourceRow) {
+ const tbody = getProductionTableBody();
+ if (!tbody) return;
+ const rows = Array.from(tbody.querySelectorAll("tr"));
+ if (rows.length <= 1) return; // nothing to reassign to
+ // If any other row already has the feature, keep it.
+ const othersHave = rows.some(r => {
+ if (r === sourceRow) return false;
+ const cell = r.cells[columnIndex];
+ const cb = cell ? cell.querySelector('input[type="checkbox"]') : null;
+ return cb && cb.checked;
+ });
+ if (othersHave) return;
+ // pick the first other row and assign
+ const target = rows.find(r => r !== sourceRow);
+ if (!target) return;
+ const targetCell = target.cells[columnIndex];
+ const targetCb = targetCell ? targetCell.querySelector('input[type="checkbox"]') : null;
+ if (targetCb) {
+ targetCb.checked = true;
+ updateUndeployButtonState(target);
}
- button.disabled = hasFeatureChecked;
- button.title = hasFeatureChecked ? "Disable the assigned features before undeploying." : "";
}
function persistProductionModels() {
@@ -741,9 +812,18 @@
});
// push to server
+ const hoststubInput = document.getElementById("hoststub");
+ const apikeyInput = document.getElementById("apikey");
+ const serviceSelect = document.getElementById("service");
+ const inference_system = {
+ service: serviceSelect ? serviceSelect.value : "",
+ hoststub: hoststubInput ? hoststubInput.value : "",
+ api_key: apikeyInput ? apikeyInput.value : ""
+ };
+
fetch(PRODUCTION_MODEL_SUBMIT_URL, {
method: "POST", headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ production_models: production_models_table })
+ body: JSON.stringify({ production_models: production_models_table, inference_system })
}).catch(err => {
console.error("Failed to persist production models", err);
});
@@ -785,7 +865,7 @@
if (!endpointBase) {
return false;
}
- const targetUrl = `${endpointBase}${TOOLING_TEST_ENDPOINT_PATH}`;
+ const targetUrl = `${endpointBase}${TEST_STRINGS.toolingEndpointPath}`;
const headers = { "Content-Type": "application/json" };
if (apikey) {
headers.Authorization = `Bearer ${apikey}`;
@@ -809,8 +889,8 @@
temperature: 0.1,
max_tokens: 1024,
messages: [
- { role: "system", content: TOOLING_TEST_SYSTEM_MESSAGE },
- { role: "user", content: TOOLING_TEST_USER_MESSAGE }
+ { role: "system", content: TEST_STRINGS.toolingSystemMessage },
+ { role: "user", content: TEST_STRINGS.toolingUserMessage }
],
tools: [{
type: "function",
@@ -911,7 +991,7 @@
if (!endpointBase) {
return false;
}
- const targetUrl = `${endpointBase}${TOOLING_TEST_ENDPOINT_PATH}`;
+ const targetUrl = `${endpointBase}${TEST_STRINGS.toolingEndpointPath}`;
const headers = { "Content-Type": "application/json" };
if (apikey) {
headers.Authorization = `Bearer ${apikey}`;
@@ -936,11 +1016,11 @@
temperature: 0.1,
max_tokens: 512,
messages: [
- { role: "system", content: VISION_TEST_SYSTEM_MESSAGE },
+ { role: "system", content: TEST_STRINGS.visionSystemMessage },
{
role: "user",
content: [
- { type: "text", text: VISION_TEST_USER_MESSAGE },
+ { type: "text", text: TEST_STRINGS.visionUserMessage },
{
type: "image_url",
image_url: {
@@ -963,7 +1043,7 @@
if (!normalizedText) {
return false;
}
- return normalizedText.indexOf(VISION_TEST_EXPECTED_TEXT) !== -1;
+ return normalizedText.indexOf(TEST_STRINGS.visionExpectedText) !== -1;
});
}
@@ -997,7 +1077,7 @@
if (cachedVisionTestImagePromise) {
return cachedVisionTestImagePromise;
}
- cachedVisionTestImagePromise = fetch(VISION_TEST_IMAGE_PATH)
+ cachedVisionTestImagePromise = fetch(TEST_STRINGS.visionTestImagePath)
.then(response => {
if (!response.ok) {
throw new Error(`Failed to load test image (${response.status})`);
@@ -1078,7 +1158,21 @@
return downloadBtn;
}
- document.addEventListener("DOMContentLoaded", normalizeProductionModelRows);
+ document.addEventListener("DOMContentLoaded", () => {
+ try {
+ normalizeProductionModelRows();
+ applyPresetInference();
+ // auto-show available models if a preset inference exists
+ const body = document.body;
+ const presetService = (body.getAttribute("data-llm-service") || "").trim();
+ const presetHoststub = (body.getAttribute("data-llm-hoststub") || "").trim();
+ if (presetService && presetHoststub) {
+ loadModelList(true);
+ }
+ } catch (e) {
+ console.error("Initialization failed", e);
+ }
+ });
</script>
@@ -1087,7 +1181,7 @@
<p>
Here you can pick models from a LLM model service to select them as production model.
- In the "Production Models" - Matrix you can then assign each selected model a function inside YaCy
+ In the "Production Models Matrix" you can then assign each selected model a function inside YaCy
</p>
<p>
<b>Install your local LLM service!</b> You need either a local <a href="https://ollama.com/">ollama</a> or <a href="https://lmstudio.ai/">LM Studio</a> instance running on your local host or inside the intranet.
@@ -1138,9 +1232,9 @@
<legend>Model Downloads</legend>
<div id="downloadActivityList"></div>
</fieldset>
- <fieldset id="availableModelsContainer" style="display:none"></fieldset>
+ <fieldset id="availableModelsContainer" style="display:none"><a name="availableModels"></a></fieldset>
- <fieldset id="productionModelsContainer" style="display: block;"><legend>Production Models</legend>
+ <fieldset id="productionModelsContainer" style="display: block;"><a name="productionModels"></a><legend>Production Models Matrix</legend>
<table class="table table-striped" id="productionModelsTable">
<thead class="thead-dark">
<tr>
diff --git a/htroot/env/grafics/AILab_Crawl.png b/htroot/env/grafics/AILab_Crawl.png
new file mode 100644
index 000000000..330918bd5
--- /dev/null
+++ b/htroot/env/grafics/AILab_Crawl.png
Binary files differ
diff --git a/htroot/env/grafics/AILab_Inference.png b/htroot/env/grafics/AILab_Inference.png
new file mode 100644
index 000000000..0f4004b54
--- /dev/null
+++ b/htroot/env/grafics/AILab_Inference.png
Binary files differ
diff --git a/htroot/env/grafics/AILab_Matrix.png b/htroot/env/grafics/AILab_Matrix.png
new file mode 100644
index 000000000..c1cf0efba
--- /dev/null
+++ b/htroot/env/grafics/AILab_Matrix.png
Binary files differ
diff --git a/htroot/env/grafics/AILab_RAG.png b/htroot/env/grafics/AILab_RAG.png
new file mode 100644
index 000000000..6e14804ef
--- /dev/null
+++ b/htroot/env/grafics/AILab_RAG.png
Binary files differ
diff --git a/htroot/env/grafics/AILab_shield.png b/htroot/env/grafics/AILab_shield.png
new file mode 100644
index 000000000..72b4cf0c9
--- /dev/null
+++ b/htroot/env/grafics/AILab_shield.png
Binary files differ
diff --git a/htroot/env/templates/submenuAI.template b/htroot/env/templates/submenuAI.template
index c9c933661..a5badb975 100644
--- a/htroot/env/templates/submenuAI.template
+++ b/htroot/env/templates/submenuAI.template
@@ -1,6 +1,7 @@
<div class="SubMenu">
<h3>AI Lab</h3>
<ul class="SubMenu">
+ <li><a href="AILab.html" class="MenuItemLink">AI Lab</a></li>
<li><a href="LLMSelection_p.html" class="MenuItemLink #(authorized)#lock::unlock#(/authorized)#">LLM Selection</a></li>
<li><a href="yacychat.html" class="MenuItemLink">Chat</a></li>
</ul>