From 40e56acc30a0d4360d2835b0fbe85acaa0d15f72 Mon Sep 17 00:00:00 2001 From: Meleki Date: Sun, 1 Mar 2026 20:38:18 +0100 Subject: [PATCH] PROMPT.md-based skill injection, scroll-on-image-load fix, skill availability UI - SkillService: read PROMPT.md instead of SKILL.md for system prompt injection - SkillInfo: add HasPromptMd field, skills without PROMPT.md are not injectable - Agent form: disable checkbox + warning for skills missing PROMPT.md - Thumbnail size: 320px (was 300px) - Chat: _scrollOnImages() re-scrolls after lazy-loaded images finish loading - CSS: skill-unavailable/warning styling, adjusted thumb dimensions --- Endpoints/SkillEndpoint.cs | 4 ++-- Services/SkillService.cs | 15 ++++++++++----- wwwroot/css/chat.css | 8 ++------ wwwroot/css/form.css | 15 +++++++++++++++ wwwroot/js/pages/agent-page.js | 7 +++++-- wwwroot/js/ui.js | 12 ++++++++++++ 6 files changed, 46 insertions(+), 15 deletions(-) diff --git a/Endpoints/SkillEndpoint.cs b/Endpoints/SkillEndpoint.cs index 00c76fb..242d3a9 100644 --- a/Endpoints/SkillEndpoint.cs +++ b/Endpoints/SkillEndpoint.cs @@ -193,8 +193,8 @@ public static class SkillEndpoint { using var image = await Image.LoadAsync(originalPath); - // Max 300px auf der längsten Seite - var maxSize = 300; + // Max 320px auf der längsten Seite + var maxSize = 320; image.Mutate(x => x.Resize(new ResizeOptions { Size = new Size(maxSize, maxSize), diff --git a/Services/SkillService.cs b/Services/SkillService.cs index eb466c1..c6b73b4 100644 --- a/Services/SkillService.cs +++ b/Services/SkillService.cs @@ -46,13 +46,14 @@ public class SkillService } /// - /// Liest die SKILL.md eines Skills. Gibt null zurück wenn nicht vorhanden. + /// Liest die PROMPT.md eines Skills. Gibt null zurück wenn nicht vorhanden. + /// Nur Skills mit PROMPT.md können im System-Prompt verwendet werden. /// public string GetSkillPrompt(string slug) { - var skillMd = Path.Combine(_skillsDir, slug, "SKILL.md"); - if (!File.Exists(skillMd)) return null; - return File.ReadAllText(skillMd); + var promptMd = Path.Combine(_skillsDir, slug, "PROMPT.md"); + if (!File.Exists(promptMd)) return null; + return File.ReadAllText(promptMd); } private SkillInfo LoadSkill(string dir) @@ -61,11 +62,14 @@ public class SkillService var metaFile = Path.Combine(dir, "_meta.json"); var skillMd = Path.Combine(dir, "SKILL.md"); + var promptMd = Path.Combine(dir, "PROMPT.md"); + var info = new SkillInfo { Slug = slug, Path = Path.GetFullPath(dir), - HasSkillMd = File.Exists(skillMd) + HasSkillMd = File.Exists(skillMd), + HasPromptMd = File.Exists(promptMd) }; // Name/Description aus SKILL.md Frontmatter lesen @@ -135,4 +139,5 @@ public class SkillInfo public string Version { get; set; } public string Path { get; set; } public bool HasSkillMd { get; set; } + public bool HasPromptMd { get; set; } } diff --git a/wwwroot/css/chat.css b/wwwroot/css/chat.css index 3d93227..f2b76a6 100644 --- a/wwwroot/css/chat.css +++ b/wwwroot/css/chat.css @@ -627,17 +627,13 @@ .krea-thumb { display: block; - max-width: 300px; - max-height: 200px; + max-width: 320px !important; + max-height: 240px; object-fit: contain; } /* Krea Image Modal */ -.krea-overlay { - backdrop-filter: blur(4px); -} - .krea-modal { max-width: 90vw; max-height: 90vh; diff --git a/wwwroot/css/form.css b/wwwroot/css/form.css index b736b1b..e757785 100644 --- a/wwwroot/css/form.css +++ b/wwwroot/css/form.css @@ -180,6 +180,21 @@ border-radius: var(--border-radius); } +.skill-unavailable { + opacity: 0.45; + cursor: not-allowed; +} + +.skill-unavailable:hover { + border-color: var(--f2-border); +} + +.skill-warning { + color: var(--dangerous); + font-size: var(--font-size-sm, 0.8em); + margin-left: auto; +} + .danger-btn { color: var(--dangerous); border-color: var(--dangerous); diff --git a/wwwroot/js/pages/agent-page.js b/wwwroot/js/pages/agent-page.js index ba9b26a..2a6a74e 100644 --- a/wwwroot/js/pages/agent-page.js +++ b/wwwroot/js/pages/agent-page.js @@ -185,14 +185,17 @@ function renderSkillCheckboxes(container, allSkills, assignedSlugs) { } for (const skill of allSkills) { const checked = assignedSlugs.includes(skill.slug) ? 'checked' : ''; + const disabled = !skill.hasPromptMd ? 'disabled' : ''; const version = skill.version ? ` v${skill.version}` : ''; const desc = skill.description ? `${skill.description}` : ''; + const warning = !skill.hasPromptMd ? '⚠ PROMPT.md fehlt – Skill nicht verwendbar' : ''; const row = document.createElement('label'); - row.className = 'skill-item'; + row.className = 'skill-item' + (!skill.hasPromptMd ? ' skill-unavailable' : ''); row.innerHTML = ` - + ${skill.name}${version} ${desc} + ${warning} `; container.appendChild(row); } diff --git a/wwwroot/js/ui.js b/wwwroot/js/ui.js index 5399b07..186669d 100644 --- a/wwwroot/js/ui.js +++ b/wwwroot/js/ui.js @@ -79,6 +79,16 @@ function scrollDown() { }); } +function _scrollOnImages(container) { + var imgs = container.querySelectorAll('img'); + for (var i = 0; i < imgs.length; i++) { + if (!imgs[i].complete) { + imgs[i].addEventListener('load', scrollDown); + imgs[i].addEventListener('error', scrollDown); + } + } +} + function esc(s) { const d = document.createElement('div'); d.textContent = s; @@ -234,6 +244,7 @@ function addAssistantBubble(text, timestamp = '', messageId = null, isMarked = f if (messageId) div.dataset.messageId = messageId; div.innerHTML = renderMarkdown(text); wrapKreaGalleries(div); + _scrollOnImages(div); _addReplyButton(div); _addMarkButton(div, messageId, isMarked); chatEl().appendChild(div); @@ -299,6 +310,7 @@ function endStreamBubble() { else { _streamDiv.innerHTML = renderMarkdown(_streamText); wrapKreaGalleries(_streamDiv); + _scrollOnImages(_streamDiv); } } _streamDiv = null;