test the graph
This commit is contained in:
+42
-45
@@ -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);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user