From 19190f90a655d6f4fb63561c1dc736c9828c0aae Mon Sep 17 00:00:00 2001 From: Alex Suprun Date: Wed, 19 Nov 2025 20:14:03 +0200 Subject: [PATCH 1/2] feat(chat): add automatic RTL direction detection - Add RTL character detection utility for Arabic, Hebrew scripts - Apply dir attribute to chat input field - Apply dir attribute to user and assistant messages - Automatically switch text direction when RTL characters detected --- src/components/ChatInterface.jsx | 22 +++++++++++++++++----- src/utils/rtlDetection.js | 24 ++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 5 deletions(-) create mode 100644 src/utils/rtlDetection.js diff --git a/src/components/ChatInterface.jsx b/src/components/ChatInterface.jsx index 0ced68576..10e96ff70 100644 --- a/src/components/ChatInterface.jsx +++ b/src/components/ChatInterface.jsx @@ -34,6 +34,7 @@ import { MicButton } from './MicButton.jsx'; import { api, authenticatedFetch } from '../utils/api'; import Fuse from 'fuse.js'; import CommandMenu from './CommandMenu'; +import { hasRTLCharacters } from '../utils/rtlDetection'; // Helper function to decode HTML entities in text @@ -91,13 +92,13 @@ function unescapeWithMathProtection(text) { } // Small wrapper to keep markdown behavior consistent in one place -const Markdown = ({ children, className }) => { +const Markdown = ({ children, className, dir }) => { const content = normalizeInlineCodeFences(String(children ?? '')); const remarkPlugins = useMemo(() => [remarkGfm, remarkMath], []); const rehypePlugins = useMemo(() => [rehypeKatex], []); return ( -
+
{ + if (!message?.content) return false; + return hasRTLCharacters(message.content); + }, [message?.content]); React.useEffect(() => { if (!autoExpandTools || !messageRef.current || !message.isToolUse) return; @@ -399,7 +406,7 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile /* User message bubble on the right */
-
+
{message.content}
{message.images && message.images.length > 0 && ( @@ -1573,11 +1580,11 @@ const MessageComponent = memo(({ message, index, prevMessage, createDiff, onFile // Normal rendering for non-JSON content return message.type === 'assistant' ? ( - + {content} ) : ( -
+
{content}
); @@ -1648,6 +1655,10 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess } return ''; }); + + // Detect RTL characters in input field + const isInputRTL = useMemo(() => hasRTLCharacters(input), [input]); + const [chatMessages, setChatMessages] = useState(() => { if (typeof window !== 'undefined' && selectedProject) { const saved = safeLocalStorage.getItem(`chat_messages_${selectedProject.name}`); @@ -4681,6 +4692,7 @@ function ChatInterface({ selectedProject, selectedSession, ws, sendMessage, mess }} placeholder={`Type / for commands, @ for files, or ask ${provider === 'cursor' ? 'Cursor' : 'Claude'} anything...`} disabled={isLoading} + dir={isInputRTL ? 'rtl' : 'ltr'} className="chat-input-placeholder block w-full pl-12 pr-20 sm:pr-40 py-1.5 sm:py-4 bg-transparent rounded-2xl focus:outline-none text-gray-900 dark:text-gray-100 placeholder-gray-400 dark:placeholder-gray-500 disabled:opacity-50 resize-none min-h-[50px] sm:min-h-[80px] max-h-[40vh] sm:max-h-[300px] overflow-y-auto text-sm sm:text-base leading-[21px] sm:leading-6 transition-all duration-200" style={{ height: '50px' }} /> diff --git a/src/utils/rtlDetection.js b/src/utils/rtlDetection.js new file mode 100644 index 000000000..4efb1e25e --- /dev/null +++ b/src/utils/rtlDetection.js @@ -0,0 +1,24 @@ +/** + * Detects if text contains at least one RTL (right-to-left) character + * Checks for Arabic, Hebrew, and other RTL script characters + * + * @param {string} text - The text to analyze + * @returns {boolean} - True if at least one RTL character is found + */ +export function hasRTLCharacters(text) { + if (!text || typeof text !== 'string') { + return false; + } + + // RTL Unicode ranges: + // Arabic: U+0600-U+06FF + // Hebrew: U+0590-U+05FF + // Arabic Supplement: U+0750-U+077F + // Arabic Extended-A: U+08A0-U+08FF + // Arabic Presentation Forms-A: U+FB50-U+FDFF + // Arabic Presentation Forms-B: U+FE70-U+FEFF + // Hebrew Presentation Forms: U+FB1D-U+FB4F + const rtlRegex = /[\u0590-\u05FF\u0600-\u06FF\u0750-\u077F\u08A0-\u08FF\uFB1D-\uFB4F\uFB50-\uFDFF\uFE70-\uFEFF]/; + + return rtlRegex.test(text); +} From bbd5d4ad1aa61058dde499aa57e59c2ae86f7e4f Mon Sep 17 00:00:00 2001 From: Alex Suprun Date: Wed, 19 Nov 2025 22:21:20 +0200 Subject: [PATCH 2/2] feat(scripts): add ccui control script for development servers Provides bash utility script for managing Claude Code UI development servers with commands for start, stop, restart, status, and logs monitoring. --- ccui.sh | 243 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100755 ccui.sh diff --git a/ccui.sh b/ccui.sh new file mode 100755 index 000000000..e30a2328d --- /dev/null +++ b/ccui.sh @@ -0,0 +1,243 @@ +#!/bin/bash + +# Claude Code UI Control Script +# Usage: ccui.sh [start|stop] + +PROJECT_DIR="/Users/alexsuprun/Documents/my-code/claudecodeui" +SERVER_PORT=3001 +CLIENT_PORT=5173 +LOG_FILE="$PROJECT_DIR/ccui.log" +PID_FILE="$PROJECT_DIR/ccui.pid" + +start() { + echo "๐Ÿš€ Starting Claude Code UI..." + + # Check if already running + if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if ps -p "$PID" > /dev/null 2>&1; then + echo "โš ๏ธ Claude Code UI is already running (PID: $PID)" + echo "๐Ÿ’ก Use 'ccui stop' to stop it first" + return 1 + else + # PID file exists but process is dead, clean it up + rm "$PID_FILE" + fi + fi + + # Check and kill processes on both ports + for PORT in $SERVER_PORT $CLIENT_PORT; do + PORT_PIDS=$(lsof -ti:$PORT 2>/dev/null) + if [ ! -z "$PORT_PIDS" ]; then + echo "โš ๏ธ Port $PORT is in use by processes: $PORT_PIDS" + echo "๐Ÿ”ง Killing processes on port $PORT..." + kill -9 $PORT_PIDS 2>/dev/null + sleep 1 + fi + done + + # Ensure .env exists + if [ ! -f "$PROJECT_DIR/.env" ]; then + echo "๐Ÿ“ Creating .env from .env.example..." + cp "$PROJECT_DIR/.env.example" "$PROJECT_DIR/.env" + fi + + # Start the dev server (both backend and frontend) in background + cd "$PROJECT_DIR" + echo "๐Ÿ“ฆ Starting development servers (backend + frontend)..." + nohup npm run dev > "$LOG_FILE" 2>&1 & + + # Save PID + echo $! > "$PID_FILE" + + echo "" + echo "โณ Waiting for servers to start..." + + # Wait for both server and client to be ready + MAX_WAIT=30 + WAIT_COUNT=0 + SERVER_READY=false + CLIENT_READY=false + + while [ $WAIT_COUNT -lt $MAX_WAIT ]; do + # Check server port + if ! $SERVER_READY && lsof -ti:$SERVER_PORT > /dev/null 2>&1; then + echo "โœ… Backend server ready on port $SERVER_PORT" + SERVER_READY=true + fi + + # Check client port + if ! $CLIENT_READY && lsof -ti:$CLIENT_PORT > /dev/null 2>&1; then + echo "โœ… Frontend client ready on port $CLIENT_PORT" + CLIENT_READY=true + fi + + # Break if both are ready + if $SERVER_READY && $CLIENT_READY; then + break + fi + + sleep 1 + WAIT_COUNT=$((WAIT_COUNT + 1)) + done + + echo "" + if $SERVER_READY && $CLIENT_READY; then + echo "โœ… Claude Code UI started successfully!" + echo "๐ŸŒ Server: http://localhost:$SERVER_PORT" + echo "๐ŸŽจ Client: http://localhost:$CLIENT_PORT" + echo "๐Ÿ“ Logs: $LOG_FILE" + echo "๐Ÿ’ก Use 'ccui stop' to stop all servers" + echo "" + + # Open browser + echo "๐ŸŒ Opening browser..." + if command -v open > /dev/null 2>&1; then + open "http://localhost:$CLIENT_PORT" + elif command -v xdg-open > /dev/null 2>&1; then + xdg-open "http://localhost:$CLIENT_PORT" + else + echo "โš ๏ธ Could not auto-open browser. Please open http://localhost:$CLIENT_PORT manually" + fi + + echo "" + echo "๐Ÿ“Š Recent logs:" + tail -n 10 "$LOG_FILE" + else + echo "โš ๏ธ Servers may not have started properly within $MAX_WAIT seconds" + echo "๐Ÿ“ Check logs for details: $LOG_FILE" + echo "๐Ÿ’ก Or run: ccui logs" + fi +} + +stop() { + echo "๐Ÿ›‘ Stopping Claude Code UI..." + + if [ ! -f "$PID_FILE" ]; then + echo "โš ๏ธ No PID file found" + + # Try to kill by ports + for PORT in $SERVER_PORT $CLIENT_PORT; do + PORT_PIDS=$(lsof -ti:$PORT 2>/dev/null) + if [ ! -z "$PORT_PIDS" ]; then + echo "๐Ÿ”ง Found processes on port $PORT: $PORT_PIDS" + kill -9 $PORT_PIDS 2>/dev/null + echo "โœ… Killed processes on port $PORT" + fi + done + return 0 + fi + + PID=$(cat "$PID_FILE") + + # Kill the main process and its children + if ps -p "$PID" > /dev/null 2>&1; then + echo "๐Ÿ”ง Killing process tree for PID $PID..." + + # Kill child processes first + pkill -P "$PID" 2>/dev/null + + # Kill main process + kill "$PID" 2>/dev/null + sleep 1 + + # Force kill if still running + if ps -p "$PID" > /dev/null 2>&1; then + echo "โšก Force killing process $PID..." + kill -9 "$PID" 2>/dev/null + fi + + echo "โœ… Process stopped" + else + echo "โ„น๏ธ Process $PID is not running" + fi + + # Clean up any remaining processes on both ports + for PORT in $SERVER_PORT $CLIENT_PORT; do + PORT_PIDS=$(lsof -ti:$PORT 2>/dev/null) + if [ ! -z "$PORT_PIDS" ]; then + echo "๐Ÿ”ง Cleaning up remaining processes on port $PORT..." + kill -9 $PORT_PIDS 2>/dev/null + fi + done + + # Remove PID file + rm -f "$PID_FILE" + echo "๐Ÿงน Cleaned up PID file" +} + +status() { + echo "๐Ÿ“Š Claude Code UI Status" + echo "โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”โ”" + + if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE") + if ps -p "$PID" > /dev/null 2>&1; then + echo "โœ… Running (PID: $PID)" + echo "๐ŸŒ Server: http://localhost:$SERVER_PORT" + echo "๐ŸŽจ Client: http://localhost:$CLIENT_PORT" + echo "๐Ÿ“ Logs: $LOG_FILE" + else + echo "โŒ Not running (stale PID file)" + fi + else + echo "โŒ Not running" + fi + + echo "" + echo "Port Status:" + + # Check server port + SERVER_PIDS=$(lsof -ti:$SERVER_PORT 2>/dev/null) + if [ ! -z "$SERVER_PIDS" ]; then + echo "๐Ÿ”Œ Server port $SERVER_PORT in use by: $SERVER_PIDS" + else + echo "๐Ÿ”Œ Server port $SERVER_PORT is free" + fi + + # Check client port + CLIENT_PIDS=$(lsof -ti:$CLIENT_PORT 2>/dev/null) + if [ ! -z "$CLIENT_PIDS" ]; then + echo "๐Ÿ”Œ Client port $CLIENT_PORT in use by: $CLIENT_PIDS" + else + echo "๐Ÿ”Œ Client port $CLIENT_PORT is free" + fi +} + +# Main command handler +COMMAND=${1:-start} + +case "$COMMAND" in + start) + start + ;; + stop) + stop + ;; + restart) + stop + sleep 2 + start + ;; + status) + status + ;; + logs) + if [ -f "$LOG_FILE" ]; then + tail -f "$LOG_FILE" + else + echo "โŒ No log file found at $LOG_FILE" + fi + ;; + *) + echo "Usage: ccui.sh [start|stop|restart|status|logs]" + echo "" + echo "Commands:" + echo " start - Start the server (default)" + echo " stop - Stop the server" + echo " restart - Restart the server" + echo " status - Check server status" + echo " logs - Follow server logs" + exit 1 + ;; +esac \ No newline at end of file