claude-web-chat/templates/chat.html
djuka 3283888738
All checks were successful
Tests / unit-tests (push) Successful in 51s
Inicijalna implementacija Claude Web Chat (Faza 1 - CLI mod)
- Login sa session cookie autentifikacijom
- Lista projekata iz filesystem-a
- Chat sa Claude CLI preko WebSocket-a
- Streaming NDJSON parsiranje iz CLI stdout-a
- Sesija zivi nezavisno od browsera (reconnect replay)
- Sidebar sa .md fajlovima i markdown renderovanjem
- Dark tema, htmx + Go templates
- 47 unit testova

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 05:03:40 +00:00

113 lines
4.2 KiB
HTML

<!DOCTYPE html>
<html lang="sr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Claude Web Chat — {{.Project}}</title>
<link rel="stylesheet" href="/static/style.css">
<script src="/static/htmx.min.js"></script>
<script src="/static/ws.js"></script>
</head>
<body>
<div class="chat-container">
<!-- Sidebar -->
<div class="sidebar">
<div class="sidebar-header">
<h3>Fajlovi</h3>
<a href="/projects" style="font-size:0.85rem;">← Projekti</a>
</div>
<div class="sidebar-content" id="file-list">
{{range .Files}}
<a class="file-item" href="#" onclick="loadFile('{{.RelPath}}'); return false;">{{.Name}}</a>
{{end}}
{{if not .Files}}
<div style="padding: 1rem; color: var(--text-muted); font-size: 0.85rem;">
Nema .md fajlova
</div>
{{end}}
</div>
</div>
<!-- Chat main area -->
<div class="chat-main">
<div class="chat-header">
<h2>{{.Project}}</h2>
<span id="ws-status" class="status">Povezivanje...</span>
</div>
<div class="chat-messages" id="chat-messages">
<!-- Messages will be appended here via OOB swap -->
</div>
<div id="typing-indicator"></div>
<div class="chat-input-area" hx-ext="ws" ws-connect="/ws?project={{.Project}}&project_dir={{.ProjectDir}}">
<form class="chat-input-form" ws-send>
<textarea id="message-input" name="message" class="chat-input" placeholder="Pošalji poruku..." rows="1"></textarea>
<button type="submit" class="btn">Pošalji</button>
</form>
</div>
</div>
<!-- File viewer overlay (hidden by default) -->
<div id="file-viewer" class="file-viewer hidden">
<div class="file-viewer-header">
<h3 id="file-viewer-title"></h3>
<button class="btn" onclick="closeFileViewer()">Zatvori</button>
</div>
<div class="file-viewer-content" id="file-viewer-content">
</div>
</div>
</div>
<script>
// Auto-resize textarea
const textarea = document.getElementById('message-input');
if (textarea) {
textarea.addEventListener('input', function() {
this.style.height = 'auto';
this.style.height = Math.min(this.scrollHeight, 200) + 'px';
});
// Submit on Enter (Shift+Enter for newline)
textarea.addEventListener('keydown', function(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.closest('form').dispatchEvent(new Event('submit', { bubbles: true }));
}
});
}
// Auto-scroll chat
const chatMessages = document.getElementById('chat-messages');
const observer = new MutationObserver(function() {
chatMessages.scrollTop = chatMessages.scrollHeight;
});
observer.observe(chatMessages, { childList: true, subtree: true });
// File viewer
function loadFile(relPath) {
fetch('/api/file?project={{.Project}}&path=' + encodeURIComponent(relPath))
.then(r => r.json())
.then(data => {
document.getElementById('file-viewer-title').textContent = data.name;
document.getElementById('file-viewer-content').innerHTML = data.html;
document.getElementById('file-viewer').classList.remove('hidden');
})
.catch(err => console.error('Error loading file:', err));
}
function closeFileViewer() {
document.getElementById('file-viewer').classList.add('hidden');
}
// Close file viewer with Escape
document.addEventListener('keydown', function(e) {
if (e.key === 'Escape') {
closeFileViewer();
}
});
</script>
</body>
</html>