MaenGit commited on
Commit
970c69c
·
1 Parent(s): 5f38269

init commit

Browse files
Files changed (4) hide show
  1. .vscode/settings.json +5 -0
  2. Dockerfile +45 -0
  3. main.py +99 -0
  4. requirements.txt +5 -0
.vscode/settings.json ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ {
2
+ "python-envs.defaultEnvManager": "ms-python.python:conda",
3
+ "python-envs.defaultPackageManager": "ms-python.python:conda",
4
+ "python-envs.pythonProjects": []
5
+ }
Dockerfile ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Start from Ollama's official image
2
+ FROM ollama/ollama
3
+
4
+ # Remove the default entrypoint
5
+ ENTRYPOINT []
6
+
7
+ # Install Python essentials
8
+ RUN apt-get update && apt-get install -y --no-install-recommends \
9
+ python3 \
10
+ python3-pip \
11
+ python3-venv \
12
+ curl \
13
+ && rm -rf /var/lib/apt/lists/*
14
+
15
+ WORKDIR /app
16
+
17
+ # Set up Virtual Env
18
+ RUN python3 -m venv /opt/venv
19
+ ENV PATH="/opt/venv/bin:$PATH"
20
+
21
+ # Install requirements
22
+ COPY requirements.txt .
23
+ RUN pip install --no-cache-dir -r requirements.txt
24
+
25
+ # Copy your code
26
+ COPY . .
27
+
28
+ # FIX: The user with UID 1000 already exists in this image,
29
+ # we just need to make sure they own the /app and the ollama path.
30
+ RUN chown -R 1000:1000 /app && \
31
+ mkdir -p /home/ollama/.ollama && \
32
+ chown -R 1000:1000 /home/ollama/.ollama
33
+
34
+ # Set the home and model path for the existing user
35
+ ENV HOME=/home/ollama \
36
+ OLLAMA_MODELS=/home/ollama/.ollama
37
+
38
+ # Switch to the existing user (UID 1000)
39
+ USER 1000
40
+
41
+ # Hugging Face standard port
42
+ EXPOSE 7860
43
+
44
+ # Startup command
45
+ CMD sh -c "ollama serve & sleep 5 && ollama pull llama3.2:1b && uvicorn main:app --host 0.0.0.0 --port 7860 --timeout-keep-alive 65"
main.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import httpx
4
+ import asyncio
5
+ import logging
6
+ from fastapi import FastAPI, HTTPException
7
+ from fastapi.responses import StreamingResponse
8
+ from fastapi.middleware.cors import CORSMiddleware
9
+ from pydantic import BaseModel
10
+ import edge_tts
11
+ import uvicorn
12
+ import base64
13
+
14
+ # إعدادات اللوج
15
+ logging.basicConfig(level=logging.INFO)
16
+ logger = logging.getLogger(__name__)
17
+
18
+ app = FastAPI()
19
+
20
+ # تفعيل CORS للاتصال مع Next.js
21
+ app.add_middleware(
22
+ CORSMiddleware,
23
+ allow_origins=["*"],
24
+ allow_methods=["*"],
25
+ allow_headers=["*"],
26
+ )
27
+
28
+ OLLAMA_URL = "http://localhost:11434/api/chat"
29
+
30
+ class ChatRequest(BaseModel):
31
+ messages: list
32
+ voice: str = "ar-SA-HamedNeural"
33
+ rate: str = "+0%"
34
+
35
+ async def stream_text_and_voice(payload,messages, voice, rate):
36
+ full_response_text = ""
37
+ sentence_buffer = ""
38
+
39
+ async with httpx.AsyncClient(timeout=None) as client:
40
+ try:
41
+ async with client.stream("POST", OLLAMA_URL, json=payload) as response:
42
+ async for line in response.aiter_lines():
43
+ if not line: continue
44
+ chunk = json.loads(line)
45
+ token = chunk.get("message", {}).get("content", "")
46
+
47
+ sentence_buffer += token
48
+ full_response_text += token
49
+
50
+ # Check for sentence end
51
+ if any(punct in token for punct in [".", "!", "?", "؟", "\n"]):
52
+ clean_text = sentence_buffer.strip()
53
+ # print(clean_text)
54
+ if clean_text:
55
+ # 1. إنشاء كائن التواصل مع edge-tts
56
+ communicate = edge_tts.Communicate(clean_text, voice, rate=rate)
57
+
58
+ async for chunk in communicate.stream():
59
+ if (chunk["type"] == "audio"):
60
+ audio_base64 = base64.b64encode(chunk["data"]).decode('utf-8')
61
+ yield f'{{ "type": "audio", "data": "{audio_base64}" }}\n'
62
+
63
+ sentence_buffer = "" # تصغير البفر لبدء جملة جديدة
64
+
65
+ # Handle remaining text in buffer
66
+ if sentence_buffer.strip():
67
+ communicate = edge_tts.Communicate(sentence_buffer.strip(), voice, rate=rate)
68
+ async for audio_chunk in communicate.stream():
69
+ if audio_chunk["type"] == "audio":
70
+ b64_data = base64.b64encode(audio_chunk["data"]).decode('utf-8')
71
+ yield json.dumps({"type": "audio", "data": b64_data}) + "\n"
72
+
73
+ # THE IMPORTANT PART: Send the text message at the end
74
+ yield json.dumps({
75
+ "type": "final_text",
76
+ "content": full_response_text
77
+ }) + "\n"
78
+
79
+ except Exception as e:
80
+ logger.error(f"Error: {e}")
81
+
82
+ @app.post("/stream-voice")
83
+ async def voice_engine(data: ChatRequest):
84
+ payload = {
85
+ "model": "llama3.2:1b", # Or whatever model you are using
86
+ "messages": data.messages,
87
+ "stream": True, # Crucial for streaming
88
+ "options": {
89
+ "temperature": 0.5,
90
+ "top_p": 0.9,
91
+ }
92
+ }
93
+ return StreamingResponse(
94
+ stream_text_and_voice(payload,data.messages, data.voice, data.rate),
95
+ media_type="audio/mpeg",
96
+ headers={"Cache-Control":"no-cache"}
97
+ )
98
+ if __name__ == "__main__":
99
+ uvicorn.run(app, host="0.0.0.0", port=7860)
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ fastapi
2
+ uvicorn
3
+ edge-tts
4
+ httpx
5
+ pydantic