File size: 13,794 Bytes
bde1021
 
 
 
 
 
 
729e882
bde1021
0893bfd
0ed18a5
0893bfd
 
 
 
0ed18a5
0893bfd
 
2e64dd6
 
264a379
44edf13
4999836
bde1021
 
42215b3
bde1021
 
 
42215b3
bde1021
 
 
33b64d8
 
bde1021
 
 
 
 
 
 
 
 
42215b3
bde1021
 
 
 
 
 
4999836
bde1021
 
 
 
 
 
42215b3
 
bde1021
 
 
 
 
 
 
44edf13
33b64d8
44edf13
3ff4918
855b657
3ff4918
 
2e64dd6
3ff4918
33b64d8
3ff4918
33b64d8
bde1021
3ff4918
bde1021
 
 
89a987c
264a379
83d6018
264a379
344cf4f
264a379
44edf13
344cf4f
8134074
44edf13
2e64dd6
 
 
 
 
 
 
44edf13
8134074
 
2e64dd6
264a379
0eb3d3e
344cf4f
2e64dd6
344cf4f
2e64dd6
 
 
 
 
 
 
 
 
495ca32
344cf4f
495ca32
2e64dd6
 
 
 
344cf4f
264a379
2e64dd6
 
 
 
264a379
2e64dd6
 
 
 
 
 
22bd0f8
2e64dd6
 
344cf4f
 
264a379
344cf4f
 
 
 
 
 
 
2e64dd6
 
344cf4f
22bd0f8
2e64dd6
264a379
344cf4f
264a379
344cf4f
2e64dd6
264a379
344cf4f
2e64dd6
344cf4f
2e64dd6
344cf4f
264a379
89a987c
344cf4f
2e64dd6
 
 
0eb3d3e
 
 
2e64dd6
 
 
 
89a987c
344cf4f
 
264a379
2e64dd6
344cf4f
2e64dd6
0eb3d3e
344cf4f
 
0eb3d3e
344cf4f
264a379
2e64dd6
344cf4f
264a379
2e64dd6
344cf4f
 
2e64dd6
344cf4f
83d6018
 
344cf4f
83d6018
 
495ca32
83d6018
 
22bd0f8
264a379
83d6018
 
 
 
264a379
83d6018
 
 
344cf4f
83d6018
 
264a379
83d6018
 
 
264a379
83d6018
 
bfebfdb
22bd0f8
264a379
83d6018
bde1021
44edf13
 
bde1021
42215b3
bde1021
 
 
 
 
 
 
 
44edf13
bde1021
e286c93
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
import gradio as gr
import pandas as pd
import numpy as np
from PIL import Image
import os
import tensorflow as tf
import glob
import requests

# ── HACK PRO STARÉ MODELY Z TEACHABLE MACHINE ─────────────────
original_init = tf.keras.layers.DepthwiseConv2D.__init__
def patched_init(self, *args, **kwargs):
    if 'groups' in kwargs:
        kwargs.pop('groups')
    original_init(self, *args, **kwargs)
tf.keras.layers.DepthwiseConv2D.__init__ = patched_init
# ──────────────────────────────────────────────────────────────

# Načtení API klíčů z prostředí Hugging Face
api_key_openrouter = os.environ.get("OPENROUTER_API_KEY")
api_key_gemini = os.environ.get("GEMINI_API_KEY")

# ── Funkce pro načtení všech CSV dat ──
def nacti_vsechna_data():
    vsechna_data_text = ""
    soubory = glob.glob("*.csv") 
    
    if not soubory:
        return "Pozor: V repozitáři nebyla nalezena žádná data (.csv)."
        
    for soubor in soubory:
        try:
            df = pd.read_csv(soubor, sep=None, engine='python')
            vsechna_data_text += f"\n--- OBSAH SOUBORU (Vzorek 15 řádků): {soubor} ---\n"
            vsechna_data_text += df.head(15).to_string() + "\n"
        except Exception as e:
            vsechna_data_text += f"\nChyba při čtení {soubor}: {str(e)}\n"
            
    return vsechna_data_text

