fix: address AI review feedback for disinto-chat (#705)
This commit is contained in:
parent
eada673493
commit
938cd319aa
5 changed files with 25 additions and 46 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Binary file not shown.
|
|
@ -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:
|
||||||
error_parts.append(error_output.decode("utf-8", errors="replace"))
|
print(f"Claude stderr: {error_output}", file=sys.stderr)
|
||||||
|
|
||||||
# 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")))
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue