From 938cd319aab4a91cb10aeb3b48691b33d66ea192 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 12 Apr 2026 00:46:45 +0000 Subject: [PATCH] fix: address AI review feedback for disinto-chat (#705) --- .gitignore | 5 +++ docker/chat/Dockerfile | 10 ++--- .../chat/__pycache__/server.cpython-311.pyc | Bin 12365 -> 0 bytes docker/chat/server.py | 36 ++++-------------- docker/chat/ui/index.html | 20 ++++------ 5 files changed, 25 insertions(+), 46 deletions(-) delete mode 100644 docker/chat/__pycache__/server.cpython-311.pyc diff --git a/.gitignore b/.gitignore index cc722c2..2fd9aed 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,8 @@ docker/agents/bin/ # Generated docker-compose.yml (run 'bin/disinto init' to regenerate) # Note: This file is now committed to track volume mount configuration # docker-compose.yml + +# Python bytecode +__pycache__/ +*.pyc +*.pyo diff --git a/docker/chat/Dockerfile b/docker/chat/Dockerfile index f83f093..194cee4 100644 --- a/docker/chat/Dockerfile +++ b/docker/chat/Dockerfile @@ -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. -# Chosen for simplicity and small image size (~100MB vs ~150MB for Go). +# Small Debian slim base with Python runtime. +# Chosen for simplicity and small image size (~100MB). # # Image size: ~100MB (well under the 200MB ceiling) # @@ -10,11 +10,9 @@ 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 \ python3 \ - python3-pip \ - && pip3 install --break-system-packages websockets \ && rm -rf /var/lib/apt/lists/* # Non-root user diff --git a/docker/chat/__pycache__/server.cpython-311.pyc b/docker/chat/__pycache__/server.cpython-311.pyc deleted file mode 100644 index 54e1b449d2782a376cbb9f509f27c33ef9ceb150..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12365 zcmc&)Yit|Wm7XDo@1ZEl)YFzcie%ZeO;U~@vgKFwvaMK_9Y2z_;)bO)W0|rz)zm?_i%qI*5^T1>qQ+tuNYfYbdESDT_Y}<66kT#4%V3Q zBGt9d_P!>{85_njjF3w4y@Yu}*wI>G*)721QpagEgS^>0uk zbwWMgaD{x0h+k6o9=!e_pNWDovYa9RVB_>pp|fs2F2xgaGBkQQCUcS?UKK>{cXMxY zlkr4+GB&{-JA3vNcQH14MM&`6rKHI9O~j^ofg}0P4TjzB)5&RBkhe zgQWbalV_kM(g|#el%I;tBqZ*;qecfqp(zo*?7Q4dT)xar!>4hRf+WSpgzcOt$kQU^ z6$NQ3nUI8Hmu93~*GZM|9!JFZ@JrKBNZ=${6k^b!F>ZQlOpL)$Y{^%*yK_g-?LK=M zM#t#G#dsnnUgzQxCtRBnMrEi;PI4CoZZbKYz}O`*ImyXT;Br!uIhcXSv?N9*lB2PS z2o#PO^@QDD;|vZ5ExH@$;|!T5y{ud~aHiOF25jybLCKHkI*wvUcD#oYe@@(~_f>B8-f3dNoyp&9o zXK(>)jtP;e#8}F01jna@v6R;cOIOFXU7MUpu}1V-C_b7@1f5T8g#A)hV5JljhIUgn z2qqH{!J3%x_!Lqo4iw6a7M2YJ@&e+ULlC(~2^5GU3Pll!Aqy-#hIjMK8}x{cw?WL# z+krZG2T&*P1nS~lK;69c3>Ea~o<3M^x%o1=Cc<6vynwRVoX90&*Ztr!6TvMg4?&n~|A(K8hMI$dwPRrP2u`2m_yB zOL0TVG2~`R=0O0!`j%uN#&gL_m?DT`_zC@L%JLoVeOhNO#V3T}AfwyJlGFne$+0LO zlZB`ppA@37qF_1cR_VHgJcosYR-KWAiA%a2M9ySP)|r?%CP~;USf5WK5qWYdk{_L! zq<94ur4Z`s+#TVQAgXdaY>2b))OFp1E#{Vgs@^pCVbhCkP`iZFJ4b!x^eR;^EVMtY z*|uD>O|1!OH6ciV`{TL??f=qjgg=))ODWFNs`IqwJPiTk{sxC}c(gEr5VOG_8wMY7BP}G&2Yvv?}b8;D}%jWb@A5H zK95sQ~E^CvbUPKJYa z-75k7MDtbX9umC_{3eLH2VWUsNw>`)ufao%%Ytqf5~E382wKHj_z<1Ji0;VoL7tn$ z27KoxRg$iz>dXvMOpic5={k@(>Psj4(Al)?Y+BrH!QyL&TDVIwscf&-la9~QrX=a zyIaZMD^|!%N{CI+`ly*BaPA0ajYH}^$}9(9jte zhq`CwC=_bIFJ_L1ZW$NPl(sF_L#=CYTxm|=B~mYM%k`~x%{FYMCB+74qcZo}c}MxW zA+4;H-^sfw)3C<=VgF5=hd%v4OijLZ)@Nt7s5z_xNs zlqJ-0bk=xbv?^|f67T02-%hjuUJOH8DcjrmWE7OPRE?SCjkGSz{3#F+hyKRJ_WMEA z->&)F=j~Zv?d==$%!-|2s~2|O+CRU4u|2c-t;k{oSlVD{m2J`37KLrevNiKZNX|Ql zZyjFjRy`e>r(=b(xYm)(&C6`_BCoP-8r!C@ZN=;hyKfzwKe*Wc&dIkpHFi*82lIto-aEcqzD4WetEw}oIfF|6e#*M}5I*oPx^Aeo z)(vl~$a(fuZI1JZ4qRY8aVvaD%5G?H!{T;`i6J0ChAC$1HrPrvbvXCl+XlZRxV*#jyo_rrO`Qkhk0=y4j;k&(WW{m-MJCRyMNGmT6Rr zwUmw)e${wY{%yvW51F5TUVn_aZKGtaSXVnGw$CzaLXm}M(+nuys}#BOpkmK5H(^%6 zR41VsdWJIQG3ZTgB<3Hn8Ye+%GFjFf>ONsGi3+y%*Wbbzrr2|X9Ks9%lVP0}MR2-M zL0Web{RouruYU~*;szw>F?mvzSJ8%q#F%_J)ijXP;bJ^mzQqQ*xu9RSBqc+A!)-*j=__p`Jl~E=bL)4(6bo~j<^Glh2WWKO zHu8dy-(!|1NtKJNPYp`RW4WV5>SMQ!JcYUH>UIj*up8at%0Lxrp+IDa;s_3e>L zD~20r3^(?6LgaIgdtjgS^V-INUDnTc*&tkEKqDd$H2DN5m}InBbq0(mkv+WSZ!JYR zHw%(IZMjNGsE|Uw)z>Pe7g3^s1Lpjz9grYNz(r?9tnmU8RaR1Yz{EV!^Ura3B>DR0T(2|&e;VjhBut~J*KxV+P@HBiwq>$>SO9M~8# zp<;C^WT2v9{170Nir&>+DjyfY37ZtJheh=D45uvNuo#3;{70~ib=Q%kcrniN07C`s zIn`kHMJB<8D~AjZux=4AiU**u0h=hD#CNVp6Muo7aR>kdduHJFVA zvjIAZ#jLQT=O{4$8k^rflWF>K^nMgj2X3t)JnzkVd6(Hl(m2g0x=+>fSthZ|LsOyG={qQ3Lz6!2Wqx5v}8@Z;R&JQij%L zb^O6j)db$Gf9P*r_P1uhvfZxvxBt*SPtWgsRNs2<=G~i_(VxcuA^!JQ{_%>k^MqPI ztkn4M63Yizf|b{F6}hwSgCKTNCa0gXMN zum=h+D`UCJZqnFIO8(B{_d$ejgB0dNY2E*T1_F1Xc4(mN&V%>CJa+$bclD77^_i`< zzr*@j6N}*vrhlvBv#nMPhiD9kdk#b7i-`MZv-OL8jYk`-ziO~SxWws&XhN|2|LjjQ z-q%3fRj$)g35?FMf(s0B8(Ug+@nCnA%TeA2ZZPVPp>?^Fch=%U=NWv->Z{xa0yH;a zLpgqet--AE&4AWpWp*!b)@W>rFi2?=3YanYk_BXT6G5A`#$uI|3~U$}jl9*8wh>KY ztSNMXFqZSdZ-4L4`U zY%ZI9TwBerC4cCDzvq}Shg5iHPY00VeMs1E4EnT&+CsNeSrH>x656BME+Hf zB!>KxQOPUT(LT#Qg%g_6Y^C|fnr7LXEa3wZGU>hRQ$HYQ7f>t9d4zlWh;X68)45X;m?OXS2In$3vRB8UYFf-I68#rV#rOi~(y4XB z$^3B(Zc0pEjq?IOOx#Xl2V~NzRNY|WYHT9T7qS^Uy(#a&n6{ zcW@YXfTl0P2#>?#0T}GFUX|x7FhyV+3-)qx>~j&@=;i6e6`eKGj2YIQ`4Wa*f=*vzlH`eD zn(Tq(QpcZZ|TZ4n{#jOc6CCrK#R7n?U8@|;`t?qvZMEt6H4`Iu$bO_ zosev8Q?{XT#bXat{hk8yEt2{El^Uvrn;*_LZJ0m4Vt07Y(AkC!_dO3Awk$VnSqi8P zVXy}mY}xt^U~c%EvVq1Gi`5Ug0?$1R1eXKBrA`05{hzjfJfm(stZhB4242ttFDT@G z{HPurvovHj(^UmOxekuqtuJX?Us407wZLg5e}jJ!%HpVbv0p0zsycA{#tLQizCvd= zw%xz+aARb7V?^D!L)*Axfz7Vli0#0;z{ZDxj^#i{CjD@8_wwfM2VKA3r)=(4H=ols zpHl-bYk`-Qz{_Czdi{3>Zw>zN`0eBP@mT%qnbu`ro8oKBRtN6+?)oxTWz)#71Hah_ z54hFpSGDR_mFics)pZMYDALe$Z|3d{@K$|Ts}Ii~g9-NfpXTyaLgvlUoF~{Y3itl} z@Xro^(xvV^uI)UoMuxP=kjkFW*b@qS;^`*hU%eB3E4uWG+Pqt9-mS8GG5`W zbZvyH)?aZJ2>_&*_G!aoey$?1OhEWL<(Su9K76?c`0y00DtfHso;=b=qxkEToiF*V?wOPz4h9V6;G*s%_|yH_`Sc8(8-RviA&agE^^8 zjs9X+N8w+%5WY~){iX(hzjR+#Mui}EU9!wlvdOARm-x6#wLsXDHSHYoB|EfuY|UIR z=uC`k@)D0@f5^4^=vqmwOld8=?QQF`){~~6Wp5TVNjn^sbEN9d;2{mNV@Hnq5jU9_ zPH`k1RkQq2Hln;4ueoG`dqQ`MXn>|#VFwS+QN|}?7bgTfCwf_yr@AA?Nl`pI+D-hJ z!$GIc;1N%q!9$%QPK&`?XjqD9Ss9uDoTtJwB%m${aNrkCj8++qMq@k=01KJ~Ho!|b zf4R*xox$Bk@oymIzu})d2=!H6)7$+E7v_c^v2_bK-@T!-J2iIa+`uEorMSC4Itv8u zf9*G(pRfC@NnwUmW=La(AV6~M{)kuHyN&xl`u_bzg*mD+M>Xas1hS0lAydE1)T>OR z#xyGVo3J9TNbFNI8f*|#_-(|eDqsjd6;3;VFe{ci%a5>(Ju1jI4ULYo6_e*RJ|Gd)Di_{mPvS zw=OJpWVWc@ZJKx6oIC4npK}BBRBg$6p3kzIvR*jXQJ-ZSvh4aS+qB|$RasUjAdss; zw$LC)DpRFq%kRmnZ_ODJHapz};w(>UD2AO+En6EEYh#vi&h`KGkvERak>84yVwxbC H4DtU1OG3Bi diff --git a/docker/chat/server.py b/docker/chat/server.py index dbb6514..485fce9 100644 --- a/docker/chat/server.py +++ b/docker/chat/server.py @@ -165,35 +165,21 @@ class ChatHandler(BaseHTTPRequestHandler): return try: - # Spawn claude --print with streaming output - # Using stream-json format for structured parsing capability + # Spawn claude --print with text output format proc = subprocess.Popen( - [CLAUDE_BIN, "--print", message, "--output-format", "stream-json"], + [CLAUDE_BIN, "--print", message], stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=False, - bufsize=0, # Unbuffered for streaming + text=True, ) - # Read and stream response - response_parts = [] - 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 response as text (Claude outputs plain text when not using stream-json) + response = proc.stdout.read() # Read stderr (should be minimal, mostly for debugging) - if proc.stderr: - error_output = proc.stderr.read() - if error_output: - error_parts.append(error_output.decode("utf-8", errors="replace")) + error_output = proc.stderr.read() + if error_output: + print(f"Claude stderr: {error_output}", file=sys.stderr) # Wait for process to complete proc.wait() @@ -202,12 +188,6 @@ class ChatHandler(BaseHTTPRequestHandler): if proc.returncode != 0: self.send_error(500, f"Claude CLI failed with exit code {proc.returncode}") 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_header("Content-Type", "text/plain; charset=utf-8") self.send_header("Content-Length", len(response.encode("utf-8"))) diff --git a/docker/chat/ui/index.html b/docker/chat/ui/index.html index 35bcc12..d3851ce 100644 --- a/docker/chat/ui/index.html +++ b/docker/chat/ui/index.html @@ -132,7 +132,7 @@
Welcome to disinto-chat. Type a message to start chatting with Claude.
-
+
@@ -161,13 +161,6 @@ return div.innerHTML.replace(/\n/g, '
'); } - // 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 sendBtn.addEventListener('click', async () => { const message = textarea.value.trim(); @@ -183,13 +176,16 @@ textarea.value = ''; try { - // Use fetch for better control over streaming - const formData = new FormData(); - formData.append('message', message); + // Use fetch with URLSearchParams for application/x-www-form-urlencoded + const params = new URLSearchParams(); + params.append('message', message); const response = await fetch('/chat', { method: 'POST', - body: formData + headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: params }); if (!response.ok) {