# ── Funkce pro model Teachable Machine ────────────
def nacti_model():
    if not os.path.exists("keras_model.h5") or not os.path.exists("labels.txt"):
        return None, "Chybí soubory modelu (keras_model.h5, labels.txt)."
        
    model = tf.keras.models.load_model("keras_model.h5", compile=False)
    labels = open("labels.txt", "r", encoding="utf-8").read().strip().split("\n")
    return model, labels

GLOBAL_MODEL, GLOBAL_LABELS = nacti_model()

# ── Funkce pro vyhodnocení střechy ─────────────
def vyhodnot_strechu(obrazek):
    if obrazek is None:
        return "Prosím, nahrajte snímek střechy."
        
    model, labels = GLOBAL_MODEL, GLOBAL_LABELS
    if model is None: 
        return labels 
        
    img = obrazek.resize((224, 224))
    arr = np.array(img, dtype=np.float32) / 255.0
    arr = np.expand_dims(arr, axis=0)
    
    predikce = model.predict(arr)[0]
    index = np.argmax(predikce)
    skore = float(predikce[index]) * 100
    label = labels[index]
    
    vysledek = f"Analýza: {label} ({skore:.1f}%)\n\n"
    label_lower = label.lower()
    
    if skore < 70:
        vysledek += "⚠️ Závěr: Výsledek je nejednoznačný (spolehlivost je nižší než 70 %).\n"
        vysledek += "💡 Doporučení: Umělá inteligence si v tomto případě není dostatečně jistá. Pro přesné posouzení doporučujeme fyzickou obhlídku střechy technikem."
    elif "nevhod" in label_lower or "2" in label_lower:
        vysledek += "❌ Závěr: Střecha NENÍ vhodná pro FVE"
    elif "vhod" in label_lower or "0" in label_lower: 
        vysledek += "✅ Závěr: Střecha JE vhodná pro FVE"
    else: 
        vysledek += "❌ Závěr: Střecha NENÍ vhodná pro FVE (Neurčitý výsledek)"
        
    return vysledek


