test the graph

This commit is contained in:
2026-07-27 18:40:05 +02:00
parent ba3de2b63b
commit ea7852256f
11 changed files with 185 additions and 64 deletions
+42 -45
View File
@@ -7,6 +7,7 @@ import (
"html/template"
"net/http"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
@@ -20,13 +21,13 @@ type Choice struct {
// StoryNode repräsentiert einen einzelnen Knotenpunkt in der Geschichte
type StoryNode struct {
ID string `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
Choices []Choice `json:"choices"`
IsReboot bool `json:"isReboot"`
IsEnd bool `json:"isEnd"`
FileSource string `json:"fileSource"`
ID string `json:"id"`
Title string `json:"title"`
Content string `json:"content"`
Choices []Choice `json:"choices"`
IsReboot bool `json:"isReboot"`
IsEnd bool `json:"isEnd"`
FileSource string `json:"fileSource"`
}
// StoryGraph hält alle Knoten und Analyse-Daten
@@ -78,8 +79,6 @@ func loadStoryGraph(rootDir string) *StoryGraph {
}
// Regex-Muster für Knoten und Links
// AsciiDoc: [[node_id]] gefolgt von === Titel
// Markdown: # node_id oder [[node_id]]
adocNodeRegex := regexp.MustCompile(`\[\[([a-zA-Z0-9_\-]+)\]\]`)
adocChoiceRegex := regexp.MustCompile(`<<([a-zA-Z0-9_\-]+)\s*,\s*([^>]+)>>`)
mdChoiceRegex := regexp.MustCompile(`\[([^\]]+)\]\((?:#)?([a-zA-Z0-9_\-]+)\)`)
@@ -112,7 +111,7 @@ func loadStoryGraph(rootDir string) *StoryGraph {
// Prüfung auf neuen Knoten-Anker [[id]]
if matches := adocNodeRegex.FindStringSubmatch(line); len(matches) > 1 {
if currentNode != nil {
currentNode.Content = cleanBodyText(currentBody.String())
currentNode.Content = renderWithEchoBox(currentBody.String())
graph.Nodes[currentNode.ID] = currentNode
currentBody.Reset()
}
@@ -186,7 +185,7 @@ func loadStoryGraph(rootDir string) *StoryGraph {
}
if currentNode != nil {
currentNode.Content = cleanBodyText(currentBody.String())
currentNode.Content = renderWithEchoBox(currentBody.String())
graph.Nodes[currentNode.ID] = currentNode
}
@@ -194,23 +193,34 @@ func loadStoryGraph(rootDir string) *StoryGraph {
})
if err != nil {
fmt.Printf("\033[1;31m[WAR NUNG]\033[0m Fehler beim Durchsuchen der Dateien: %v\n", err)
fmt.Printf("\033[1;31m[WARNUNG]\033[0m Fehler beim Durchsuchen der Dateien: %v\n", err)
}
return graph
}
// cleanBodyText entfernt Syntax-Rauschen für die HTML-Darstellung
func cleanBodyText(raw string) string {
// Links entfernen/bereinigen
// renderWithEchoBox entfernt Syntax-Rauschen und nutzt den Debian 13 Docker Container als HTML Echo Box
func renderWithEchoBox(raw string) string {
fmt.Println("render with docker")
// Links entfernen (Diese sollen als Buttons, nicht als Text-Wirrwarr auftauchen)
adocChoiceRegex := regexp.MustCompile(`<<[a-zA-Z0-9_\-]+\s*,\s*([^>]+)>>`)
cleaned := adocChoiceRegex.ReplaceAllString(raw, "")
mdChoiceRegex := regexp.MustCompile(`\[([^\]]+)\]\((?:#)?[a-zA-Z0-9_\-]+\)`)
cleaned = mdChoiceRegex.ReplaceAllString(cleaned, "")
// HTML-Maskierung
return html.EscapeString(strings.TrimSpace(cleaned))
// Docker Echo Box aufrufen (Debian 13 Asciidoctor)
cmd := exec.Command("docker", "run", "--rm", "-i", "asciidoctor-echo")
cmd.Stdin = strings.NewReader(strings.TrimSpace(cleaned))
out, err := cmd.Output()
if err != nil {
// Fallback bei Docker-Fehler (z.B. wenn der Daemon nicht läuft)
fmt.Printf("\033[1;31m[DOCKER FEHLER]\033[0m Konnte Echo Box nicht nutzen. Fallback auf RAW Text.\n")
return html.EscapeString(strings.TrimSpace(cleaned))
}
return string(out)
}
// validateGraph prüft Verzweigungen auf Konsistenz
@@ -246,7 +256,7 @@ func validateGraph(g *StoryGraph) {
// Konsolen-Bericht
if len(g.BrokenLinks) > 0 {
fmt.Println("\n\033[1;31m[!] DEFECKTE ZIEL-LINKS (Fehlende Knoten):\033[0m")
fmt.Println("\n\033[1;31m[!] DEFEKTE ZIEL-LINKS (Fehlende Knoten):\033[0m")
for _, link := range g.BrokenLinks {
fmt.Printf(" - %s\n", link)
}
@@ -300,13 +310,6 @@ func startServer(g *StoryGraph, htmlPath string, port int, watchDir string) {
http.ServeFile(w, r, htmlPath)
})
http.HandleFunc("/api/graph", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
freshGraph := loadStoryGraph(watchDir)
// Einfacher JSON Output
fmt.Fprintf(w, `{"nodes":%d, "start":"%s"}`, len(freshGraph.Nodes), freshGraph.StartNodeID)
})
err := http.ListenAndServe(addr, nil)
if err != nil {
fmt.Printf("Server-Fehler: %v\n", err)
@@ -360,7 +363,6 @@ const htmlTemplate = `<!DOCTYPE html>
pointer-events: none;
}
/* Window Maker Header */
header {
background: #111;
border-bottom: 2px solid var(--term-green);
@@ -389,7 +391,6 @@ const htmlTemplate = `<!DOCTYPE html>
background: rgba(0, 20, 10, 0.5);
}
/* Hauptbereich */
main {
flex: 1;
display: flex;
@@ -403,7 +404,6 @@ const htmlTemplate = `<!DOCTYPE html>
z-index: 1;
}
/* Story Terminal Content */
.node-card {
border: 1px solid var(--term-dim);
background: rgba(5, 15, 10, 0.85);
@@ -424,11 +424,19 @@ const htmlTemplate = `<!DOCTYPE html>
.node-body {
line-height: 1.6;
font-size: 1rem;
white-space: pre-wrap;
margin-bottom: 20px;
}
/* Choices List */
/* AsciiDoc Formatierungen im Terminal Look */
.node-body p { margin-bottom: 15px; }
.node-body ul, .node-body ol { margin-bottom: 15px; margin-left: 20px; }
.node-body li { margin-bottom: 5px; }
.node-body strong, .node-body b { color: var(--term-amber); }
.node-body em, .node-body i { color: var(--term-cyan); font-style: italic;}
.node-body code { background: rgba(0, 255, 102, 0.1); padding: 2px 5px; border-radius: 3px; font-family: var(--font-stack); }
.node-body h1, .node-body h2, .node-body h3, .node-body h4 { color: var(--term-cyan); margin: 20px 0 10px 0; border-bottom: 1px dashed var(--term-dim); padding-bottom: 5px; }
.node-body blockquote { border-left: 3px solid var(--term-dim); padding-left: 10px; margin-bottom: 15px; font-style: italic; opacity: 0.8; }
.choices-container {
display: flex;
flex-direction: column;
@@ -471,7 +479,6 @@ const htmlTemplate = `<!DOCTYPE html>
color: var(--term-green);
}
/* Controls & Graph Modal Toggle */
footer {
border-top: 1px solid var(--term-dim);
padding: 10px 20px;
@@ -496,7 +503,6 @@ const htmlTemplate = `<!DOCTYPE html>
color: var(--term-cyan);
}
/* Graph Canvas View */
#graph-modal {
display: none;
position: fixed;
@@ -541,17 +547,15 @@ const htmlTemplate = `<!DOCTYPE html>
</div>
</footer>
<!-- Visual Graph Overlay -->
<div id="graph-modal">
<div style="display:flex; justify-size:space-between; margin-bottom:100px;">
<div style="display:flex; justify-size:space-between; margin-bottom:10px;">
<span style="color:var(--term-cyan)">=== INTERAKTIVER VORHERSAGE-GRAPH ===</span>
<button class="btn-small" onclick="toggleGraphView()">[X] SCHLIESSEN</button>
<button class="btn-small" style="margin-left: auto;" onclick="toggleGraphView()">[X] SCHLIESSEN</button>
</div>
<canvas id="graph-canvas"></canvas>
</div>
<script>
// Story Graph Injection aus Go Backend
const storyData = {{.}};
let currentNodeID = storyData.startNodeId || Object.keys(storyData.nodes)[0];
@@ -559,7 +563,6 @@ const htmlTemplate = `<!DOCTYPE html>
let hullIntegrity = 100;
let history = [];
// Synthesizer Audio API (Retro-Beeps)
const audioCtx = new (window.AudioContext || window.webkitAudioContext)();
function playBeep(freq = 440, type = 'square', duration = 0.05) {
try {
@@ -590,7 +593,8 @@ const htmlTemplate = `<!DOCTYPE html>
document.getElementById('stat-node-id').innerText = node.id;
document.getElementById('node-title').innerText = "=== " + node.title + " ===";
document.getElementById('node-body').innerText = node.content;
// WICHTIG: Nutzt jetzt gerendertes HTML statt purem Text
document.getElementById('node-body').innerHTML = node.content;
const choicesBox = document.getElementById('choices-box');
choicesBox.innerHTML = '';
@@ -622,7 +626,6 @@ const htmlTemplate = `<!DOCTYPE html>
choicesBox.appendChild(btn);
}
// Automatischer Scroll nach oben
document.getElementById('terminal-viewport').scrollTop = 0;
}
@@ -632,7 +635,6 @@ const htmlTemplate = `<!DOCTYPE html>
renderNode(storyData.startNodeId);
}
// Keyboard Shortcuts
window.addEventListener('keydown', (e) => {
if (e.key >= '1' && e.key <= '9') {
const idx = parseInt(e.key) - 1;
@@ -647,7 +649,6 @@ const htmlTemplate = `<!DOCTYPE html>
}
});
// Visueller Canvas Graph
function toggleGraphView() {
const modal = document.getElementById('graph-modal');
if (modal.style.display === 'flex') {
@@ -675,7 +676,6 @@ const htmlTemplate = `<!DOCTYPE html>
const positions = {};
// Knoten im Kreis anordnen
keys.forEach((key, idx) => {
const angle = (idx / total) * 2 * Math.PI;
const x = centerX + radius * Math.cos(angle);
@@ -683,7 +683,6 @@ const htmlTemplate = `<!DOCTYPE html>
positions[key] = { x, y };
});
// Kanten zeichnen
ctx.strokeStyle = 'rgba(51, 255, 102, 0.3)';
ctx.lineWidth = 1;
@@ -701,7 +700,6 @@ const htmlTemplate = `<!DOCTYPE html>
}
});
// Knotenpunkte zeichnen
keys.forEach(key => {
const pos = positions[key];
ctx.beginPath();
@@ -721,7 +719,6 @@ const htmlTemplate = `<!DOCTYPE html>
});
}
// Starten
window.onload = () => {
renderNode(currentNodeID);
};