First commit
Browse files- .gitignore +1 -0
- app/templates/base.html +188 -14
- app/templates/chat.html +123 -8
- app/templates/dev.html +4 -6
- app/ui.py +11 -13
.gitignore
CHANGED
|
@@ -31,3 +31,4 @@ Thumbs.db
|
|
| 31 |
|
| 32 |
# RAG index files
|
| 33 |
.faiss/
|
|
|
|
|
|
| 31 |
|
| 32 |
# RAG index files
|
| 33 |
.faiss/
|
| 34 |
+
/backup
|
app/templates/base.html
CHANGED
|
@@ -4,34 +4,208 @@
|
|
| 4 |
<meta charset="utf-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 6 |
<title>matrix-ai</title>
|
|
|
|
| 7 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 8 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 9 |
-
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&display=swap" rel="stylesheet">
|
|
|
|
| 10 |
<style>
|
| 11 |
-
:root {
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
</style>
|
| 22 |
</head>
|
| 23 |
<body>
|
|
|
|
|
|
|
| 24 |
<header>
|
| 25 |
-
<
|
| 26 |
<nav>
|
| 27 |
-
<a href="/">Home</a>
|
| 28 |
<a href="/chat">Chat</a>
|
| 29 |
<a href="/dev">Dev</a>
|
| 30 |
<a href="/docs" target="_blank" rel="noreferrer">API Docs</a>
|
| 31 |
</nav>
|
| 32 |
</header>
|
|
|
|
| 33 |
<div class="wrap">
|
| 34 |
{% block body %}{% endblock %}
|
| 35 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
</body>
|
| 37 |
-
</html>
|
|
|
|
| 4 |
<meta charset="utf-8" />
|
| 5 |
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
| 6 |
<title>matrix-ai</title>
|
| 7 |
+
|
| 8 |
<link rel="preconnect" href="https://fonts.googleapis.com">
|
| 9 |
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
| 10 |
+
<link href="https://fonts.googleapis.com/css2?family=Share+Tech+Mono&family=Inter:wght@400;600&display=swap" rel="stylesheet">
|
| 11 |
+
|
| 12 |
<style>
|
| 13 |
+
:root {
|
| 14 |
+
--bg: #020402;
|
| 15 |
+
--bg2: #071007;
|
| 16 |
+
--text: #c8facc;
|
| 17 |
+
--muted: #7ef7a7;
|
| 18 |
+
--matrix: #00ff9c;
|
| 19 |
+
--matrix-dim: #00b86b;
|
| 20 |
+
--card: #061006cc; /* translucent */
|
| 21 |
+
--border: #0d1e0f;
|
| 22 |
+
}
|
| 23 |
+
|
| 24 |
+
html, body { height: 100%; }
|
| 25 |
+
body {
|
| 26 |
+
margin: 0;
|
| 27 |
+
color: var(--text);
|
| 28 |
+
background:
|
| 29 |
+
radial-gradient(1200px 800px at 100% -10%, rgba(0,255,156,0.06), transparent 40%),
|
| 30 |
+
radial-gradient(1000px 600px at -10% 100%, rgba(0,255,156,0.05), transparent 40%),
|
| 31 |
+
linear-gradient(180deg, var(--bg), var(--bg2) 60%, var(--bg));
|
| 32 |
+
font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
| 33 |
+
overflow-y: auto;
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
/* Subtle scanlines */
|
| 37 |
+
body::after {
|
| 38 |
+
content: "";
|
| 39 |
+
position: fixed;
|
| 40 |
+
inset: 0;
|
| 41 |
+
pointer-events: none;
|
| 42 |
+
background: repeating-linear-gradient(
|
| 43 |
+
to bottom,
|
| 44 |
+
rgba(0, 0, 0, 0.06) 0px,
|
| 45 |
+
rgba(0, 0, 0, 0.06) 1px,
|
| 46 |
+
transparent 2px,
|
| 47 |
+
transparent 3px
|
| 48 |
+
);
|
| 49 |
+
mix-blend-mode: overlay;
|
| 50 |
+
opacity: 0.6;
|
| 51 |
+
z-index: 0;
|
| 52 |
+
}
|
| 53 |
+
|
| 54 |
+
/* Code rain canvas (behind everything) */
|
| 55 |
+
#code-rain {
|
| 56 |
+
position: fixed;
|
| 57 |
+
inset: 0;
|
| 58 |
+
width: 100vw;
|
| 59 |
+
height: 100vh;
|
| 60 |
+
z-index: 0;
|
| 61 |
+
pointer-events: none;
|
| 62 |
+
opacity: 0.65; /* keep it subtle */
|
| 63 |
+
}
|
| 64 |
+
|
| 65 |
+
header {
|
| 66 |
+
position: sticky; top: 0; z-index: 3;
|
| 67 |
+
display: flex; align-items: center; gap: 18px;
|
| 68 |
+
padding: 18px 22px;
|
| 69 |
+
background: linear-gradient(180deg, rgba(0,0,0,0.35), rgba(0,0,0,0));
|
| 70 |
+
border-bottom: 1px solid var(--border);
|
| 71 |
+
backdrop-filter: blur(4px);
|
| 72 |
+
}
|
| 73 |
+
.brand {
|
| 74 |
+
font-family: "Share Tech Mono", monospace;
|
| 75 |
+
color: var(--matrix);
|
| 76 |
+
text-shadow: 0 0 8px rgba(0,255,156,0.4);
|
| 77 |
+
letter-spacing: 0.04em;
|
| 78 |
+
font-size: 18px;
|
| 79 |
+
}
|
| 80 |
+
nav a {
|
| 81 |
+
color: var(--muted);
|
| 82 |
+
text-decoration: none;
|
| 83 |
+
margin-right: 16px;
|
| 84 |
+
transition: color .15s ease, text-shadow .15s ease;
|
| 85 |
+
}
|
| 86 |
+
nav a:hover {
|
| 87 |
+
color: var(--matrix);
|
| 88 |
+
text-shadow: 0 0 8px rgba(0,255,156,0.4);
|
| 89 |
+
}
|
| 90 |
+
|
| 91 |
+
.wrap { position: relative; z-index: 2; max-width: 980px; margin: 0 auto; padding: 26px 22px 60px; }
|
| 92 |
+
.card {
|
| 93 |
+
background: var(--card);
|
| 94 |
+
border: 1px solid var(--border);
|
| 95 |
+
border-radius: 16px;
|
| 96 |
+
box-shadow: 0 0 0 1px rgba(0,255,156,0.06), 0 8px 30px rgba(0,0,0,0.35);
|
| 97 |
+
padding: 20px;
|
| 98 |
+
}
|
| 99 |
+
h2, h3, h4 { font-family: "Share Tech Mono", monospace; color: var(--matrix); letter-spacing: .02em; }
|
| 100 |
+
p { color: var(--text); opacity: 0.95; }
|
| 101 |
+
|
| 102 |
+
input, textarea {
|
| 103 |
+
width: 100%;
|
| 104 |
+
color: var(--text);
|
| 105 |
+
background: #020a04;
|
| 106 |
+
border: 1px solid var(--border);
|
| 107 |
+
border-radius: 12px;
|
| 108 |
+
padding: 12px 12px;
|
| 109 |
+
font-size: 14px;
|
| 110 |
+
font-family: "Share Tech Mono", monospace;
|
| 111 |
+
outline: none;
|
| 112 |
+
transition: border-color .15s ease, box-shadow .15s ease;
|
| 113 |
+
}
|
| 114 |
+
input:focus, textarea:focus {
|
| 115 |
+
border-color: var(--matrix);
|
| 116 |
+
box-shadow: 0 0 0 3px rgba(0,255,156,0.08), 0 0 12px rgba(0,255,156,0.25) inset;
|
| 117 |
+
}
|
| 118 |
+
|
| 119 |
+
button {
|
| 120 |
+
background: linear-gradient(180deg, #00ff9c, #00c97e);
|
| 121 |
+
color: #002f1b;
|
| 122 |
+
border: 0;
|
| 123 |
+
padding: 10px 16px;
|
| 124 |
+
border-radius: 12px;
|
| 125 |
+
font-weight: 700;
|
| 126 |
+
font-family: "Share Tech Mono", monospace;
|
| 127 |
+
letter-spacing: 0.03em;
|
| 128 |
+
cursor: pointer;
|
| 129 |
+
box-shadow: 0 6px 20px rgba(0,255,156,0.25);
|
| 130 |
+
transition: transform .05s ease, box-shadow .15s ease, filter .15s ease;
|
| 131 |
+
}
|
| 132 |
+
button:hover { filter: brightness(1.05); box-shadow: 0 10px 30px rgba(0,255,156,0.35); }
|
| 133 |
+
button:active { transform: translateY(1px); }
|
| 134 |
+
|
| 135 |
+
pre, code {
|
| 136 |
+
font-family: "Share Tech Mono", monospace;
|
| 137 |
+
background: #020a04;
|
| 138 |
+
border: 1px solid var(--border);
|
| 139 |
+
border-radius: 12px;
|
| 140 |
+
}
|
| 141 |
+
pre { padding: 12px; overflow: auto; }
|
| 142 |
+
|
| 143 |
+
@keyframes glow {
|
| 144 |
+
0%, 100% { text-shadow: 0 0 10px rgba(0,255,156,0.12); }
|
| 145 |
+
50% { text-shadow: 0 0 14px rgba(0,255,156,0.28); }
|
| 146 |
+
}
|
| 147 |
+
h3 { animation: glow 3.5s ease-in-out infinite; }
|
| 148 |
</style>
|
| 149 |
</head>
|
| 150 |
<body>
|
| 151 |
+
<canvas id="code-rain"></canvas>
|
| 152 |
+
|
| 153 |
<header>
|
| 154 |
+
<div class="brand">MATRIX-AI</div>
|
| 155 |
<nav>
|
|
|
|
| 156 |
<a href="/chat">Chat</a>
|
| 157 |
<a href="/dev">Dev</a>
|
| 158 |
<a href="/docs" target="_blank" rel="noreferrer">API Docs</a>
|
| 159 |
</nav>
|
| 160 |
</header>
|
| 161 |
+
|
| 162 |
<div class="wrap">
|
| 163 |
{% block body %}{% endblock %}
|
| 164 |
</div>
|
| 165 |
+
|
| 166 |
+
<script>
|
| 167 |
+
// Lightweight "Matrix" code rain — tuned for Spaces
|
| 168 |
+
(function () {
|
| 169 |
+
const c = document.getElementById('code-rain');
|
| 170 |
+
const ctx = c.getContext('2d', { alpha: true });
|
| 171 |
+
let w, h, cols, drops;
|
| 172 |
+
const fontSize = 20; // <-- CHANGED: Increased from 16 to 20
|
| 173 |
+
const charSet = 'アァカサタナハマヤラワ0123456789アイウエオアイウエオ01';
|
| 174 |
+
|
| 175 |
+
function resize() {
|
| 176 |
+
w = c.width = window.innerWidth;
|
| 177 |
+
h = c.height = window.innerHeight;
|
| 178 |
+
cols = Math.floor(w / fontSize);
|
| 179 |
+
drops = Array(cols).fill(0).map(() => Math.floor(Math.random() * -50));
|
| 180 |
+
ctx.font = fontSize + "px 'Share Tech Mono', monospace";
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
function draw() {
|
| 184 |
+
// translucent rect to create trail effect
|
| 185 |
+
ctx.fillStyle = 'rgba(2, 10, 4, 0.10)';
|
| 186 |
+
ctx.fillRect(0, 0, w, h);
|
| 187 |
+
|
| 188 |
+
for (let i = 0; i < cols; i++) {
|
| 189 |
+
const x = i * fontSize;
|
| 190 |
+
const y = drops[i] * fontSize;
|
| 191 |
+
const ch = charSet[Math.floor(Math.random() * charSet.length)];
|
| 192 |
+
// leading glow + core
|
| 193 |
+
ctx.shadowColor = 'rgba(0,255,156,0.35)';
|
| 194 |
+
ctx.shadowBlur = 8;
|
| 195 |
+
ctx.fillStyle = '#00ff9c';
|
| 196 |
+
ctx.fillText(ch, x, y);
|
| 197 |
+
|
| 198 |
+
// advance
|
| 199 |
+
if (y > h && Math.random() > 0.975) drops[i] = 0;
|
| 200 |
+
else drops[i]++;
|
| 201 |
+
}
|
| 202 |
+
setTimeout(draw, 70); // <-- CHANGED: Replaced requestAnimationFrame for slower speed
|
| 203 |
+
}
|
| 204 |
+
|
| 205 |
+
window.addEventListener('resize', resize);
|
| 206 |
+
resize();
|
| 207 |
+
draw(); // Start the animation
|
| 208 |
+
})();
|
| 209 |
+
</script>
|
| 210 |
</body>
|
| 211 |
+
</html>
|
app/templates/chat.html
CHANGED
|
@@ -1,14 +1,129 @@
|
|
| 1 |
{% extends "base.html" %}
|
| 2 |
{% block body %}
|
| 3 |
<div class="card">
|
| 4 |
-
<h3>Chat
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
</form>
|
| 9 |
-
{% if answer %}
|
| 10 |
-
<h4>Answer</h4>
|
| 11 |
-
<pre>{{ answer }}</pre>
|
| 12 |
-
{% endif %}
|
| 13 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 14 |
{% endblock %}
|
|
|
|
| 1 |
{% extends "base.html" %}
|
| 2 |
{% block body %}
|
| 3 |
<div class="card">
|
| 4 |
+
<h3>Chat — Matrix System 1.0</h3>
|
| 5 |
+
|
| 6 |
+
<!-- Messages -->
|
| 7 |
+
<div id="messages" style="margin-top:14px; display:flex; flex-direction:column; gap:10px; max-height:60vh; overflow:auto;">
|
| 8 |
+
<!-- Filled by JS from localStorage -->
|
| 9 |
+
</div>
|
| 10 |
+
|
| 11 |
+
<!-- Composer -->
|
| 12 |
+
<form id="chatForm" style="display:grid; gap:12px; margin-top:14px;">
|
| 13 |
+
<textarea id="question" rows="4" placeholder="Ask anything about Matrix EcoSystem, Guardian, or Hub..."></textarea>
|
| 14 |
+
<div style="display:flex; gap:10px; align-items:center;">
|
| 15 |
+
<button id="sendBtn" type="submit">Send</button>
|
| 16 |
+
<button id="clearBtn" type="button" style="background:#0c1d13; color:#7ef7a7; box-shadow:none; border:1px solid var(--border);">Clear</button>
|
| 17 |
+
</div>
|
| 18 |
</form>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</div>
|
| 20 |
+
|
| 21 |
+
<style>
|
| 22 |
+
.bubble {
|
| 23 |
+
max-width: 80%;
|
| 24 |
+
border: 1px solid var(--border);
|
| 25 |
+
border-radius: 14px;
|
| 26 |
+
padding: 10px 12px;
|
| 27 |
+
line-height: 1.45;
|
| 28 |
+
white-space: pre-wrap;
|
| 29 |
+
word-break: break-word;
|
| 30 |
+
box-shadow: 0 4px 16px rgba(0,0,0,0.25), 0 0 0 1px rgba(0,255,156,0.05);
|
| 31 |
+
font-family: "Share Tech Mono", monospace;
|
| 32 |
+
}
|
| 33 |
+
.user { align-self: flex-end; background: #062013; color: var(--text); border-color: #0e2e1a; }
|
| 34 |
+
.bot { align-self: flex-start; background: #05140c; color: var(--text); border-color: #0c2416; }
|
| 35 |
+
.meta { font-size: 11px; opacity: .6; margin-top: 2px; }
|
| 36 |
+
</style>
|
| 37 |
+
|
| 38 |
+
<script>
|
| 39 |
+
(function () {
|
| 40 |
+
const KEY = 'matrix_ai_chat_history';
|
| 41 |
+
const messagesEl = document.getElementById('messages');
|
| 42 |
+
const form = document.getElementById('chatForm');
|
| 43 |
+
const input = document.getElementById('question');
|
| 44 |
+
const sendBtn = document.getElementById('sendBtn');
|
| 45 |
+
const clearBtn = document.getElementById('clearBtn');
|
| 46 |
+
|
| 47 |
+
function loadHistory() {
|
| 48 |
+
try {
|
| 49 |
+
return JSON.parse(localStorage.getItem(KEY) || '[]');
|
| 50 |
+
} catch { return []; }
|
| 51 |
+
}
|
| 52 |
+
function saveHistory(hist) {
|
| 53 |
+
localStorage.setItem(KEY, JSON.stringify(hist.slice(-100))); // cap
|
| 54 |
+
}
|
| 55 |
+
function msgEl(role, text, ts) {
|
| 56 |
+
const wrap = document.createElement('div');
|
| 57 |
+
wrap.style.display = 'flex';
|
| 58 |
+
wrap.style.flexDirection = 'column';
|
| 59 |
+
wrap.style.gap = '2px';
|
| 60 |
+
const b = document.createElement('div');
|
| 61 |
+
b.className = 'bubble ' + (role === 'user' ? 'user' : 'bot');
|
| 62 |
+
b.textContent = text;
|
| 63 |
+
const meta = document.createElement('div');
|
| 64 |
+
meta.className = 'meta';
|
| 65 |
+
meta.textContent = new Date(ts).toLocaleString();
|
| 66 |
+
wrap.appendChild(b);
|
| 67 |
+
wrap.appendChild(meta);
|
| 68 |
+
return wrap;
|
| 69 |
+
}
|
| 70 |
+
function render(hist) {
|
| 71 |
+
messagesEl.innerHTML = '';
|
| 72 |
+
hist.forEach(m => messagesEl.appendChild(msgEl(m.role, m.text, m.ts)));
|
| 73 |
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
let history = loadHistory();
|
| 77 |
+
if (history.length === 0) {
|
| 78 |
+
// seed a friendly welcome
|
| 79 |
+
history.push({ role: 'bot', text: 'Welcome to MATRIX-AI. Ask me about Matrix System 1.0, Guardian, or the Hub.', ts: Date.now() });
|
| 80 |
+
saveHistory(history);
|
| 81 |
+
}
|
| 82 |
+
render(history);
|
| 83 |
+
|
| 84 |
+
clearBtn.addEventListener('click', () => {
|
| 85 |
+
history = [];
|
| 86 |
+
saveHistory(history);
|
| 87 |
+
render(history);
|
| 88 |
+
});
|
| 89 |
+
|
| 90 |
+
form.addEventListener('submit', async (e) => {
|
| 91 |
+
e.preventDefault();
|
| 92 |
+
const q = (input.value || '').trim();
|
| 93 |
+
if (!q) return;
|
| 94 |
+
input.value = '';
|
| 95 |
+
sendBtn.disabled = true;
|
| 96 |
+
|
| 97 |
+
// append user message
|
| 98 |
+
const userMsg = { role: 'user', text: q, ts: Date.now() };
|
| 99 |
+
history.push(userMsg);
|
| 100 |
+
saveHistory(history);
|
| 101 |
+
render(history);
|
| 102 |
+
|
| 103 |
+
try {
|
| 104 |
+
const port = (window.location.port || (window.location.href.includes('/+/') ? '' : ''));
|
| 105 |
+
const base = window.location.origin; // same origin (HF proxies handle it)
|
| 106 |
+
const r = await fetch(base.replace(/\/+$/, '') + '/v1/chat', {
|
| 107 |
+
method: 'POST',
|
| 108 |
+
headers: { 'content-type': 'application/json' },
|
| 109 |
+
body: JSON.stringify({ query: q })
|
| 110 |
+
});
|
| 111 |
+
let answer = '(no answer)';
|
| 112 |
+
if (r.ok) {
|
| 113 |
+
const data = await r.json();
|
| 114 |
+
answer = (data && (data.answer || data.response || JSON.stringify(data))) || answer;
|
| 115 |
+
} else {
|
| 116 |
+
answer = `HTTP ${r.status}`;
|
| 117 |
+
}
|
| 118 |
+
history.push({ role: 'bot', text: answer, ts: Date.now() });
|
| 119 |
+
} catch (err) {
|
| 120 |
+
history.push({ role: 'bot', text: 'Error: ' + (err && err.message ? err.message : String(err)), ts: Date.now() });
|
| 121 |
+
} finally {
|
| 122 |
+
saveHistory(history);
|
| 123 |
+
render(history);
|
| 124 |
+
sendBtn.disabled = false;
|
| 125 |
+
}
|
| 126 |
+
});
|
| 127 |
+
})();
|
| 128 |
+
</script>
|
| 129 |
{% endblock %}
|
app/templates/dev.html
CHANGED
|
@@ -1,12 +1,10 @@
|
|
| 1 |
{% extends "base.html" %}
|
| 2 |
{% block body %}
|
| 3 |
<div class="card">
|
| 4 |
-
<h3>Dev
|
| 5 |
-
<form method="post" style="display:grid; gap:12px;">
|
| 6 |
-
<textarea name="payload" rows="
|
| 7 |
-
<div
|
| 8 |
-
<button type="submit">Call /v1/plan</button>
|
| 9 |
-
</div>
|
| 10 |
</form>
|
| 11 |
|
| 12 |
{% if error %}
|
|
|
|
| 1 |
{% extends "base.html" %}
|
| 2 |
{% block body %}
|
| 3 |
<div class="card">
|
| 4 |
+
<h3>Dev — Exercise /v1/plan</h3>
|
| 5 |
+
<form method="post" style="display:grid; gap:12px; margin-top:12px;">
|
| 6 |
+
<textarea name="payload" rows="18" spellcheck="false">{{ sample }}</textarea>
|
| 7 |
+
<div><button type="submit">Call /v1/plan</button></div>
|
|
|
|
|
|
|
| 8 |
</form>
|
| 9 |
|
| 10 |
{% if error %}
|
app/ui.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
from fastapi import APIRouter, Request, Form
|
| 2 |
-
from fastapi.responses import HTMLResponse
|
| 3 |
from fastapi.templating import Jinja2Templates
|
| 4 |
import httpx, os, json
|
| 5 |
|
|
@@ -7,13 +7,13 @@ router = APIRouter()
|
|
| 7 |
templates = Jinja2Templates(directory="app/templates")
|
| 8 |
|
| 9 |
def _self_base_url() -> str:
|
| 10 |
-
# When running inside HF Space Docker, use localhost + PORT
|
| 11 |
port = os.getenv("PORT", "7860")
|
| 12 |
return f"http://127.0.0.1:{port}"
|
| 13 |
|
| 14 |
-
@router.get("/",
|
| 15 |
-
async def
|
| 16 |
-
|
|
|
|
| 17 |
|
| 18 |
@router.get("/chat", response_class=HTMLResponse)
|
| 19 |
async def chat_get(request: Request):
|
|
@@ -21,11 +21,10 @@ async def chat_get(request: Request):
|
|
| 21 |
|
| 22 |
@router.post("/chat", response_class=HTMLResponse)
|
| 23 |
async def chat_post(request: Request, question: str = Form(...)):
|
| 24 |
-
# Call your /v1/chat (or return a placeholder)
|
| 25 |
-
base_url = _self_base_url()
|
| 26 |
try:
|
| 27 |
-
async with httpx.AsyncClient(timeout=15.0) as client:
|
| 28 |
-
r = await client.post("/v1/chat",
|
|
|
|
| 29 |
data = r.json()
|
| 30 |
answer = data.get("answer", "(no answer)")
|
| 31 |
except Exception as e:
|
|
@@ -34,7 +33,6 @@ async def chat_post(request: Request, question: str = Form(...)):
|
|
| 34 |
|
| 35 |
@router.get("/dev", response_class=HTMLResponse)
|
| 36 |
async def dev_get(request: Request):
|
| 37 |
-
# Prefill a realistic plan request used by Matrix-Guardian
|
| 38 |
sample = {
|
| 39 |
"context": {
|
| 40 |
"entity_uid": "matrix-ai",
|
|
@@ -49,14 +47,14 @@ async def dev_get(request: Request):
|
|
| 49 |
|
| 50 |
@router.post("/dev", response_class=HTMLResponse)
|
| 51 |
async def dev_post(request: Request, payload: str = Form(...)):
|
| 52 |
-
base_url = _self_base_url()
|
| 53 |
try:
|
| 54 |
body = json.loads(payload)
|
| 55 |
except Exception as e:
|
| 56 |
return templates.TemplateResponse("dev.html", {"request": request, "sample": payload, "error": f"Invalid JSON: {e}"})
|
|
|
|
| 57 |
try:
|
| 58 |
-
async with httpx.AsyncClient(timeout=15.0) as client:
|
| 59 |
-
r = await client.post("/v1/plan",
|
| 60 |
r.raise_for_status()
|
| 61 |
data = r.json()
|
| 62 |
pretty = json.dumps(data, indent=2)
|
|
|
|
| 1 |
from fastapi import APIRouter, Request, Form
|
| 2 |
+
from fastapi.responses import HTMLResponse, RedirectResponse
|
| 3 |
from fastapi.templating import Jinja2Templates
|
| 4 |
import httpx, os, json
|
| 5 |
|
|
|
|
| 7 |
templates = Jinja2Templates(directory="app/templates")
|
| 8 |
|
| 9 |
def _self_base_url() -> str:
|
|
|
|
| 10 |
port = os.getenv("PORT", "7860")
|
| 11 |
return f"http://127.0.0.1:{port}"
|
| 12 |
|
| 13 |
+
@router.get("/", include_in_schema=False)
|
| 14 |
+
async def home_redirect():
|
| 15 |
+
# Default to the Chat page
|
| 16 |
+
return RedirectResponse(url="/chat", status_code=302)
|
| 17 |
|
| 18 |
@router.get("/chat", response_class=HTMLResponse)
|
| 19 |
async def chat_get(request: Request):
|
|
|
|
| 21 |
|
| 22 |
@router.post("/chat", response_class=HTMLResponse)
|
| 23 |
async def chat_post(request: Request, question: str = Form(...)):
|
|
|
|
|
|
|
| 24 |
try:
|
| 25 |
+
async with httpx.AsyncClient(base_url=_self_base_url(), timeout=15.0) as client:
|
| 26 |
+
r = await client.post("/v1/chat", json={"query": question})
|
| 27 |
+
r.raise_for_status()
|
| 28 |
data = r.json()
|
| 29 |
answer = data.get("answer", "(no answer)")
|
| 30 |
except Exception as e:
|
|
|
|
| 33 |
|
| 34 |
@router.get("/dev", response_class=HTMLResponse)
|
| 35 |
async def dev_get(request: Request):
|
|
|
|
| 36 |
sample = {
|
| 37 |
"context": {
|
| 38 |
"entity_uid": "matrix-ai",
|
|
|
|
| 47 |
|
| 48 |
@router.post("/dev", response_class=HTMLResponse)
|
| 49 |
async def dev_post(request: Request, payload: str = Form(...)):
|
|
|
|
| 50 |
try:
|
| 51 |
body = json.loads(payload)
|
| 52 |
except Exception as e:
|
| 53 |
return templates.TemplateResponse("dev.html", {"request": request, "sample": payload, "error": f"Invalid JSON: {e}"})
|
| 54 |
+
|
| 55 |
try:
|
| 56 |
+
async with httpx.AsyncClient(base_url=_self_base_url(), timeout=15.0) as client:
|
| 57 |
+
r = await client.post("/v1/plan", json=body)
|
| 58 |
r.raise_for_status()
|
| 59 |
data = r.json()
|
| 60 |
pretty = json.dumps(data, indent=2)
|