commit 5d2c052b7052de81fa29fa72e66408656dfd98e4 Author: FatihMakes Date: Mon Dec 15 18:56:25 2025 +0300 Add files via upload diff --git a/.env b/.env new file mode 100644 index 0000000..4566e61 --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +GROQ_API_KEY = PASTE YOUR API GROQ_API_KEY +#IF YOU DON'T KNOW HOW, THEN WATCH THE VIDEO https://youtu.be/TROMQ_Du7q8?si=UO7TnaUJ6JBCZvoi \ No newline at end of file diff --git a/aircraft_module.py b/aircraft_module.py new file mode 100644 index 0000000..b644564 --- /dev/null +++ b/aircraft_module.py @@ -0,0 +1,194 @@ +import os +import math +import time +import requests +import webbrowser +import re +from difflib import SequenceMatcher +from dotenv import load_dotenv +from voice_player import edge_speak + +load_dotenv() + +MY_LAT = float(os.getenv("LAT", 40.7908711)) +MY_LON = float(os.getenv("LON", -73.3746079)) + +OPENSKY_STATES = "https://opensky-network.org/api/states/all" +OPENSKY_FLIGHTS = "https://opensky-network.org/api/flights/aircraft" + +def normalize(text: str): + text = text.lower() + text = re.sub(r"[^a-z0-9\s]", "", text) + text = re.sub(r"\s+", " ", text).strip() + return text + + +def smart_match(user_input: str, command: str): + user = normalize(user_input) + cmd = normalize(command) + + if cmd in user: + return True + + user_words = set(user.split()) + cmd_words = set(cmd.split()) + + if cmd_words and len(user_words & cmd_words) / len(cmd_words) >= 0.6: + return True + + return SequenceMatcher(None, user, cmd).ratio() >= 0.6 + + +def match_commands(user_input: str, commands: list): + return any(smart_match(user_input, cmd) for cmd in commands) + +def haversine(lat1, lon1, lat2, lon2): + R = 6371 + d_lat = math.radians(lat2 - lat1) + d_lon = math.radians(lon2 - lon1) + a = ( + math.sin(d_lat / 2) ** 2 + + math.cos(math.radians(lat1)) + * math.cos(math.radians(lat2)) + * math.sin(d_lon / 2) ** 2 + ) + return 2 * R * math.atan2(math.sqrt(a), math.sqrt(1 - a)) + +def get_nearby_aircraft(radius_km=200): + lamin, lamax = MY_LAT - 1, MY_LAT + 1 + lomin, lomax = MY_LON - 1, MY_LON + 1 + + try: + r = requests.get( + f"{OPENSKY_STATES}?lamin={lamin}&lamax={lamax}&lomin={lomin}&lomax={lomax}", + timeout=10 + ) + data = r.json() + except: + return [] + + aircraft = [] + + for s in data.get("states", []): + lat, lon = s[6], s[5] + if lat is None or lon is None: + continue + + dist = haversine(MY_LAT, MY_LON, lat, lon) + if dist > radius_km: + continue + + velocity = s[9] or 0 + heading = s[10] or 0 + + aircraft.append({ + "icao24": s[0], + "callsign": (s[1] or "Unknown").strip(), + "country": s[2] or "Unknown", + "lat": lat, + "lon": lon, + "distance": dist, + "velocity": velocity, + "heading": heading + }) + + return aircraft + + +def get_flight_route(icao24): + now = int(time.time()) + begin = now - 6 * 3600 + + try: + r = requests.get( + f"{OPENSKY_FLIGHTS}?icao24={icao24}&begin={begin}&end={now}", + timeout=10 + ) + flights = r.json() + if flights: + f = flights[-1] + return f.get("estDepartureAirport"), f.get("estArrivalAirport") + except: + pass + + return None, None + +def generate_jarvis_aircraft_report(planes): + total = len(planes) + closest = min(planes, key=lambda x: x["distance"]) + + dist = closest["distance"] + speed = closest["velocity"] * 3.6 + heading = closest["heading"] + + if dist < 50: + situation = "approaching your position" + elif dist < 120: + situation = "passing through nearby airspace" + else: + situation = "operating at a safe distance" + + dep, arr = get_flight_route(closest["icao24"]) + dep = dep or "unknown origin" + arr = arr or "unknown destination" + + return ( + f"Sir, radar scan complete. " + f"I have detected {total} aircraft in your vicinity. " + f"The nearest one is {closest['callsign']}, registered in {closest['country']}. " + f"It is approximately {dist:.1f} kilometers away, " + f"traveling at {speed:.0f} kilometers per hour, " + f"heading {heading:.0f} degrees, " + f"currently {situation}. " + f"Estimated route is from {dep} to {arr}. " + f"No immediate threat detected." + ) + +def speak_detailed_aircraft_report(player): + planes = get_nearby_aircraft() + + if not planes: + text = "Sir, no aircraft detected in your operational airspace." + edge_speak(text, player) + player.write_log(text) + return + + text = generate_jarvis_aircraft_report(planes) + edge_speak(text, player) + player.write_log(text) + +def handle_aircraft_command(user_text, player): + if match_commands(user_text, [ + "give me aircraft details", + "tell me detailed report", + "give me full detailed aircraft report", + "give me advanced aircraft report", + "tell me aircraft details" + ]): + speak_detailed_aircraft_report(player) + return True + + if match_commands(user_text, [ + "open aircraft", + "open flight radar", + "show aircraft map" + ]): + webbrowser.open(f"https://www.flightradar24.com/{MY_LAT},{MY_LON}/10") + edge_speak("Opening aircraft radar, sir.", player) + player.write_log("Opening aircraft radar, sir.") + return True + + if match_commands(user_text, [ + "aircraft summary", + "planes nearby", + "give me aircraft" + ]): + planes = get_nearby_aircraft() + text = f"Sir, I currently detect {len(planes)} aircraft nearby." + edge_speak(text, player) + player.write_log(text) + return True + + + + return False diff --git a/face.png b/face.png new file mode 100644 index 0000000..409192a Binary files /dev/null and b/face.png differ diff --git a/groq_ai.py b/groq_ai.py new file mode 100644 index 0000000..6e89abb --- /dev/null +++ b/groq_ai.py @@ -0,0 +1,68 @@ +from groq import Groq +from dotenv import load_dotenv +import os + +load_dotenv() +GROQ_API_KEY = os.getenv("GROQ_API_KEY") +if not GROQ_API_KEY: + raise ValueError("❌ GROQ_API_KEY not found in .env") + +client = Groq(api_key=GROQ_API_KEY) + +SYSTEM_PROMPT = """ +Give useful and very shortly answers but use maximum 2-3 sentence(and keep that sentences short(a little short)) +add "sir" like jarvis but not in every sentence. +""" + +chat_history = [] + +def get_best_model(): + model_list = client.models.list() + + available = [m.id for m in model_list.data] + print("Available Groq Models:\n", available) + + priority = [ + "llama-3.3-70b", + "llama-3.2-90b", + "llama-3.2-70b", + "llama-3.1-8b", + "mixtral-8x7b", + "gemma2-9b-it" + ] + + for name in priority: + for model_id in available: + if name in model_id.lower(): + print(f"\n✔ Selected model: {model_id}\n") + return model_id + + + return available[0] + +BEST_MODEL = get_best_model() + +def get_response(user_text): + global chat_history + + chat_history.append({"role": "user", "content": user_text}) + + messages = ( + [{"role": "system", "content": SYSTEM_PROMPT}] + + chat_history[-5:] + ) + + response = client.chat.completions.create( + model=BEST_MODEL, + messages=messages, + max_tokens=200 + ) + + text = response.choices[0].message.content.strip() + chat_history.append({"role": "assistant", "content": text}) + return text + + +def reset_history(): + global chat_history + chat_history = [] diff --git a/main.py b/main.py new file mode 100644 index 0000000..466f3ae --- /dev/null +++ b/main.py @@ -0,0 +1,40 @@ +import asyncio +import threading +from voice_player import VoiceImagePlayer, edge_speak +from speech_to_text import record_voice +from groq_ai import get_response +from aircraft_module import handle_aircraft_command + +async def get_voice_input(): + return await asyncio.to_thread(record_voice) + +async def ai_loop(player): + + while True: + user_text = await get_voice_input() + if not user_text: + continue + + handled = handle_aircraft_command(user_text, player) + if handled: + continue + + response = get_response(user_text) + print("AI:", response) + + edge_speak(response, player) + player.write_log(f"You: {user_text}") + player.write_log(f"AI: {response}") + +def main_loop(): + player = VoiceImagePlayer("face.png", size=(900, 900)) + + def thread_target(): + asyncio.run(ai_loop(player)) + + threading.Thread(target=thread_target, daemon=True).start() + player.root.mainloop() + + +if __name__ == "__main__": + main_loop() diff --git a/speech_to_text.py b/speech_to_text.py new file mode 100644 index 0000000..15471b9 --- /dev/null +++ b/speech_to_text.py @@ -0,0 +1,32 @@ +# vosk_voice.py +import sounddevice as sd +import vosk +import queue +import sys +import json + +MODEL_PATH = "C:/Users/90553/Downloads/vosk/vosk-model-small-en-us-0.15" #put your path in here +model = vosk.Model(MODEL_PATH) + +q = queue.Queue() + +def callback(indata, frames, time, status): + if status: + print(status, file=sys.stderr) + q.put(bytes(indata)) + +def record_voice(prompt="🎙 I'm listening, sir...", timeout=None, phrase_time_limit=None): + + print(prompt) + rec = vosk.KaldiRecognizer(model, 16000) + + with sd.RawInputStream(samplerate=16000, blocksize=8000, dtype='int16', + channels=1, callback=callback): + while True: + data = q.get() + if rec.AcceptWaveform(data): + result = json.loads(rec.Result()) + text = result.get("text", "") + if text.strip(): + print("👤 You:", text) + return text \ No newline at end of file diff --git a/voice_player.py b/voice_player.py new file mode 100644 index 0000000..7526478 --- /dev/null +++ b/voice_player.py @@ -0,0 +1,149 @@ +import os +import io +import random +import threading +import asyncio +import edge_tts +import sounddevice as sd +import soundfile as sf +from PIL import Image, ImageTk +import tkinter as tk +from tkinter.scrolledtext import ScrolledText +import re +import html + +# ---------------- TEXT NORMALIZATION ---------------- # + +def normalize_text(text: str) -> str: + text = text.strip() + text = re.sub(r'\s+', ' ', text) + return html.escape(text) # SSML güvenliği + +# ---------------- SSML BUILDER ---------------- # + +def build_ssml(text: str, voice: str) -> str: + return ( + f'' + f'' + f'{text}' + f'' + f'' + ) + +# ---------------- UI CLASS ---------------- # + +class VoiceImagePlayer: + def __init__(self, face_path, size=(760, 760)): + self.root = tk.Tk() + self.root.title("AI - EPISODE 2") + self.root.resizable(False, False) + self.root.geometry("760x900") + + self.fixed_size = size + self.face_original = Image.open(face_path).resize(self.fixed_size, Image.LANCZOS) + + self.face_label = tk.Label(self.root, bg="#001f4d", borderwidth=0) + self.face_label.place(relx=0.5, rely=0.4, anchor="center") + + self.scale = 1.0 + self.playing = False + + self.text_box = ScrolledText( + self.root, + fg="#00ffff", + bg="#001f4d", + insertbackground="#00ffff", + height=12, + borderwidth=0, + wrap="word", + font=("Consolas", 10), + padx=10, + pady=10 + ) + self.text_box.place(relx=0.5, rely=0.85, anchor="center") + self.text_box.configure(state="disabled") + + self.animate() + self.root.protocol("WM_DELETE_WINDOW", lambda: os._exit(0)) + + # 🔥 BU METOT EKSİKTİ – ŞİMDİ VAR + def write_log(self, text: str): + self.text_box.configure(state="normal") + self.text_box.insert(tk.END, text + "\n") + self.text_box.see(tk.END) + self.text_box.configure(state="disabled") + + def start_animation(self): + self.playing = True + + def stop_animation(self): + self.playing = False + + def animate(self): + if self.playing: + self.scale += random.uniform(-0.01, 0.015) + self.scale = min(max(self.scale, 1.02), 1.08) + else: + self.scale += (1.0 - self.scale) * 0.1 + + w, h = self.fixed_size + resized = self.face_original.resize( + (int(w * self.scale), int(h * self.scale)), + Image.LANCZOS + ) + + img = ImageTk.PhotoImage(resized) + self.face_label.configure(image=img) + self.face_label.image = img + + self.root.after(50, self.animate) + +# ---------------- VOICE ENGINE ---------------- # + +VOICE = "en-GB-RyanNeural" + # Erkek, stabil + +def edge_speak(text: str, img_player: VoiceImagePlayer, voice: str = VOICE): + if not text.strip(): + return + + clean_text = text.strip() + + def _thread(): + async def _run(): + audio_buffer = bytearray() + first_audio = False + + try: + communicate = edge_tts.Communicate( + clean_text, + voice + ) + + async for chunk in communicate.stream(): + if chunk["type"] == "audio": + audio_buffer.extend(chunk["data"]) + if not first_audio: + first_audio = True + img_player.start_animation() + + except Exception as e: + print("TTS ERROR:", e) + img_player.stop_animation() + return + + if not audio_buffer: + img_player.stop_animation() + return + + data, samplerate = sf.read(io.BytesIO(audio_buffer), dtype="float32") + sd.play(data, samplerate) + sd.wait() + img_player.stop_animation() + + loop = asyncio.new_event_loop() + asyncio.set_event_loop(loop) + loop.run_until_complete(_run()) + loop.close() + + threading.Thread(target=_thread, daemon=True).start()