diff --git a/.gitignore b/.gitignore index fec312fd..4388b995 100644 --- a/.gitignore +++ b/.gitignore @@ -160,4 +160,5 @@ cython_debug/ .idea/ notes.md -data/ \ No newline at end of file +data/ +.vscode \ No newline at end of file diff --git a/devika.dockerfile b/devika.dockerfile index 191cd91c..cc9155b5 100644 --- a/devika.dockerfile +++ b/devika.dockerfile @@ -12,14 +12,14 @@ ENV PYTHONDONTWRITEBYTECODE 1 RUN apt-get update && apt-get upgrade -y RUN apt-get install -y build-essential software-properties-common curl sudo wget git RUN apt-get install -y python3 python3-pip -RUN curl -fsSL https://astral.sh/uv/install.sh | sudo -E bash - -RUN $HOME/.cargo/bin/uv venv -ENV PATH="/home/nonroot/devika/.venv/bin:$HOME/.cargo/bin:$PATH" +RUN curl -LsSf https://astral.sh/uv/install.sh | sh +RUN $HOME/.local/bin/uv venv +ENV PATH="/home/nonroot/devika/.venv/bin:$HOME/.local/bin:$PATH" # copy devika python engine only -RUN $HOME/.cargo/bin/uv venv +RUN $HOME/.local/bin/uv venv COPY requirements.txt /home/nonroot/devika/ -RUN UV_HTTP_TIMEOUT=100000 $HOME/.cargo/bin/uv pip install -r requirements.txt +RUN UV_HTTP_TIMEOUT=100000 $HOME/.local/bin/uv pip install -r requirements.txt RUN playwright install-deps chromium RUN playwright install chromium @@ -32,7 +32,7 @@ RUN chown -R nonroot:nonroot /home/nonroot/devika USER nonroot WORKDIR /home/nonroot/devika -ENV PATH="/home/nonroot/devika/.venv/bin:$HOME/.cargo/bin:$PATH" +ENV PATH="/home/nonroot/devika/.venv/bin:$HOME/.local/bin:$PATH" RUN mkdir /home/nonroot/devika/db -ENTRYPOINT [ "python3", "-m", "devika" ] +ENTRYPOINT [ "python3", "-m", "devika" ] \ No newline at end of file diff --git a/devika.py b/devika.py index 961b792a..d239b8f7 100644 --- a/devika.py +++ b/devika.py @@ -206,4 +206,4 @@ def status(): if __name__ == "__main__": logger.info("Devika is up and running!") - socketio.run(app, debug=False, port=1337, host="0.0.0.0") + socketio.run(app, debug=True, port=1337, host="0.0.0.0") diff --git a/docker-compose.yaml b/docker-compose.yaml index cb7d06cb..eae6b588 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,18 +1,14 @@ version: "3.9" services: - ollama-service: - image: ollama/ollama:latest - expose: - - 11434 + nginx: + image: nginx:latest + restart: always + volumes: + - ./nginx/nginx.conf:/etc/nginx/nginx.conf + - /home/nonroot/devika/data:/var/www/html ports: - - 11434:11434 - healthcheck: - test: ["CMD-SHELL", "curl -f http://localhost:11434/ || exit 1"] - interval: 5s - timeout: 30s - retries: 5 - start_period: 30s + - 80:80 networks: - devika-subnetwork @@ -20,8 +16,6 @@ services: build: context: . dockerfile: devika.dockerfile - depends_on: - - ollama-service expose: - 1337 ports: @@ -36,6 +30,7 @@ services: start_period: 30s volumes: - devika-backend-dbstore:/home/nonroot/devika/db + - /home/nonroot/devika/data:/home/nonroot/devika/data networks: - devika-subnetwork @@ -44,9 +39,11 @@ services: context: . dockerfile: app.dockerfile args: - - VITE_API_BASE_URL=http://127.0.0.1:1337 + - VITE_API_BASE_URL=http://47.121.183.184:1337 depends_on: - devika-backend-engine + environment: + - VITE_API_BASE_URL=http://47.121.183.184:1337 expose: - 3000 ports: @@ -58,4 +55,4 @@ networks: devika-subnetwork: volumes: - devika-backend-dbstore: \ No newline at end of file + devika-backend-dbstore: diff --git a/nginx/nginx.conf b/nginx/nginx.conf new file mode 100644 index 00000000..9c974bc9 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,33 @@ +events {} +http { + + + #keepalive_timeout 0; + keepalive_timeout 65; + upstream backend { + server devika-backend-engine:1337; # a 服务的 Docker Compose 服务名 + } + server { + listen 80; + server_name _; + location /projects { + root /var/www/html; # 根目录 + index index.html; + } + location / { + proxy_pass http://devika-frontend-app:3000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /api { + rewrite ^/api/(.*)$ /$1 break; + proxy_pass http://backend/api; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} + +} diff --git a/sample.config.toml b/sample.config.toml index 62cde107..878dc736 100644 --- a/sample.config.toml +++ b/sample.config.toml @@ -16,6 +16,7 @@ GEMINI = "" MISTRAL = "" GROQ = "" NETLIFY = "" +KIMI = "" [API_ENDPOINTS] BING = "https://api.bing.microsoft.com/v7.0/search" @@ -31,4 +32,4 @@ LOG_REST_API = "true" LOG_PROMPTS = "false" [TIMEOUT] -INFERENCE = 60 \ No newline at end of file +INFERENCE = 6000000 \ No newline at end of file diff --git a/src/agents/coder/prompt.jinja2 b/src/agents/coder/prompt.jinja2 index c6336a03..4a7191c6 100644 --- a/src/agents/coder/prompt.jinja2 +++ b/src/agents/coder/prompt.jinja2 @@ -28,24 +28,24 @@ Read the step-by-step plan carefully. Think step-by-step. Learn relevant informa Your response should only be in the following Markdown format: ~~~ -File: `main.py`: -```py +File: main.html: +```html print("Example") ``` -File: `src/main.rs`: +File: src/main.rs: ```rs fn main() { println!("Example"); } ``` -File: `nested/directory/example/code.py`: +File: nested/directory/example/code.py: ```py print("Example") ``` -File: `README.md` +File: README.md ```md # Example diff --git a/src/agents/planner/planner.py b/src/agents/planner/planner.py index 3f241773..5234ba28 100644 --- a/src/agents/planner/planner.py +++ b/src/agents/planner/planner.py @@ -50,7 +50,7 @@ def parse_response(self, response: str): elif current_section == "focus": result["focus"] += " " + line elif current_section == "plans": - if line.startswith("- [ ] Step"): + if line.find('Step') != -1: current_step = line.split(":")[0].strip().split(" ")[-1] result["plans"][int(current_step)] = line.split(":", 1)[1].strip() elif current_step: diff --git a/src/browser/search.py b/src/browser/search.py index 030fa5fe..e56d6fb2 100644 --- a/src/browser/search.py +++ b/src/browser/search.py @@ -1,3 +1,6 @@ +from lxml import etree + +from duckduckgo_search import DDGS import requests from src.config import Config @@ -62,6 +65,7 @@ def get_first_link(self): print(error) return "" + # class DuckDuckGoSearch: # def __init__(self): # self.query_result = None @@ -90,6 +94,7 @@ class DuckDuckGoSearch: currently, the package is not working with our current setup. """ + def __init__(self): from curl_cffi import requests as curl_requests self.query_result = None @@ -98,37 +103,24 @@ def __init__(self): def _get_url(self, method, url, data): try: - resp = self.asession.request(method, url, data=data) - if resp.status_code == 200: - return resp.content - if resp.status_code == (202, 301, 403): - raise Exception(f"Error: {resp.status_code} rate limit error") - if not resp: - return None + return None except Exception as error: if "timeout" in str(error).lower(): raise TimeoutError("Duckduckgo timed out error") def duck(self, query): - resp = self._get_url("POST", "https://duckduckgo.com/", data={"q": query}) - vqd = self.extract_vqd(resp) - - params = {"q": query, "kl": 'en-us', "p": "1", "s": "0", "df": "", "vqd": vqd, "ex": ""} - resp = self._get_url("GET", "https://links.duckduckgo.com/d.js", params) - page_data = self.text_extract_json(resp) - results = [] - for row in page_data: - href = row.get("u") - if href and href != f"http://www.google.com/search?q={query}": - body = self.normalize(row["a"]) - if body: - result = { - "title": self.normalize(row["t"]), - "href": self.normalize_url(href), - "body": self.normalize(row["a"]), - } - results.append(result) + with DDGS(timeout=20, proxies=None) as ddgs: + for r in ddgs.text(keywords=query, max_results=3, backend="html"): + # print(r) + if r is not None: + r['content'] = get_html_content(r['href']) + if r['content']: + results.append({ + "title": self.normalize(r['title']), + "href": self.normalize_url(r['href']), + "body": r['content'], + }) self.query_result = results @@ -136,7 +128,9 @@ def search(self, query): self.duck(query) def get_first_link(self): - return self.query_result[0]["href"] + if len(self.query_result) > 0: + return self.query_result[0]["href"] + return "" @staticmethod def extract_vqd(html_bytes: bytes) -> str: @@ -165,3 +159,51 @@ def normalize_url(url: str) -> str: @staticmethod def normalize(raw_html: str) -> str: return unescape(re.sub("<.*?>", "", raw_html)) if raw_html else "" + + +def get_html_content(url): + # 发送HTTP请求获取网页源代码 + if url is None: + return "url为空!" + try: + header = { + 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36', + 'cookie': '__yjs_duid=1_b1ac9fc87dce4de5552d7cf0924fb4981686228951567; u=b0281776fd75d3eefeb3562b2a5e6534; ' + '__bid_n=1889b14047a51b2b754207; ' + 'FPTOKEN=qU+ieOMqkW6y6DlsOZ+D/T' + '+SCY6yS3dYvGXKibFoGBijKuUuSbc3ACFDzjlcC18wuDjNLENrw4ktAFAqnl3Akg492Lr4fbvNrkdJ' + '/ZQrluIdklkNDAKYnPrpcbe2H9y7AtX+/b+FCTkSTNv5+qB3OtQQ3BXXsEen72oEoAfK+H6' + '/u6ltZPdyHttJBJiXEDDS3EiUVt+S2w+8ozXENWbNt/AHeCgNUMmdeDinAKCR+nQSGK/twOoTLOU/nxBeSAazg' + '+wu5K8ooRmW00Bk6XAqC4Cb829XR3UinZHRsJxt7q9biKzYQh' + '+Yu5s6EHypKwpA6RPtVAC1axxbxza0l5LJ5hX8IxJXDaQ6srFoEzQ92jM0rmDynp+gT' + '+3qNfEtB2PjkURvmRghGUn8wOcUUKPOqg==|mfg5DyAulnBuIm/fNO5JCrEm9g5yXrV1etiaV0jqQEw=|10' + '|dcfdbf664758c47995de31b90def5ca5; PHPSESSID=18397defd82b1b3ef009662dc77fe210; ' + 'Hm_lvt_de3f6fd28ec4ac19170f18e2a8777593=1686322028,1686360205; ' + 'history=cid%3A2455%2Ccid%3A2476%2Ccid%3A5474%2Ccid%3A5475%2Ccid%3A2814%2Cbid%3A3667; ' + 'Hm_lpvt_de3f6fd28ec4ac19170f18e2a8777593=1686360427'} + response = requests.get(url, header) + response.encoding = response.apparent_encoding + html_content = response.text + + # 使用lxml解析网页源代码 + tree = etree.HTML(html_content) + + # 使用XPath选择器提取纯文本内容 + # 这里假设要提取的内容位于

