Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 98 additions & 0 deletions run_chatbot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
"""
run_chatbot.py — Standalone launcher for the eSim AI Chatbot
=====================================================================
Run from the project root:
python run_chatbot.py

Requirements:
pip install PyQt5 ollama pillow

For voice input:
pip install SpeechRecognition pyaudio

For offline voice (no internet):
pip install faster-whisper

LLM backend:
1. Install Ollama from https://ollama.com
2. ollama serve
3. ollama pull qwen2.5:3b # text model (~2GB)
4. ollama pull moondream # vision model (~1.6GB, optional)
"""
import os
import sys

# Add src/ to Python path so all local imports resolve correctly
_HERE = os.path.dirname(os.path.abspath(__file__))
_SRC = os.path.join(_HERE, 'src')
if _SRC not in sys.path:
sys.path.insert(0, _SRC)

# Minimal Appconfig stub so Chatbot.py imports cleanly without the
# full eSim configuration layer (which expects an installed eSim).
# Only used when running chatbot standalone (not via Application.py).
import types
_conf_mod = types.ModuleType("configuration")
_app_mod = types.ModuleType("configuration.Appconfig")

class _AppConfigStub:
"""Minimal stub — only attributes that Chatbot.py actually uses."""
noteArea = {}
procThread_list = []
process_obj = []
_APPLICATION = "eSim"
_VERSION = "2.4"
current_project = {"ProjectName": None}
def print_info(self, *a): pass
def print_error(self, *a): pass

_app_mod.Appconfig = _AppConfigStub
_conf_mod.Appconfig = _app_mod
sys.modules["configuration"] = _conf_mod
sys.modules["configuration.Appconfig"] = _app_mod

# Now safe to import the chatbot UI
from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QFont
from frontEnd.Chatbot import ChatbotGUI # noqa: E402


def main():
app = QApplication(sys.argv)
app.setApplicationName("eSim AI Chatbot")
app.setFont(QFont("Segoe UI", 10))

# High-DPI support (Windows)
try:
from PyQt5.QtCore import Qt
QApplication.setAttribute(Qt.AA_EnableHighDpiScaling, True)
QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps, True)
except Exception:
pass

window = ChatbotGUI()
window.setWindowTitle("eSim AI Assistant — Standalone")
window.resize(520, 640)
window.show()

print("=" * 60)
print(" eSim AI Chatbot — running standalone")
print("=" * 60)
print()
print(" Status: Window opened [OK]")
print()
print(" To enable AI responses:")
print(" 1. Install Ollama: https://ollama.com")
print(" 2. Run: ollama serve")
print(" 3. Run: ollama pull qwen2.5:3b")
print(" 4. Restart this script")
print()
print(" The Live/Offline indicator in the top-right")
print(" of the chat window shows Ollama's status.")
print("=" * 60)

sys.exit(app.exec_())


if __name__ == "__main__":
main()
46 changes: 37 additions & 9 deletions src/chatbot/chatbot_thread.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,29 @@ def _downscale_image_bytes(raw_bytes: bytes) -> bytes:


def get_stt_backend() -> str:
"""Returns 'google' if SpeechRecognition is installed, else 'none'."""
"""Return the best available speech-to-text backend.

Priority: faster-whisper (offline, best quality) > vosk (offline, lightweight)
> google (online, no install) > none
"""
# Check faster-whisper first: offline, no internet needed
try:
import faster_whisper # noqa: F401
return "whisper"
except ImportError:
pass

# Check vosk: offline, very lightweight
try:
import vosk # noqa: F401
return "vosk"
except ImportError:
pass

# Fall back to Google online STT (requires internet)
if _SR_AVAILABLE:
return "google"

return "none"


Expand All @@ -85,13 +105,19 @@ def start_ollama(stop_flag=None):
The polling loop checks stop_flag() each second and exits early if cancelled.
"""
if os.name == 'nt':
subprocess.Popen('start cmd /k "ollama serve"', shell=True)
# Run ollama serve silently in the background without a visible CMD window.
# CREATE_NO_WINDOW prevents a terminal from popping up and staying open.
subprocess.Popen(
['ollama', 'serve'],
creationflags=subprocess.CREATE_NO_WINDOW,
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)
else:
subprocess.Popen(
['bash', '-c',
'x-terminal-emulator -e "ollama serve" || '
'gnome-terminal -- ollama serve || '
'xterm -e "ollama serve"']
['bash', '-c', 'ollama serve'],
stdout=subprocess.DEVNULL,
stderr=subprocess.DEVNULL,
)

for _ in range(30):
Expand Down Expand Up @@ -344,9 +370,10 @@ def run(self):
bot_response = ""
for chunk in stream:
if self._stop_requested:
bot_response += "\n\n Generation stopped."
bot_response += "\n\n\u23f9 Generation stopped."
break
bot_response += chunk["message"]["content"]
# Use .get() to safely handle malformed/done chunks from Ollama
bot_response += chunk.get("message", {}).get("content", "")

bot_response = bot_response.strip()
if not bot_response:
Expand Down Expand Up @@ -510,7 +537,8 @@ def _chat_once(self, model_name: str, prompt: str, image_bytes_list):
if self._stop_requested:
response += "\n\n\u23f9 Generation stopped."
break
piece = chunk["message"]["content"]
# Use .get() to safely handle malformed/done chunks from Ollama
piece = chunk.get("message", {}).get("content", "")
response += piece
token_count += 1

Expand Down
Loading