mirror of
https://github.com/yacy/yacy_search_server.git
synced 2025-12-13 04:14:35 -05:00
424 lines
15 KiB
HTML
424 lines
15 KiB
HTML
<!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-primary btn-sm" href="RAGConfig_p.html">Wire RAG prompts</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: access rates, grant or deny non-localhost access. Activate the front page link for chat to complete this quest.</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="AIShield_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>
|