Initial deployment of Claude Code with Qwen local
Browse files- Dockerfile +51 -0
- app_claude_code_gui.py +81 -0
Dockerfile
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM python:3.12-slim
|
| 2 |
+
|
| 3 |
+
# Install system dependencies
|
| 4 |
+
RUN apt-get update && apt-get install -y \
|
| 5 |
+
git \
|
| 6 |
+
curl \
|
| 7 |
+
build-essential \
|
| 8 |
+
libopenblas-dev \
|
| 9 |
+
nodejs \
|
| 10 |
+
npm \
|
| 11 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 12 |
+
|
| 13 |
+
# Set working directory
|
| 14 |
+
WORKDIR /app
|
| 15 |
+
|
| 16 |
+
# Install llama-cpp-python with OpenBLAS and Streamlit
|
| 17 |
+
RUN pip install llama-cpp-python[server] streamlit
|
| 18 |
+
|
| 19 |
+
# Download Qwen2.5-Coder-7B-Instruct GGUF Q4_K_M
|
| 20 |
+
RUN mkdir -p /app/models
|
| 21 |
+
RUN curl -L -o /app/models/qwen2.5-coder-7b-instruct-q4_k_m.gguf https://huggingface.co/Qwen/Qwen2.5-Coder-7B-Instruct-GGUF/resolve/main/qwen2.5-coder-7b-instruct-q4_k_m.gguf
|
| 22 |
+
|
| 23 |
+
# Install Claude Code globally
|
| 24 |
+
RUN npm install -g @anthropic-ai/claude-code
|
| 25 |
+
|
| 26 |
+
# Create a dummy config.toml for OpenManus if needed, or just for structure
|
| 27 |
+
RUN mkdir -p /app/config
|
| 28 |
+
RUN echo '[llm]\nmodel = "qwen2.5-coder"\nbase_url = "http://localhost:8001/v1"\napi_key = "not-needed"\nmax_tokens = 4096\ntemperature = 0.0\n' > /app/config/config.toml
|
| 29 |
+
|
| 30 |
+
# Copy our custom GUI for Claude Code
|
| 31 |
+
COPY app_claude_code_gui.py /app/app_claude_code_gui.py
|
| 32 |
+
|
| 33 |
+
# Create a startup script
|
| 34 |
+
RUN echo '#!/bin/bash\n\
|
| 35 |
+
# Start llama-cpp-python server in background\n\
|
| 36 |
+
python3 -m llama_cpp.server --model /app/models/qwen2.5-coder-7b-instruct-q4_k_m.gguf --host 0.0.0.0 --port 8001 &\n\
|
| 37 |
+
\n\
|
| 38 |
+
# Wait for server to start\n\
|
| 39 |
+
until curl -s http://localhost:8001/v1/models > /dev/null; do\n\
|
| 40 |
+
echo "Waiting for LLM server..."\n\
|
| 41 |
+
sleep 5\n\
|
| 42 |
+
done\n\
|
| 43 |
+
\n\
|
| 44 |
+
# Start Streamlit UI on port 7860\n\
|
| 45 |
+
streamlit run app_claude_code_gui.py --server.port 7860 --server.address 0.0.0.0\n\
|
| 46 |
+
' > /app/start.sh && chmod +x /app/start.sh
|
| 47 |
+
|
| 48 |
+
# HF Spaces expect a web service on port 7860
|
| 49 |
+
EXPOSE 7860
|
| 50 |
+
|
| 51 |
+
CMD ["/app/start.sh"]
|
app_claude_code_gui.py
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import streamlit as st
|
| 2 |
+
import subprocess
|
| 3 |
+
import threading
|
| 4 |
+
import queue
|
| 5 |
+
import os
|
| 6 |
+
import sys
|
| 7 |
+
|
| 8 |
+
st.set_page_config(page_title="Claude Code Web UI", page_icon="💻")
|
| 9 |
+
|
| 10 |
+
st.title("💻 Claude Code Web UI")
|
| 11 |
+
st.markdown("Interface pour Claude Code avec Qwen2.5-Coder-7B (GGUF) local")
|
| 12 |
+
|
| 13 |
+
if "messages" not in st.session_state:
|
| 14 |
+
st.session_state.messages = []
|
| 15 |
+
|
| 16 |
+
for message in st.session_state.messages:
|
| 17 |
+
with st.chat_message(message["role"]):
|
| 18 |
+
st.markdown(message["content"])
|
| 19 |
+
|
| 20 |
+
def run_command(command, output_queue):
|
| 21 |
+
try:
|
| 22 |
+
# Ensure claude-code uses the local LLM server
|
| 23 |
+
# This might require setting environment variables or a config file for claude-code
|
| 24 |
+
# For now, we assume claude-code can be configured to use an OpenAI-compatible endpoint
|
| 25 |
+
# or we might need to modify its source/config if it's hardcoded to Anthropic API.
|
| 26 |
+
# Let's try to pass the API base URL as an environment variable if claude-code supports it.
|
| 27 |
+
# If not, this part will need further investigation.
|
| 28 |
+
|
| 29 |
+
# For demonstration, we'll just run a simple command or simulate claude-code interaction.
|
| 30 |
+
# A real integration would involve piping input/output to the claude-code CLI.
|
| 31 |
+
|
| 32 |
+
# Example: running a simple shell command for now
|
| 33 |
+
process = subprocess.Popen(
|
| 34 |
+
command,
|
| 35 |
+
shell=True,
|
| 36 |
+
stdout=subprocess.PIPE,
|
| 37 |
+
stderr=subprocess.STDOUT,
|
| 38 |
+
text=True,
|
| 39 |
+
bufsize=1
|
| 40 |
+
)
|
| 41 |
+
for line in iter(process.stdout.readline, ""):
|
| 42 |
+
output_queue.put(line)
|
| 43 |
+
process.stdout.close()
|
| 44 |
+
process.wait()
|
| 45 |
+
output_queue.put(None)
|
| 46 |
+
except Exception as e:
|
| 47 |
+
output_queue.put(f"Error executing command: {e}\n")
|
| 48 |
+
output_queue.put(None)
|
| 49 |
+
|
| 50 |
+
if prompt := st.chat_input("Entrez votre commande Claude Code ou shell..."):
|
| 51 |
+
st.session_state.messages.append({"role": "user", "content": prompt})
|
| 52 |
+
with st.chat_message("user"):
|
| 53 |
+
st.markdown(prompt)
|
| 54 |
+
|
| 55 |
+
with st.chat_message("assistant"):
|
| 56 |
+
response_placeholder = st.empty()
|
| 57 |
+
full_response = ""
|
| 58 |
+
output_queue = queue.Queue()
|
| 59 |
+
|
| 60 |
+
# Prepend 'claude-code' if the user just types a command, or allow direct shell commands
|
| 61 |
+
command_to_run = prompt
|
| 62 |
+
if not prompt.startswith("claude-code") and not prompt.startswith("npm") and not prompt.startswith("python") and not prompt.startswith("ls"):
|
| 63 |
+
command_to_run = f"claude-code {prompt}"
|
| 64 |
+
|
| 65 |
+
thread = threading.Thread(target=run_command, args=([command_to_run], output_queue))
|
| 66 |
+
thread.start()
|
| 67 |
+
|
| 68 |
+
while True:
|
| 69 |
+
try:
|
| 70 |
+
line = output_queue.get(timeout=1)
|
| 71 |
+
if line is None:
|
| 72 |
+
break
|
| 73 |
+
full_response += line
|
| 74 |
+
response_placeholder.markdown(f"```bash\n{full_response}▌\n```")
|
| 75 |
+
except queue.Empty:
|
| 76 |
+
if not thread.is_alive():
|
| 77 |
+
break
|
| 78 |
+
continue
|
| 79 |
+
|
| 80 |
+
response_placeholder.markdown(f"```bash\n{full_response}\n```")
|
| 81 |
+
st.session_state.messages.append({"role": "assistant", "content": full_response})
|