标签内,你可以根据实际情况调整XPath表达式 + content = get_text(tree, url, '//p/text()') + + if (len(content) == 0): + content = get_text(tree, url, '//body/text()') + return content + except BaseException as e: + print(e) + return None + + +def get_text(tree, url, regex): + paragraphs = tree.xpath(regex) + content = [] + # 打印提取的文本内容 + for paragraph in paragraphs: + content.append(paragraph) + return content diff --git a/src/config.py b/src/config.py index a3303118..f2b3c511 100644 --- a/src/config.py +++ b/src/config.py @@ -47,6 +47,10 @@ def get_bing_api_endpoint(self): def get_bing_api_key(self): return self.config["API_KEYS"]["BING"] + + def get_kimi_api_key(self): + return self.config["API_KEYS"]["KIMI"] + def get_google_search_api_key(self): return self.config["API_KEYS"]["GOOGLE_SEARCH"] @@ -109,7 +113,7 @@ def get_logging_prompts(self): return self.config["LOGGING"]["LOG_PROMPTS"] == "true" def get_timeout_inference(self): - return self.config["TIMEOUT"]["INFERENCE"] + return 6000000000 def set_bing_api_key(self, key): self.config["API_KEYS"]["BING"] = key diff --git a/src/init.py b/src/init.py index abb8b95b..89953982 100644 --- a/src/init.py +++ b/src/init.py @@ -26,6 +26,7 @@ def init_devika(): from src.bert.sentence import SentenceBert + # using llm instead logger.info("Loading sentence-transformer BERT models...") prompt = "Light-weight keyword extraction exercise for BERT model loading.".strip() SentenceBert(prompt).extract_keywords() diff --git a/src/llm/china_client.py b/src/llm/china_client.py new file mode 100644 index 00000000..7fb0d35f --- /dev/null +++ b/src/llm/china_client.py @@ -0,0 +1,21 @@ +from openai import OpenAI + +from src.config import Config + + +class China: + def __init__(self): + config = Config() + api_key = config.get_kimi_api_key() + self.client = OpenAI( + api_key=api_key, + base_url="https://api.moonshot.cn/v1", + ) + + def inference(self, model_id: str, prompt: str) -> str: + result = self.client.chat.completions.create( + model=model_id, + messages=[{"role": "user", "content": prompt}], + temperature=0.3, + ) + return result.choices[0].message.content diff --git a/src/llm/llm.py b/src/llm/llm.py index 24f20d59..d6656b8a 100644 --- a/src/llm/llm.py +++ b/src/llm/llm.py @@ -3,6 +3,7 @@ import tiktoken from typing import List, Tuple +from .china_client import China from src.socket_instance import emit_agent from .ollama_client import Ollama from .claude_client import Claude @@ -31,44 +32,51 @@ def __init__(self, model_id: str = None): self.log_prompts = config.get_logging_prompts() self.timeout_inference = config.get_timeout_inference() self.models = { - "CLAUDE": [ - ("Claude 3 Opus", "claude-3-opus-20240229"), - ("Claude 3 Sonnet", "claude-3-sonnet-20240229"), - ("Claude 3 Haiku", "claude-3-haiku-20240307"), - ], - "OPENAI": [ - ("GPT-4o-mini", "gpt-4o-mini"), - ("GPT-4o", "gpt-4o"), - ("GPT-4 Turbo", "gpt-4-turbo"), - ("GPT-3.5 Turbo", "gpt-3.5-turbo-0125"), - ], - "GOOGLE": [ - ("Gemini 1.0 Pro", "gemini-pro"), - ("Gemini 1.5 Flash", "gemini-1.5-flash"), - ("Gemini 1.5 Pro", "gemini-1.5-pro"), - ], - "MISTRAL": [ - ("Mistral 7b", "open-mistral-7b"), - ("Mistral 8x7b", "open-mixtral-8x7b"), - ("Mistral Medium", "mistral-medium-latest"), - ("Mistral Small", "mistral-small-latest"), - ("Mistral Large", "mistral-large-latest"), - ], - "GROQ": [ - ("LLAMA3 8B", "llama3-8b-8192"), - ("LLAMA3 70B", "llama3-70b-8192"), - ("LLAMA2 70B", "llama2-70b-4096"), - ("Mixtral", "mixtral-8x7b-32768"), - ("GEMMA 7B", "gemma-7b-it"), - ], + # "CLAUDE": [ + # ("Claude 3 Opus", "claude-3-opus-20240229"), + # ("Claude 3 Sonnet", "claude-3-sonnet-20240229"), + # ("Claude 3 Haiku", "claude-3-haiku-20240307"), + # ], + # "OPENAI": [ + # ("GPT-4o-mini", "gpt-4o-mini"), + # ("GPT-4o", "gpt-4o"), + # ("GPT-4 Turbo", "gpt-4-turbo"), + # ("GPT-3.5 Turbo", "gpt-3.5-turbo-0125"), + # ], + # "GOOGLE": [ + # ("Gemini 1.0 Pro", "gemini-pro"), + # ("Gemini 1.5 Flash", "gemini-1.5-flash"), + # ("Gemini 1.5 Pro", "gemini-1.5-pro"), + # ], + # "MISTRAL": [ + # ("Mistral 7b", "open-mistral-7b"), + # ("Mistral 8x7b", "open-mixtral-8x7b"), + # ("Mistral Medium", "mistral-medium-latest"), + # ("Mistral Small", "mistral-small-latest"), + # ("Mistral Large", "mistral-large-latest"), + # ], + # "GROQ": [ + # ("LLAMA3 8B", "llama3-8b-8192"), + # ("LLAMA3 70B", "llama3-70b-8192"), + # ("LLAMA2 70B", "llama2-70b-4096"), + # ("Mixtral", "mixtral-8x7b-32768"), + # ("GEMMA 7B", "gemma-7b-it"), + # ], "OLLAMA": [], + "国内大模型": [ + ("kimi moonshot-v1-8k", "moonshot-v1-8k"), + ("kimi moonshot-v1-32k", "moonshot-v1-32k"), + # ("kimi kimi-latest-8k", "kimi-latest-8k"), + # ("kimi kimi-latest-32k", "kimi-latest-32k"), + # ("kimi kimi-latest-128k", "kimi-latest-128k"), + ], "LM_STUDIO": [ ("LM Studio", "local-model"), ], } if ollama.client: - self.models["OLLAMA"] = [(model["name"], model["name"]) for model in ollama.models] + self.models["OLLAMA"] = [(model["model"], model["model"]) for model in ollama.models] def list_models(self) -> dict: return self.models @@ -100,6 +108,7 @@ def inference(self, prompt: str, project_name: str) -> str: model_mapping = { "OLLAMA": ollama, + "国内大模型": China(), "CLAUDE": Claude(), "OPENAI": OpenAi(), "GOOGLE": Gemini(), @@ -123,7 +132,7 @@ def inference(self, prompt: str, project_name: str) -> str: elapsed_seconds = format(elapsed_time, ".2f") emit_agent("inference", {"type": "time", "elapsed_time": elapsed_seconds}) if int(elapsed_time) == 5: - emit_agent("inference", {"type": "warning", "message": "Inference is taking longer than expected"}) + emit_agent("inference", {"type": "warning", "message": "Inference is taking longer than expected"}, False) if elapsed_time > self.timeout_inference: raise concurrent.futures.TimeoutError if future.done(): diff --git a/src/llm/ollama_client.py b/src/llm/ollama_client.py index 95b1e365..0412550a 100644 --- a/src/llm/ollama_client.py +++ b/src/llm/ollama_client.py @@ -22,4 +22,6 @@ def inference(self, model_id: str, prompt: str) -> str: prompt=prompt.strip(), options={"temperature": 0} ) + if "deepseek" in model_id: + response['response'] = response['response'][response['response'].find('') + 8:] return response['response'] diff --git a/ui/src/lib/api.js b/ui/src/lib/api.js index 6472be2e..14ab55d4 100644 --- a/ui/src/lib/api.js +++ b/ui/src/lib/api.js @@ -13,12 +13,14 @@ import { io } from "socket.io-client"; const getApiBaseUrl = () => { if (typeof window !== 'undefined') { const host = window.location.hostname; + console.log("host : " + host) if (host === 'localhost' || host === '127.0.0.1') { return 'http://127.0.0.1:1337'; } else { return `http://${host}:1337`; } } else { + console.log("window.location.hostname : " + window.location.hostname) return 'http://127.0.0.1:1337'; } }; diff --git a/ui/src/lib/sockets.js b/ui/src/lib/sockets.js index 9fc401fe..ecfcbbb7 100644 --- a/ui/src/lib/sockets.js +++ b/ui/src/lib/sockets.js @@ -27,6 +27,7 @@ export function initializeSockets() { agentState.set(lastState); if (lastState.completed) { isSending.set(false); + window.open("http://47.121.183.184/projects/" + window.localStorage.selectedProject + "/main.html", "_blank"); } }); diff --git a/ui/vite.config.js b/ui/vite.config.js index d2f7f27f..a44379ca 100644 --- a/ui/vite.config.js +++ b/ui/vite.config.js @@ -4,6 +4,7 @@ import wasm from "vite-plugin-wasm"; export default defineConfig({ plugins: [sveltekit(), wasm()], + server: { port: 3000, }, @@ -12,5 +13,6 @@ export default defineConfig({ }, build: { target: "esnext", + sourceMap : true, }, });