All checks were successful
Tests / unit-tests (push) Successful in 8s
- 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>
149 lines
6.0 KiB
HTML
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>
|