# ── JÁDRO S PODROBNÝM TRASOVÁNÍM HISTORIE VÝBĚRU (ROUTING LOG) ──
def proved_api_volani(otazka, historie):
    log_list = []
    log_list.append("⏱️ **[00:00]** Inicializace požadavku uživatele.")
    
    data_text = nacti_vsechna_data()
    log_list.append(f"📄 **[Data]** Úspěšně načteny lokální CSV soubory ({len(data_text)} znaků v kontextu).")
    
    prompt = (
        "Jsi špičkový datový analytik a expert na fotovoltaiku pro firmu DAMR58H v Ústeckém kraji. "
        "Odpovídej vždy věcně, logicky a VÝHRADNĚ PERFEKTNÍ GRAMATICKOU ČEŠTINOU.\n\n"
        "Pravidla pro analýzu:\n"
        "1. Pokud se uživatel ptá na data ze souborů CSV, NEKOPÍRUJ suše strukturu sloupců. "
        "Místo toho se podívej na hodnoty, hledej trendy, porovnávej obce a dělej reálné energetické závěry.\n"
        "2. Pravidlo o 70% jistotě a fyzické obhlídce technikem aplikuj POUZE tehdy, když vyhodnocuješ konkrétní skóre z analýzy FOTOGRAFIE střechy. Nemíchej to do tabulek.\n\n"
        f"Tady jsou data z našich souborů k analýze:\n{data_text}\n\n"
        f"Otázka: {otazka}"
    )
    
    historie.append([otazka, "⏳ Spouštím vyhledávání volného serveru..."])
    yield historie, "### 🤖 Aktivní model: Vyhledávám...", "\n".join(log_list)
    
    # KROK 1: Pokus o OpenRouter servery
    if api_key_openrouter:
        log_list.append("🔗 **[OpenRouter]** API klíč detekován. Sestavuji prioritní frontu free modelů.")
        url_or = "https://openrouter.ai/api/v1/chat/completions"
        headers_or = {
            "Authorization": f"Bearer {api_key_openrouter}",
            "Content-Type": "application/json",
            "HTTP-Referer": "https://huggingface.co",
            "X-Title": "Solar Advisor"
        }
        
        modely_or = [
            "meta-llama/llama-3.3-70b-instruct:free",
            "meta-llama/llama-3.2-3b-instruct:free",
            "openrouter/free"
        ]
        
        for model in modely_or:
            model_short = model.split('/')[-1]
            log_list.append(f"📡 **[Požadavek]** Odesílám dotaz na endpoint: `{model}`")
            yield historie, f"### 🤖 OpenRouter: Zkouším `{model_short}`...", "\n".join(log_list)
            
            payload = {
                "model": model,
                "messages": [{"role": "user", "content": prompt}],
                "temperature": 0.5
            }
            
            try:
                res = requests.post(url_or, headers=headers_or, json=payload, timeout=18)
                if res.status_code == 200:
                    data = res.json()
                    realny_model = data.get("model", model)
                    odpoved = data["choices"][0]["message"]["content"]
                    
                    log_list.append(f"✅ **[Odezva]** Server odpověděl úspěšně (Kód 200).")
                    log_list.append(f"🏷️ **[Identifikace]** OpenRouter přidělil reálnou instanci: `{realny_model}`")
                    
                    # FILTR PROTI "MRDKÁM": Pokud router podhodil slabý model
                    if model == "openrouter/free":
                        nevhodne_prazdne_modely = ["llama-3-8b", "pygmalion", "gemma-7b"]
                        if any(x in realny_model.lower() for x in nevhodne_prazdne_modely):
                            log_list.append(f"❌ **[Filtr]** Odmítnuto! Přidělený model `{realny_model}` nesplňuje kvalitativní kritéria pro češtinu. Zahazuji spojení.")
                            yield historie, "### ⚠️ OpenRouter podstrčil slabý model. Přepínám...", "\n".join(log_list)
                            break # Vyskočí z OpenRouteru a padá do KROKU 2 (Gemini)
                    
                    historie[-1][1] = odpoved
                    log_list.append("🏁 **[Dokončeno]** Data úspěšně vygenerována and odeslána do chatu.")
                    yield historie, f"### 🤖 Aktivní model: `{realny_model.split('/')[-1]}` (OpenRouter)", "\n".join(log_list)
                    return
                else:
                    log_list.append(f"⚠️ **[Chyba]** Model `{model_short}` vrátil status kód {res.status_code}. Zkouším další v pořadí.")
            except Exception as e:
                log_list.append(f"💥 **[Selhání]** Spojení s `{model_short}` spadlo na timeout nebo síťovou chybu.")
                continue
    else:
        log_list.append("⚠️ **[OpenRouter]** Klíč OPENROUTER_API_KEY chybí. Přeskakuji celou OpenRouter sadu.")

    # KROK 2: Totální záloha přes Google Gemini API
    if api_key_gemini:
        log_list.append("🚀 **[Fallback]** Inicializuji nouzový most na Google Cloud (Gemini API)...")
        yield historie, "### 🚀 Nouzový režim: Připojuji se na Google Gemini API...", "\n".join(log_list)
        
        url_gemini = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={api_key_gemini}"
        payload_gemini = {
            "contents": [{"parts": [{"text": prompt}]}],
            "generationConfig": {"temperature": 0.4}
        }
        
        try:
            res = requests.post(url_gemini, json=payload_gemini, timeout=20)
            if res.status_code == 200:
                data = res.json()
                odpoved = data['candidates'][0]['content']['parts'][0]['text']
                
                log_list.append("✅ **[Odezva]** Google Gemini 1.5 Flash zareagoval okamžitě s kódem 200.")
                log_list.append("🏁 **[Dokončeno]** Výstup vygenerován záložní infrastrukturou Google.")
                
                historie[-1][1] = odpoved
                yield historie, "### ⚡ Aktivní model: `Gemini 1.5 Flash` (Google Cloud)", "\n".join(log_list)
                return
            else:
                log_list.append(f"❌ **[Kritická chyba]** Google API odmítlo požadavek se statusem {res.status_code}.")
                status_g = f"Chyba {res.status_code}"
        except Exception as e:
            log_list.append(f"💥 **[Kritická chyba]** Google API je nedostupné nebo vypršel časový limit.")
            status_g = "Network/Timeout"
    else:
        log_list.append("❌ **[Kritická chyba]** Záložní klíč GEMINI_API_KEY není v systému definován.")
        status_g = "Klíč chybí"

    # Pokud selhalo úplně všechno
    log_list.append("🚨 **[FATAL]** Systém vyčerpal všechny dostupné servery i nouzové zálohy. Komunikace přerusená.")
    historie[-1][1] = f"❌ Selhal OpenRouter i záložní Google API. (Detaily: Gemini status: {status_g}). Zkus to poslat znovu za minutku."
    yield historie, "### 🤖 Aktivní model: Žádný (Všechny sítě přetíženy)", "\n".join(log_list)


