Files
gott/Services/SkillService.cs
T
Meleki 40e56acc30 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
2026-03-01 20:38:18 +01:00

144 lines
4.5 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text.Json;
namespace Gott.Services;
public class SkillService
{
private readonly string _skillsDir;
public string SkillsDir => _skillsDir;
public SkillService(string dataDir)
{
_skillsDir = Path.Combine(dataDir, "skills");
Directory.CreateDirectory(_skillsDir);
}
/// <summary>
/// Scannt den Skills-Ordner und gibt alle verfügbaren Skills zurück.
/// </summary>
public List<SkillInfo> GetAllSkills()
{
var skills = new List<SkillInfo>();
if (!Directory.Exists(_skillsDir)) return skills;
foreach (var dir in Directory.GetDirectories(_skillsDir))
{
var skill = LoadSkill(dir);
if (skill != null) skills.Add(skill);
}
return skills.OrderBy(s => s.Name).ToList();
}
/// <summary>
/// Lädt einen einzelnen Skill anhand seines Slugs.
/// </summary>
public SkillInfo GetSkill(string slug)
{
var dir = Path.Combine(_skillsDir, slug);
if (!Directory.Exists(dir)) return null;
return LoadSkill(dir);
}
/// <summary>
/// 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.
/// </summary>
public string GetSkillPrompt(string slug)
{
var promptMd = Path.Combine(_skillsDir, slug, "PROMPT.md");
if (!File.Exists(promptMd)) return null;
return File.ReadAllText(promptMd);
}
private SkillInfo LoadSkill(string dir)
{
var slug = Path.GetFileName(dir);
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),
HasPromptMd = File.Exists(promptMd)
};
// Name/Description aus SKILL.md Frontmatter lesen
if (File.Exists(skillMd))
{
var content = File.ReadAllText(skillMd);
var frontmatter = ParseFrontmatter(content);
if (frontmatter.TryGetValue("name", out var name)) info.Name = name;
if (frontmatter.TryGetValue("description", out var desc)) info.Description = desc;
if (frontmatter.TryGetValue("version", out var ver)) info.Version = ver;
}
// _meta.json als Fallback/Ergänzung
if (File.Exists(metaFile))
{
try
{
var json = File.ReadAllText(metaFile);
using var doc = JsonDocument.Parse(json);
var root = doc.RootElement;
if (string.IsNullOrEmpty(info.Name) && root.TryGetProperty("name", out var n))
info.Name = n.GetString();
if (string.IsNullOrEmpty(info.Version) && root.TryGetProperty("version", out var v))
info.Version = v.GetString();
if (root.TryGetProperty("slug", out var s) && !string.IsNullOrEmpty(s.GetString()))
info.Slug = s.GetString();
}
catch { /* Fehler in _meta.json ignorieren */ }
}
// Fallback: Name = Slug
if (string.IsNullOrEmpty(info.Name)) info.Name = slug;
return info;
}
/// <summary>
/// Parst einen einfachen YAML-Frontmatter-Block (--- ... ---) aus einer Markdown-Datei.
/// </summary>
private Dictionary<string, string> ParseFrontmatter(string content)
{
var result = new Dictionary<string, string>();
if (!content.StartsWith("---")) return result;
var end = content.IndexOf("---", 3, StringComparison.Ordinal);
if (end < 0) return result;
var frontmatter = content.Substring(3, end - 3).Trim();
foreach (var line in frontmatter.Split('\n'))
{
var colon = line.IndexOf(':');
if (colon < 0) continue;
var key = line.Substring(0, colon).Trim();
var value = line.Substring(colon + 1).Trim();
result[key] = value;
}
return result;
}
}
public class SkillInfo
{
public string Slug { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string Version { get; set; }
public string Path { get; set; }
public bool HasSkillMd { get; set; }
public bool HasPromptMd { get; set; }
}