refactor: split serve.py and index.html into single-responsibility modules
Backend:
- path_validator.py: PathValidator-Klasse für Pfad-Validierung
- file_ops.py: read_file, write_file, directory_exists, file_exists
- content_types.py: get_content_type mit EXTENSION_MAP
- handler.py: Handler-Klasse mit do_GET/do_PUT, nutzt above modules
- serve.py: Entry-Point (main, find_free_port), setzt Handler.validator/directory
Frontend:
- css/variables.css: CSS-Variablen (--bg-*, --text-*, --accent, etc.)
- css/styles.css: Alle CSS-Regeln (modal, card, template-grid, etc.)
- js/utils.js: esc, showToast, copyContentToClipboard
- js/modal.js: showModal, closeModal, closeEditModal, wasViewModalOpen
- js/editor.js: editModalContent, createJsonEditUI, extractJsonFromForm
- js/api.js: viewTemplate, copyContent, loadTemplates, saveEditedContent
- js/templates.js: renderTemplates, applyFilters, parseTypeFromHash
- js/main.js: Event-Listener, Hash-Filter, Initialisierung
- index.html: Inline-CSS/JS entfernt, <link>/<script src>-Tags hinzugefügt
Smoke test: SO_REUSEADDR für schnelle Port-Wiederverwendung
2026-05-03 14:40:44 +02:00
/ * *
* JSON - Editor für die Template - Bearbeitung .
*
* Generiert ein Formular aus JSON - Daten und extrahiert
* die Werte zurück in ein JSON - Objekt .
* /
let editContainerRef = null ;
let currentIndent = 2 ;
/ * *
* Öffne Edit - Modal für eine Template - Datei
* @ param { string } path - Pfad zur Template - Datei
* /
function editModalContent ( path ) {
window . currentEditTemplate = path ;
const title = path . split ( '/' ) . pop ( ) ;
document . getElementById ( 'edit-title' ) . textContent = ` Template bearbeiten: ${ title } ` ;
window . _wasViewModalOpen = document . getElementById ( 'modal' ) . classList . contains ( 'active' ) ;
// Inhalt laden und editierbare Formulare abhängig vom Dateityp erstellen
fetch ( path )
. then ( r => r . text ( ) )
. then ( content => {
const editContainer = document . getElementById ( 'edit-content-content' ) ;
editContainer . innerHTML = '' ;
if ( path . endsWith ( '.json' ) ) {
try {
const jsonData = JSON . parse ( content ) ;
// Original-Indent erkennen
const indentMatch = content . match ( /^\s{2,8}/m ) ;
currentIndent = indentMatch ? indentMatch [ 0 ] . length : 2 ;
createJsonEditUI ( editContainer , jsonData , path ) ;
} catch ( e ) {
// Falls JSON ungültig, als Text bearbeiten
createTextEditUI ( editContainer , content ) ;
}
} else {
// Markdown als einfaches Textfeld
createTextEditUI ( editContainer , content ) ;
}
document . getElementById ( 'edit-modal' ) . classList . add ( 'active' ) ;
} )
. catch ( e => showToast ( ` ✗ Fehler beim Laden: ${ e . message } ` ) ) ;
}
/ * *
* Erstelle ein Textarea für die Bearbeitung
* @ param { HTMLElement } container - Ziel - Container
* @ param { string } content - Dateiinhalt
* /
function createTextEditUI ( container , content ) {
const textarea = document . createElement ( 'textarea' ) ;
textarea . id = 'edit-textarea' ;
textarea . value = content ;
textarea . style . cssText = 'width: 100%; min-height: 300px; background: var(--bg-input); color: var(--text-primary); border: 1px solid var(--border); border-radius: 4px; padding: 10px; font-family: var(--mono); font-size: 13px;' ;
textarea . spellcheck = false ;
container . appendChild ( textarea ) ;
}
/ * *
* Erstelle ein Formular aus JSON - Daten
* @ param { HTMLElement } container - Ziel - Container
* @ param { Object } jsonData - Zu bearbeitende JSON - Daten
* @ param { string } editPathHint - Pfad - Hinweis
* /
function createJsonEditUI ( container , jsonData , editPathHint = '' ) {
editContainerRef = container ;
container . innerHTML = '' ;
const formDiv = document . createElement ( 'div' ) ;
formDiv . style . cssText = 'display: flex; flex-direction: column; gap: 16px; padding: 8px; background: var(--bg-card); border-radius: 4px; margin: 0; min-height: 300px; overflow-y: auto;' ;
// Rekursive Funktion zum Erstellen von Eingabefeldern für alle Properties
function buildJsonForm ( data , prefix = '' , level = 0 , targetElement = null ) {
if ( typeof data !== 'object' || data === null ) return ;
const container = targetElement || document . getElementById ( 'edit-content-content' ) ;
if ( ! container ) return ;
Object . keys ( data ) . forEach ( key => {
const fullKey = prefix ? ` ${ prefix } . ${ key } ` : key ;
const value = data [ key ] ;
const isObject = typeof value === 'object' && value !== null && ! Array . isArray ( value ) ;
const isArray = Array . isArray ( value ) ;
// Für Objekte: Rekursiv alle inneren Properties anzeigen
if ( isObject ) {
const fieldContainer = document . createElement ( 'div' ) ;
fieldContainer . style . cssText = 'background: #1a1a1a; padding: 12px; border-radius: 4px; border-left: 3px solid #4CAF50;' ;
const label = document . createElement ( 'label' ) ;
label . htmlFor = ` edit- ${ fullKey . replace ( /[.\s]/g , '-' ) } ` ;
label . textContent = fullKey + ' (Objekt)' ;
label . style . cssText = 'font-weight: 600; color: #4CAF50; margin-bottom: 8px; display: block; font-size: 14px;' ;
fieldContainer . appendChild ( label ) ;
// Rekursiv innere Properties in den Container einfügen
const innerContainer = document . createElement ( 'div' ) ;
innerContainer . style . cssText = 'padding-left: 12px; margin-top: 4px;' ;
buildJsonForm ( value , fullKey , level + 1 , innerContainer ) ;
fieldContainer . appendChild ( innerContainer ) ;
container . appendChild ( fieldContainer ) ;
return ;
}
// Für Arrays: Rekursiv jedes Element als eigenes Feld anzeigen
if ( isArray ) {
const arrayContainer = document . createElement ( 'div' ) ;
arrayContainer . style . cssText = 'background: #1a1a1a; padding: 12px; border-radius: 4px; border-left: 3px solid #2196F3;' ;
const arrayLabel = document . createElement ( 'label' ) ;
arrayLabel . htmlFor = ` edit- ${ fullKey . replace ( /[.\s]/g , '-' ) } ` ;
arrayLabel . textContent = fullKey + ' (Array) - ' + value . length + ' Elemente' ;
arrayLabel . style . cssText = 'font-weight: 600; color: #2196F3; margin-bottom: 8px; display: block; font-size: 14px;' ;
arrayContainer . appendChild ( arrayLabel ) ;
// Für jedes Array-Element ein separates Eingabefeld
const arrayItemsContainer = document . createElement ( 'div' ) ;
arrayItemsContainer . style . cssText = 'margin-top: 4px; padding-left: 12px;' ;
value . forEach ( ( item , index ) => {
const itemKey = ` ${ fullKey } [ ${ index } ] ` ;
const itemContainer = document . createElement ( 'div' ) ;
itemContainer . style . cssText = 'background: #2a2a2a; padding: 8px; margin: 4px 0; border-radius: 3px; border-left: 2px solid #FF9800;' ;
const itemLabel = document . createElement ( 'label' ) ;
itemLabel . htmlFor = ` edit- ${ itemKey . replace ( /[.\s]/g , '-' ) } ` ;
itemLabel . textContent = ` Element [ ${ index } ] ` ;
itemLabel . style . cssText = 'font-weight: 500; color: #FF9800; margin-bottom: 4px; display: block; font-size: 13px;' ;
itemContainer . appendChild ( itemLabel ) ;
// Prüfen, ob das Array-Element selbst ein Objekt ist
if ( typeof item === 'object' && item !== null ) {
const innerObjContainer = document . createElement ( 'div' ) ;
innerObjContainer . style . cssText = 'padding-left: 12px; margin-top: 4px;' ;
buildJsonForm ( item , itemKey , level + 1 , innerObjContainer ) ;
itemContainer . appendChild ( innerObjContainer ) ;
} else {
const itemFieldContainer = document . createElement ( 'div' ) ;
itemFieldContainer . style . cssText = 'background: var(--bg-input); padding: 8px; border-radius: 3px; margin-bottom: 4px;' ;
const input = document . createElement ( 'input' ) ;
const type = typeof item === 'boolean' ? 'checkbox' : typeof item === 'number' ? 'number' : 'text' ;
input . type = type ;
input . value = item !== null && item !== undefined ? item : '' ;
input . dataset . key = itemKey ;
input . dataset . type = typeof item ;
input . dataset . arrayIndex = index ;
if ( type === 'checkbox' ) input . checked = item ;
else input . value = String ( item !== null && item !== undefined ? item : '' ) ;
input . style . cssText = 'width: 100%; padding: 8px; background: #222222; color: #ffffff; border: 1px solid #555; border-radius: 3px; font-family: var(--mono); font-size: 13px;' ;
itemFieldContainer . appendChild ( input ) ;
itemContainer . appendChild ( itemFieldContainer ) ;
}
arrayItemsContainer . appendChild ( itemContainer ) ;
} ) ;
arrayContainer . appendChild ( arrayItemsContainer ) ;
container . appendChild ( arrayContainer ) ;
return ;
}
// Für primitive Werte: Standard-Eingabefeld erstellen
const fieldContainer = document . createElement ( 'div' ) ;
fieldContainer . style . cssText = 'background: var(--bg-input); padding: 8px; border-radius: 4px; border: 1px solid transparent;' ;
const label = document . createElement ( 'label' ) ;
label . htmlFor = ` edit- ${ fullKey . replace ( /[.\s]/g , '-' ) } ` ;
label . textContent = fullKey ;
label . style . cssText = 'font-weight: 600; color: var(--text-primary); display: block; margin-bottom: 4px; font-size: 14px; margin-top: 0;' ;
fieldContainer . appendChild ( label ) ;
2026-05-03 15:06:03 +02:00
// Das 'template'-Feld als Textarea rendern
const isTemplateField = ( fullKey === 'template' ) ;
refactor: split serve.py and index.html into single-responsibility modules
Backend:
- path_validator.py: PathValidator-Klasse für Pfad-Validierung
- file_ops.py: read_file, write_file, directory_exists, file_exists
- content_types.py: get_content_type mit EXTENSION_MAP
- handler.py: Handler-Klasse mit do_GET/do_PUT, nutzt above modules
- serve.py: Entry-Point (main, find_free_port), setzt Handler.validator/directory
Frontend:
- css/variables.css: CSS-Variablen (--bg-*, --text-*, --accent, etc.)
- css/styles.css: Alle CSS-Regeln (modal, card, template-grid, etc.)
- js/utils.js: esc, showToast, copyContentToClipboard
- js/modal.js: showModal, closeModal, closeEditModal, wasViewModalOpen
- js/editor.js: editModalContent, createJsonEditUI, extractJsonFromForm
- js/api.js: viewTemplate, copyContent, loadTemplates, saveEditedContent
- js/templates.js: renderTemplates, applyFilters, parseTypeFromHash
- js/main.js: Event-Listener, Hash-Filter, Initialisierung
- index.html: Inline-CSS/JS entfernt, <link>/<script src>-Tags hinzugefügt
Smoke test: SO_REUSEADDR für schnelle Port-Wiederverwendung
2026-05-03 14:40:44 +02:00
const type = typeof value === 'boolean' ? 'checkbox' : typeof value === 'number' ? 'number' : 'text' ;
2026-05-03 15:06:03 +02:00
if ( isTemplateField ) {
const textarea = document . createElement ( 'textarea' ) ;
textarea . id = ` edit- ${ fullKey . replace ( /[.\s]/g , '-' ) } ` ;
textarea . value = value !== null && value !== undefined ? value : '' ;
textarea . dataset . key = fullKey ;
textarea . dataset . type = typeof value ;
textarea . style . cssText = 'width: 100%; min-height: 200px; padding: 8px; background: #222222; color: #ffffff; border: 1px solid #555; border-radius: 3px; font-family: var(--mono); font-size: 13px; resize: vertical;' ;
textarea . spellcheck = false ;
fieldContainer . appendChild ( textarea ) ;
} else {
const input = document . createElement ( 'input' ) ;
input . type = type ;
input . value = value !== null && value !== undefined ? value : '' ;
input . dataset . key = fullKey ;
input . dataset . type = typeof value ;
if ( type === 'checkbox' ) input . checked = value ;
else input . value = String ( value !== null && value !== undefined ? value : '' ) ;
refactor: split serve.py and index.html into single-responsibility modules
Backend:
- path_validator.py: PathValidator-Klasse für Pfad-Validierung
- file_ops.py: read_file, write_file, directory_exists, file_exists
- content_types.py: get_content_type mit EXTENSION_MAP
- handler.py: Handler-Klasse mit do_GET/do_PUT, nutzt above modules
- serve.py: Entry-Point (main, find_free_port), setzt Handler.validator/directory
Frontend:
- css/variables.css: CSS-Variablen (--bg-*, --text-*, --accent, etc.)
- css/styles.css: Alle CSS-Regeln (modal, card, template-grid, etc.)
- js/utils.js: esc, showToast, copyContentToClipboard
- js/modal.js: showModal, closeModal, closeEditModal, wasViewModalOpen
- js/editor.js: editModalContent, createJsonEditUI, extractJsonFromForm
- js/api.js: viewTemplate, copyContent, loadTemplates, saveEditedContent
- js/templates.js: renderTemplates, applyFilters, parseTypeFromHash
- js/main.js: Event-Listener, Hash-Filter, Initialisierung
- index.html: Inline-CSS/JS entfernt, <link>/<script src>-Tags hinzugefügt
Smoke test: SO_REUSEADDR für schnelle Port-Wiederverwendung
2026-05-03 14:40:44 +02:00
2026-05-03 15:06:03 +02:00
input . style . cssText = 'width: 100%; padding: 8px; background: #222222; color: #ffffff; border: 1px solid #555; border-radius: 3px; font-family: var(--mono); font-size: 13px;' ;
fieldContainer . appendChild ( input ) ;
}
refactor: split serve.py and index.html into single-responsibility modules
Backend:
- path_validator.py: PathValidator-Klasse für Pfad-Validierung
- file_ops.py: read_file, write_file, directory_exists, file_exists
- content_types.py: get_content_type mit EXTENSION_MAP
- handler.py: Handler-Klasse mit do_GET/do_PUT, nutzt above modules
- serve.py: Entry-Point (main, find_free_port), setzt Handler.validator/directory
Frontend:
- css/variables.css: CSS-Variablen (--bg-*, --text-*, --accent, etc.)
- css/styles.css: Alle CSS-Regeln (modal, card, template-grid, etc.)
- js/utils.js: esc, showToast, copyContentToClipboard
- js/modal.js: showModal, closeModal, closeEditModal, wasViewModalOpen
- js/editor.js: editModalContent, createJsonEditUI, extractJsonFromForm
- js/api.js: viewTemplate, copyContent, loadTemplates, saveEditedContent
- js/templates.js: renderTemplates, applyFilters, parseTypeFromHash
- js/main.js: Event-Listener, Hash-Filter, Initialisierung
- index.html: Inline-CSS/JS entfernt, <link>/<script src>-Tags hinzugefügt
Smoke test: SO_REUSEADDR für schnelle Port-Wiederverwendung
2026-05-03 14:40:44 +02:00
container . appendChild ( fieldContainer ) ;
} ) ;
}
buildJsonForm ( jsonData , '' , 0 , formDiv ) ;
container . appendChild ( formDiv ) ;
}
/ * *
* Extrahiere den Wert aus einem Input - Element
* @ param { HTMLElement } input - Input - Element
* @ returns { * } Extrahierter Wert
* /
function extractInputValue ( input ) {
if ( input . type === 'checkbox' ) {
return input . checked ;
}
const value = input . value ;
try {
// Nur wenn der Input-Typ "text" ist und Wert JSON-ähnlich formatiert
if ( input . type === 'text' && value . length > 0 ) {
if ( ( value . startsWith ( '{' ) && value . endsWith ( '}' ) ) ||
( value . startsWith ( '[' ) && value . endsWith ( ']' ) ) ) {
try {
return JSON . parse ( value ) ;
} catch ( parseErr ) {
// Kein gültiges JSON, behalte String-Wert
}
}
}
if ( input . dataset . type === 'number' ) {
return Number ( value ) ;
}
return value ;
} catch ( e ) {
return value ;
}
}
/ * *
* Extrahiere JSON aus dem Formular - Container
* @ param { HTMLElement } formDiv - Formular - Container
* @ returns { Object } Extrahiertes JSON - Objekt
* /
function extractJsonFromForm ( formDiv ) {
// Map: full key -> { value, type }
const inputEntries = [ ] ;
const inputs = formDiv . querySelectorAll ( '[data-key]' ) ;
inputs . forEach ( input => {
const key = input . dataset . key || '' ;
inputEntries . push ( { key , value : extractInputValue ( input ) } ) ;
} ) ;
// Sort by key to ensure stable parent-first construction
inputEntries . sort ( ( a , b ) => a . key . localeCompare ( b . key ) ) ;
const result = { } ;
inputEntries . forEach ( ( { key , value } ) => {
const parts = key . split ( '.' ) ;
let cur = result ;
for ( let i = 0 ; i < parts . length ; i ++ ) {
const part = parts [ i ] ;
const isLast = i === parts . length - 1 ;
const m = part . match ( /^([^\[\]]+)\[(\d+)\]$/ ) ;
if ( m && ! isLast ) {
const base = m [ 1 ] ;
const idx = Number ( m [ 2 ] ) ;
if ( ! cur [ base ] ) cur [ base ] = [ ] ;
if ( ! cur [ base ] [ idx ] ) cur [ base ] [ idx ] = { } ;
cur = cur [ base ] [ idx ] ;
} else if ( m && isLast ) {
// Array leaf
const base = m [ 1 ] ;
const idx = Number ( m [ 2 ] ) ;
if ( ! cur [ base ] ) cur [ base ] = [ ] ;
cur [ base ] [ idx ] = value ;
} else if ( isLast ) {
cur [ part ] = value ;
} else {
if ( ! cur [ part ] ) cur [ part ] = { } ;
cur = cur [ part ] ;
}
}
} ) ;
return result ;
}
2026-05-03 15:34:29 +02:00
/ * *
* LLM - API - Konfiguration ( llama . cpp / Kilo - Instanz )
* /
const LLM _BASE = 'http://localhost:8001/v1' ;
const LLM _MODEL = 'Qwen3.6-35B-A3B-UD-Q3_K_S.gguf' ;
/ * *
* Zustand für den Tunen - Dialog
* /
window . _tuneState = { path : '' , rawPath : '' , template : '' } ;
/ * *
* Öffne Tune - Modal für eine Template - Datei
* @ param { string } path - Pfad zur Template - Datei
* /
function tuneModalContent ( path ) {
window . _tuneState . path = path ;
window . _tuneState . rawPath = '' ;
window . _tuneState . template = '' ;
window . _tuneState . generated = '' ;
const title = path . split ( '/' ) . pop ( ) ;
document . getElementById ( 'tune-title' ) . textContent = ` Prompt tunen: ${ title } ` ;
// Generate-Button aktivieren, Apply-Button verstecken
document . getElementById ( 'tune-generate-btn' ) . style . display = '' ;
document . getElementById ( 'tune-apply-btn' ) . style . display = 'none' ;
document . getElementById ( 'tune-generate-btn' ) . disabled = false ;
document . getElementById ( 'tune-generate-btn' ) . innerHTML = `
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke - width = "2" >
< path d = "M12 2v4m0 12v4m8-8h-4M6 12H2m14.5-7.5L16 5.5m-8 8L5.5 16m13-10.5L16 8m-8 8L5.5 16" / >
< / s v g >
Generieren ` ;
const container = document . getElementById ( 'tune-content' ) ;
container . innerHTML = '<div style="text-align:center;padding:40px;color:var(--text-muted)">Lade Template...</div>' ;
document . getElementById ( 'tune-modal' ) . classList . add ( 'active' ) ;
fetch ( path )
. then ( r => r . json ( ) )
. then ( data => {
window . _tuneState . template = data . template || '' ;
renderTuneUI ( window . _tuneState ) ;
} )
. catch ( e => {
container . innerHTML = ` <div style="color:var(--text-error);text-align:center;padding:20px">✗ Fehler: ${ e . message } </div> ` ;
} ) ;
}
/ * *
* Render die Tune - UI mit Original - Template und Preprompt
* @ param { Object } state - Tune - Zustand
* /
function renderTuneUI ( state ) {
const container = document . getElementById ( 'tune-content' ) ;
container . innerHTML = '' ;
// Original-Template
const originalPanel = document . createElement ( 'div' ) ;
originalPanel . style . cssText = 'display:flex;flex-direction:column;gap:6px;' ;
const origLabel = document . createElement ( 'label' ) ;
origLabel . style . cssText = 'font-weight:600;font-size:13px;color:var(--text-secondary);' ;
origLabel . textContent = 'Original-Template' ;
originalPanel . appendChild ( origLabel ) ;
const origTextarea = document . createElement ( 'textarea' ) ;
origTextarea . id = 'tune-original' ;
origTextarea . value = state . template ;
origTextarea . readOnly = true ;
origTextarea . style . cssText = 'width:100%;min-height:200px;padding:10px;background:var(--bg-input);color:var(--text-primary);border:1px solid var(--border);border-radius:4px;font-family:var(--mono);font-size:13px;resize:vertical;' ;
originalPanel . appendChild ( origTextarea ) ;
container . appendChild ( originalPanel ) ;
// Preprompt-Feld
const prepromptPanel = document . createElement ( 'div' ) ;
prepromptPanel . style . cssText = 'display:flex;flex-direction:column;gap:6px;' ;
const prepromptLabel = document . createElement ( 'label' ) ;
prepromptLabel . style . cssText = 'font-weight:600;font-size:13px;color:var(--text-secondary);' ;
prepromptLabel . textContent = 'Preprompt (deine Wünsche zur Verbesserung)' ;
prepromptPanel . appendChild ( prepromptLabel ) ;
const prepromptTextarea = document . createElement ( 'textarea' ) ;
prepromptTextarea . id = 'tune-preprompt' ;
prepromptTextarea . style . cssText = 'width:100%;min-height:80px;padding:10px;background:var(--bg-input);color:var(--text-primary);border:1px solid var(--border);border-radius:4px;font-family:var(--mono);font-size:13px;resize:vertical;' ;
prepromptTextarea . placeholder = 'z.B. Mach das Template kürzer, füge Security-Checks hinzu, verwende einen professionelleren Ton...' ;
prepromptPanel . appendChild ( prepromptTextarea ) ;
container . appendChild ( prepromptPanel ) ;
// Ergebnis-Panel (initial versteckt)
const resultPanel = document . createElement ( 'div' ) ;
resultPanel . id = 'tune-result-panel' ;
resultPanel . style . cssText = 'display:none;flex-direction:column;gap:6px;' ;
const resultLabel = document . createElement ( 'label' ) ;
resultLabel . style . cssText = 'font-weight:600;font-size:13px;color:var(--text-secondary);' ;
resultLabel . textContent = 'Generiertes Template' ;
resultPanel . appendChild ( resultLabel ) ;
const resultTextarea = document . createElement ( 'textarea' ) ;
resultTextarea . id = 'tune-result' ;
resultTextarea . readOnly = true ;
resultTextarea . style . cssText = 'width:100%;min-height:200px;padding:10px;background:var(--bg-input);color:var(--text-primary);border:1px solid var(--border);border-radius:4px;font-family:var(--mono);font-size:13px;resize:vertical;' ;
resultPanel . appendChild ( resultTextarea ) ;
container . appendChild ( resultPanel ) ;
}
/ * *
* Sende Preprompt + Template an LLM und zeige Ergebnis
* /
async function generateTunedTemplate ( ) {
const preprompt = document . getElementById ( 'tune-preprompt' ) . value . trim ( ) ;
const original = document . getElementById ( 'tune-original' ) . value ;
if ( ! preprompt ) {
showToast ( '✗ Bitte Preprompt eingeben' ) ;
return ;
}
const btn = document . getElementById ( 'tune-generate-btn' ) ;
btn . disabled = true ;
btn . innerHTML = '⏳ Generiere...' ;
const resultPanel = document . getElementById ( 'tune-result-panel' ) ;
const resultTextarea = document . getElementById ( 'tune-result' ) ;
resultPanel . style . display = 'flex' ;
resultTextarea . value = 'Warte auf Generierung...' ;
const systemPrompt = 'Du bist ein Experte für Prompt-Engineering. Du erhältst ein vorhandenes Template und Verbesserungswünsche. Gib NUR das verbesserte Template zurück. Keine Erklärungen, keine Markdown-Codeblöcke, keine Zitatzeichen. Nur den reinen Template-Text.' ;
const userPrompt = ` Verbessere dieses Template basierend auf den folgenden Anweisungen:
=== Vorhandenes Template ===
$ { original }
=== Verbesserungswünsche ===
$ { preprompt }
Gib das verbesserte Template zurück . ` ;
try {
const response = await fetch ( ` ${ LLM _BASE } /chat/completions ` , {
method : 'POST' ,
headers : { 'Content-Type' : 'application/json' } ,
body : JSON . stringify ( {
model : LLM _MODEL ,
messages : [
{ role : 'system' , content : systemPrompt } ,
{ role : 'user' , content : userPrompt }
] ,
stream : false ,
temperature : 0.7 ,
max _tokens : 2048
} )
} ) ;
if ( ! response . ok ) throw new Error ( ` HTTP ${ response . status } ` ) ;
const data = await response . json ( ) ;
let generated = data . choices ? . [ 0 ] ? . message ? . content || '' ;
// Bereinige Markdown-Codeblöcke falls vorhanden
generated = generated . replace ( /^```(?:markdown|text)?\s*\n?/i , '' ) . replace ( /\n```$/ , '' ) ;
generated = generated . trim ( ) ;
window . _tuneState . generated = generated ;
resultTextarea . value = generated || '(Leer - nothing generated)' ;
// Ergebnis-Panel hervorheben und einblenden
resultPanel . style . border = '1px solid var(--accent)' ;
resultPanel . style . transition = 'border-color 0.3s' ;
document . getElementById ( 'tune-apply-btn' ) . style . display = '' ;
resultPanel . scrollIntoView ( { behavior : 'smooth' , block : 'center' } ) ;
showToast ( '✓ Template generiert' ) ;
} catch ( e ) {
resultTextarea . value = ` Fehler: ${ e . message } \n \n Stelle sicher, dass der llama.cpp Server auf ${ LLM _BASE } läuft. ` ;
showToast ( ` ✗ ${ e . message } ` ) ;
} finally {
btn . disabled = false ;
btn . innerHTML = `
< svg width = "14" height = "14" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" stroke - width = "2" >
< path d = "M12 2v4m0 12v4m8-8h-4M6 12H2m14.5-7.5L16 5.5m-8 8L5.5 16m13-10.5L16 8m-8 8L5.5 16" / >
< / s v g >
Generieren ` ;
}
}
/ * *
* Generiertes Template übernehmen ( ersetzt template - Feld im JSON )
* /
async function applyTunedContent ( ) {
const state = window . _tuneState ;
const generated = document . getElementById ( 'tune-result' ) . value ;
if ( ! generated || generated . startsWith ( 'Fehler:' ) || generated . startsWith ( 'Leer' ) ) {
showToast ( '✗ Kein gültiges Ergebnis zum Übernehmen' ) ;
return ;
}
try {
const response = await fetch ( state . path ) ;
if ( ! response . ok ) throw new Error ( 'Konnte Template nicht laden' ) ;
const data = await response . json ( ) ;
data . template = generated ;
const saveResponse = await fetch ( state . path , {
method : 'PUT' ,
headers : { 'Content-Type' : 'text/plain' } ,
body : JSON . stringify ( data , null , 2 )
} ) ;
if ( saveResponse . ok ) {
showToast ( '✓ Template übernommen und gespeichert' ) ;
closeTuneModal ( ) ;
} else {
throw new Error ( ` HTTP ${ saveResponse . status } ` ) ;
}
} catch ( e ) {
showToast ( ` ✗ Fehler beim Speichern: ${ e . message } ` ) ;
}
}
/ * *
* Schließe Tune - Modal
* /
function closeTuneModal ( ) {
document . getElementById ( 'tune-modal' ) . classList . remove ( 'active' ) ;
window . _tuneState = { path : '' , rawPath : '' , template : '' } ;
}
refactor: split serve.py and index.html into single-responsibility modules
Backend:
- path_validator.py: PathValidator-Klasse für Pfad-Validierung
- file_ops.py: read_file, write_file, directory_exists, file_exists
- content_types.py: get_content_type mit EXTENSION_MAP
- handler.py: Handler-Klasse mit do_GET/do_PUT, nutzt above modules
- serve.py: Entry-Point (main, find_free_port), setzt Handler.validator/directory
Frontend:
- css/variables.css: CSS-Variablen (--bg-*, --text-*, --accent, etc.)
- css/styles.css: Alle CSS-Regeln (modal, card, template-grid, etc.)
- js/utils.js: esc, showToast, copyContentToClipboard
- js/modal.js: showModal, closeModal, closeEditModal, wasViewModalOpen
- js/editor.js: editModalContent, createJsonEditUI, extractJsonFromForm
- js/api.js: viewTemplate, copyContent, loadTemplates, saveEditedContent
- js/templates.js: renderTemplates, applyFilters, parseTypeFromHash
- js/main.js: Event-Listener, Hash-Filter, Initialisierung
- index.html: Inline-CSS/JS entfernt, <link>/<script src>-Tags hinzugefügt
Smoke test: SO_REUSEADDR für schnelle Port-Wiederverwendung
2026-05-03 14:40:44 +02:00
// Export for api.js (global scope, loaded before api.js)
// editModalContent, createTextEditUI, createJsonEditUI, extractInputValue, extractJsonFromForm
// sind als globale Funktionen verfügbar