# ── OPRAVENÁ LOGIKA OVLÁDACÍCH TLAČÍTEK (OPRAVENO 'v' -> 'in') ──
def chatbot_odpoved_manual(otazka, historie):
    if not otazka.strip():
        yield "", historie, "### 🤖 Aktivní model: Bez zátěže", "Žádný dotaz ke zpracování."
        return
    historie = historie or []
    for nova_hist, status, log in proved_api_volani(otazka, historie):
        yield "", nova_hist, status, log

def undo_posledni(historie):
    if historie:
        historie.pop()
    return historie, "### 🤖 Aktivní model: Poslední zpráva smazána", "🗑️ Poslední log vyčištěn smazáním zprávy."

def upravit_posledni(historie):
    if not historie:
        return "", historie, "### 🤖 Aktivní model: Žrádná historie k úpravě", "Žádný log k dispozici."
    posledni_dotaz = historie[-1][0]
    historie.pop()
    return posledni_dotaz, historie, "### 🤖 Aktivní model: Upravujete zprávu...", f"✏️ Upravuje se dotaz: '{posledni_dotaz}'"

def resend_funkce(historie):
    if not historie:
        yield historie, "### 🤖 Aktivní model: Žádná historie k odeslání", "Není co posílat znovu."
        return
    posledni_otazka = historie[-1][0]
    historie.pop()
    for nova_hist, status, log in proved_api_volani(posledni_otazka, historie):
        yield nova_hist, status, log


# ── UI Rozhraní (Gradio) ──────────────────────────
with gr.Blocks(title="Solární poradce") as demo:
    gr.Markdown("# Solární poradce pro Ústecký kraj")
    
    with gr.Tabs():
        with gr.TabItem("Analýza střechy"):
            gr.Markdown("### Nahrajte snímek střechy")
            with gr.Row():
                with gr.Column():
                    vstup_img = gr.Image(type="pil", label="Vyberte obrázek")
                    analyzovat_btn = gr.Button("Vyhodnotit", variant="primary")
                with gr.Column():
                    vystup_text = gr.Textbox(label="Výsledek", lines=4)
                    
                analyzovat_btn.click(fn=vyhodnot_strechu, inputs=vstup_img, outputs=vystup_text)
                
        with gr.TabItem("Chatbot s daty"):
            status_model = gr.Markdown("### 🤖 Aktivní model: Bez zátěže")
            
            chatbot = gr.Chatbot(label="Odpovědi solárního asistenta", show_copy_button=True)
            vstup_text = gr.Textbox(label="Váš dotaz", placeholder="Napište otázku a stiskněte Enter...")
            
            with gr.Row():
                btn_undo = gr.Button("↩️ Zpět (Undo)", variant="secondary", size="sm")
                btn_edit = gr.Button("✏️ Upravit (Edit)", variant="secondary", size="sm")
                btn_resend = gr.Button("🔄 Znovu poslat (Resend)", variant="primary", size="sm")
            
            with gr.Accordion("🔍 Zobrazit historii hledání modelu (Routing Log)", open=False):
                log_vystup = gr.Markdown("Zatím neproběhlo žádné vyhledávání modelu.")
            
            vstup_text.submit(
                fn=chatbot_odpoved_manual, 
                inputs=[vstup_text, chatbot], 
                outputs=[vstup_text, chatbot, status_model, log_vystup]
            )
            
            btn_undo.click(fn=undo_posledni, inputs=[chatbot], outputs=[chatbot, status_model, log_vystup])
            btn_edit.click(fn=upravit_posledni, inputs=[chatbot], outputs=[vstup_text, chatbot, status_model, log_vystup])
            btn_resend.click(fn=resend_funkce, inputs=[chatbot], outputs=[chatbot, status_model, log_vystup])

demo.launch()