claude-web-chat/templates/chat.html
djuka 93dbb33198
All checks were successful
Tests / unit-tests (push) Successful in 8s
Ispravka dupliranja poruka i dodat Plan/Code mod
- Uklonjen --include-partial-messages (izazivao duple assistant evente)
- content_block_start preskače tool_use blokove (prazni divovi)
- Shift+Tab prebacuje između Code i Plan moda
- Plan mod šalje instrukciju da Claude samo planira bez izmena
- CSS za mode bar i plan poruke (plava boja, ⊞ prefix)

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

149 lines
6.0 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}}">
<div class="mode-bar">
<span class="mode-indicator" id="mode-indicator">
<span class="mode-label mode-active" id="mode-code" onclick="setMode('code')">Code</span>
<span class="mode-label" id="mode-plan" onclick="setMode('plan')">Plan</span>
</span>
<span class="mode-hint">Shift+Tab za promenu moda</span>
</div>
<form class="chat-input-form" ws-send>
<input type="hidden" name="mode" id="mode-input" value="code">
<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>
// Mode switching
let currentMode = 'code';
function setMode(mode) {
currentMode = mode;
document.getElementById('mode-input').value = mode;
const codeEl = document.getElementById('mode-code');
const planEl = document.getElementById('mode-plan');
const textarea = document.getElementById('message-input');
if (mode === 'plan') {
codeEl.classList.remove('mode-active');
planEl.classList.add('mode-active');
textarea.placeholder = 'Plan mod — opiši šta treba analizirati...';
} else {
planEl.classList.remove('mode-active');
codeEl.classList.add('mode-active');
textarea.placeholder = 'Pošalji poruku...';
}
textarea.focus();
}
// 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), Shift+Tab for mode switch
textarea.addEventListener('keydown', function(e) {
if (e.key === 'Tab' && e.shiftKey) {
e.preventDefault();
setMode(currentMode === 'code' ? 'plan' : 'code');
} else if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
this.closest('form').dispatchEvent(new Event('submit', { bubbles: true }));
}
});
}
// Global Shift+Tab handler (works even when textarea not focused)
document.addEventListener('keydown', function(e) {
if (e.key === 'Tab' && e.shiftKey) {
e.preventDefault();
setMode(currentMode === 'code' ? 'plan' : 'code');
}
if (e.key === 'Escape') {
closeFileViewer();
}
});
// 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');
}
</script>
</body>
</html>