test the graph
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
FROM debian:trixie-slim
|
||||
|
||||
# Asciidoctor installieren und apt-Cache sofort wieder leeren, um das Image klein zu halten
|
||||
RUN apt-get update && \
|
||||
apt-get install -y --no-install-recommends asciidoctor && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# ENTRYPOINT als Echo Box:
|
||||
# -s = embedded (kein html/body wraper)
|
||||
# -o - = output auf stdout
|
||||
# - = read von stdin
|
||||
ENTRYPOINT ["asciidoctor", "-s", "-o", "-", "-"]
|
||||
@@ -10,3 +10,7 @@ go build -o cyoa-engine main.go
|
||||
./cyoa-engine -dir ./stories -serve -port 8080
|
||||
# Wenn du jetzt in Neovim Dateien abspeicherst oder neue `[[node_id]]`-Knoten anlegst, musst du im
|
||||
# Browser nur `http://localhost:8080` aufrufen und siehst sofort deine Änderungen inkl. vollständiger Link-Prüfung!
|
||||
|
||||
# baue das docker image
|
||||
|
||||
docker build -t asciidoctor-echo -f Dockerfile.echobox .
|
||||
+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);
|
||||
};
|
||||
|
||||
@@ -1,16 +1,20 @@
|
||||
[[funk]]
|
||||
=== Konfiguration: Komunijation wieder herstellen
|
||||
# Konfiguration: Komunijation wieder herstellen
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
Systemmeldung: OK .
|
||||
====
|
||||
#### Systemmeldung
|
||||
```
|
||||
: OK .
|
||||
```
|
||||
|
||||
## Kontext
|
||||
|
||||
Daten fliessen in die Komunikation Buffer, erste Trägerfrequenzen wurden gefunden, automatisches nachführen aktiv. +
|
||||
Aber dann war es das auch schon, keine Kontakt zur Flotte keine Updates. Da draussen ist etwa und es hat wohl mit den selben Schwirigkeiten zu kämpfen.
|
||||
|
||||
* <<steuerung, [01] die Steuerung mit simpler sensorig.>>
|
||||
* <<waffen, [02] die Waffen auf automatik schalten.>>
|
||||
* <<sensoren, [03] alle Sensoren zur anayse.>>
|
||||
* <<start, [04] zum anfang zurück.>>
|
||||
|
||||
## Was werden wir ausführen?
|
||||
|
||||
<<steuerung, [01] die Steuerung mit simpler sensorig.>>
|
||||
<<waffen, [02] die Waffen auf automatik schalten.>>
|
||||
<<sensoren, [03] alle Sensoren zur anayse.>>
|
||||
<<start, [04] zum anfang zurück.>>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
[[sensoren]]
|
||||
# Konfiguration: Sensoren zur anayse
|
||||
|
||||
#### Systemmeldung
|
||||
```
|
||||
: sensor daten angeschlossen
|
||||
```
|
||||
|
||||
|
||||
## Kontext
|
||||
|
||||
|
||||
|
||||
### Was werden wir ausführen?
|
||||
|
||||
<<funk, [01] die Komunikation auf dem Haupt Kanal evtl Updates der Flotte.>>
|
||||
<<waffen, [02] die Waffen auf automatik schalten.>>
|
||||
<<steuerung, [03] die Steuerung mit simpler sensorig.>>
|
||||
<<start, [04] zum anfang zurück.>>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[[start]]
|
||||
## Sektor 42-A: Die erste Annäherung
|
||||
# Sektor 42-A: Die erste Annäherung
|
||||
|
||||
Welche Konfiguration ist die richtige, wir wissen einfach noch nicht genug über die beanstandetetn Fehler. +
|
||||
Wie nicht nur in der Computer Technik üblicher werden wir das gute alte Ausschlussverfahren anwenden. Folgendende Optionen bieten sich uns als nächste zum ausschliessen an.
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
[[steuerung]]
|
||||
=== Konfiguration: Steuerung wieder herstellen
|
||||
# Konfiguration: Steuerung wieder herstellen
|
||||
|
||||
[WARNING]
|
||||
====
|
||||
Systemmeldung: einige Variablen konnten nicht verarbeitet werden
|
||||
====
|
||||
#### Systemmeldung
|
||||
```
|
||||
: einige Variablen konnten nicht verarbeitet werden
|
||||
```
|
||||
|
||||
|
||||
## Kontext
|
||||
|
||||
Die verarbeitung hat sehr lange gedauert, kein gutes Zeichen. Die Meldung sagt aber eben auch nichts genaueres. +
|
||||
Hier braucht es noch einige versuche um an die teile zu gelangen die nicht zur Eingabe passen. Erst eine Block ausschluss Verharen und dann Zeilenweise bis der Punkt gefunden wurde wo es bricht.
|
||||
|
||||
* <<funk, [01] die Komunikation auf dem Haupt Kanal evtl Updates der Flotte.>>
|
||||
* <<waffen, [02] die Waffen auf automatik schalten.>>
|
||||
* <<sensoren, [03] alle Sensoren zur anayse.>>
|
||||
* <<start, [04] zum anfang zurück.>>
|
||||
### Was werden wir ausführen?
|
||||
|
||||
<<steuerungoben, [01] steuer config oben.>>
|
||||
<<steuerungmitte, [02] steuer config mitte.>>
|
||||
<<steuerungunten, [03] steuer config unten.>>
|
||||
<<start, [04] zum anfang zurück.>>
|
||||
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
[[steuerungmitte]]
|
||||
# Konfiguration: Steuerung wieder herstellen
|
||||
|
||||
#### Systemmeldung
|
||||
```
|
||||
: einige Variablen konnten nicht verarbeitet werden
|
||||
```
|
||||
|
||||
|
||||
## Kontext
|
||||
|
||||
Mitte
|
||||
|
||||
### Was werden wir ausführen?
|
||||
|
||||
<<steuerungoben, [01] steuer config oben.>>
|
||||
<<steuerungmitte, [02] steuer config mitte.>>
|
||||
<<steuerungunten, [03] steuer config unten.>>
|
||||
<<start, [04] zum anfang zurück.>>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
[[steuerungoben]]
|
||||
# Konfiguration: Steuerung wieder herstellen
|
||||
|
||||
#### Systemmeldung
|
||||
```
|
||||
: einige Variablen konnten nicht verarbeitet werden
|
||||
```
|
||||
|
||||
|
||||
## Kontext
|
||||
|
||||
Oben
|
||||
|
||||
### Was werden wir ausführen?
|
||||
|
||||
<<steuerungoben, [01] steuer config oben.>>
|
||||
<<steuerungmitte, [02] steuer config mitte.>>
|
||||
<<steuerungunten, [03] steuer config unten.>>
|
||||
<<start, [04] zum anfang zurück.>>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
[[steuerungunten]]
|
||||
# Konfiguration: Steuerung wieder herstellen
|
||||
|
||||
#### Systemmeldung
|
||||
```
|
||||
: einige Variablen konnten nicht verarbeitet werden
|
||||
```
|
||||
|
||||
|
||||
## Kontext
|
||||
|
||||
Mitte
|
||||
|
||||
### Was werden wir ausführen?
|
||||
|
||||
<<steuerungoben, [01] steuer config oben.>>
|
||||
<<steuerungmitte, [02] steuer config mitte.>>
|
||||
<<steuerungunten, [03] steuer config unten.>>
|
||||
<<start, [04] zum anfang zurück.>>
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
[[waffen]]
|
||||
# Konfiguration: Waffen auf automatik schalten
|
||||
|
||||
#### Systemmeldung
|
||||
```
|
||||
: energy low, stand by on low power
|
||||
```
|
||||
|
||||
## Kontext
|
||||
|
||||
|
||||
|
||||
|
||||
### Was werden wir ausführen?
|
||||
|
||||
<<steuerung, [01] die Steuerung mit simpler sensorig.>>
|
||||
<<funk, [02] die Komunikation auf dem Haupt Kanal evtl Updates der Flotte.>>
|
||||
<<sensoren, [03] alle Sensoren zur anayse.>>
|
||||
<<start, [04] zum anfang zurück.>>
|
||||
Reference in New Issue
Block a user