fix: address AI review feedback for disinto-chat (#705)
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
ci/woodpecker/pr/smoke-init Pipeline was successful

This commit is contained in:
Claude 2026-04-12 00:46:45 +00:00
parent eada673493
commit 938cd319aa
5 changed files with 25 additions and 46 deletions

5
.gitignore vendored
View file

@ -32,3 +32,8 @@ docker/agents/bin/
# Generated docker-compose.yml (run 'bin/disinto init' to regenerate) # Generated docker-compose.yml (run 'bin/disinto init' to regenerate)
# Note: This file is now committed to track volume mount configuration # Note: This file is now committed to track volume mount configuration
# docker-compose.yml # docker-compose.yml
# Python bytecode
__pycache__/
*.pyc
*.pyo

View file

@ -1,7 +1,7 @@
# disinto-chat — minimal HTTP+WebSocket backend for Claude chat UI # disinto-chat — minimal HTTP backend for Claude chat UI
# #
# Small Debian slim base with Python runtime and websockets library. # Small Debian slim base with Python runtime.
# Chosen for simplicity and small image size (~100MB vs ~150MB for Go). # Chosen for simplicity and small image size (~100MB).
# #
# Image size: ~100MB (well under the 200MB ceiling) # Image size: ~100MB (well under the 200MB ceiling)
# #
@ -10,11 +10,9 @@
FROM debian:bookworm-slim FROM debian:bookworm-slim
# Install Python and websockets (no build-time network access needed) # Install Python (no build-time network access needed)
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
python3 \ python3 \
python3-pip \
&& pip3 install --break-system-packages websockets \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Non-root user # Non-root user

View file

@ -165,35 +165,21 @@ class ChatHandler(BaseHTTPRequestHandler):
return return
try: try:
# Spawn claude --print with streaming output # Spawn claude --print with text output format
# Using stream-json format for structured parsing capability
proc = subprocess.Popen( proc = subprocess.Popen(
[CLAUDE_BIN, "--print", message, "--output-format", "stream-json"], [CLAUDE_BIN, "--print", message],
stdout=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, stderr=subprocess.PIPE,
text=False, text=True,
bufsize=0, # Unbuffered for streaming
) )
# Read and stream response # Read response as text (Claude outputs plain text when not using stream-json)
response_parts = [] response = proc.stdout.read()
error_parts = []
# Read stdout in chunks
while True:
chunk = proc.stdout.read(4096)
if not chunk:
break
try:
response_parts.append(chunk.decode("utf-8"))
except UnicodeDecodeError:
response_parts.append(chunk.decode("utf-8", errors="replace"))
# Read stderr (should be minimal, mostly for debugging) # Read stderr (should be minimal, mostly for debugging)
if proc.stderr: error_output = proc.stderr.read()
error_output = proc.stderr.read() if error_output:
if error_output: print(f"Claude stderr: {error_output}", file=sys.stderr)
error_parts.append(error_output.decode("utf-8", errors="replace"))
# Wait for process to complete # Wait for process to complete
proc.wait() proc.wait()
@ -202,12 +188,6 @@ class ChatHandler(BaseHTTPRequestHandler):
if proc.returncode != 0: if proc.returncode != 0:
self.send_error(500, f"Claude CLI failed with exit code {proc.returncode}") self.send_error(500, f"Claude CLI failed with exit code {proc.returncode}")
return return
# Combine response parts
response = "".join(response_parts)
# If using stream-json, we could parse and reformat here.
# For now, return as-is (HTMX will display it in the UI).
self.send_response(200) self.send_response(200)
self.send_header("Content-Type", "text/plain; charset=utf-8") self.send_header("Content-Type", "text/plain; charset=utf-8")
self.send_header("Content-Length", len(response.encode("utf-8"))) self.send_header("Content-Length", len(response.encode("utf-8")))

View file

@ -132,7 +132,7 @@
<div class="content">Welcome to disinto-chat. Type a message to start chatting with Claude.</div> <div class="content">Welcome to disinto-chat. Type a message to start chatting with Claude.</div>
</div> </div>
</div> </div>
<form class="input-area" hx-post="/chat" hx-swap="none" hx-target="#messages"> <form class="input-area">
<textarea name="message" placeholder="Type your message..." required></textarea> <textarea name="message" placeholder="Type your message..." required></textarea>
<button type="submit" id="send-btn">Send</button> <button type="submit" id="send-btn">Send</button>
</form> </form>
@ -161,13 +161,6 @@
return div.innerHTML.replace(/\n/g, '<br>'); return div.innerHTML.replace(/\n/g, '<br>');
} }
// Handle HTMX swap for streaming responses
document.body.addEventListener('htmx:afterSwap', function(event) {
const newContent = event.detail.xhr.responseText;
// HTMX will handle the swap; we just need to scroll to bottom
messagesDiv.scrollTop = messagesDiv.scrollHeight;
});
// Send message handler // Send message handler
sendBtn.addEventListener('click', async () => { sendBtn.addEventListener('click', async () => {
const message = textarea.value.trim(); const message = textarea.value.trim();
@ -183,13 +176,16 @@
textarea.value = ''; textarea.value = '';
try { try {
// Use fetch for better control over streaming // Use fetch with URLSearchParams for application/x-www-form-urlencoded
const formData = new FormData(); const params = new URLSearchParams();
formData.append('message', message); params.append('message', message);
const response = await fetch('/chat', { const response = await fetch('/chat', {
method: 'POST', method: 'POST',
body: formData headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
body: params
}); });
if (!response.ok) { if (!response.ok) {