File size: 6,528 Bytes
b7f3dcc
 
 
 
4718250
 
 
b7f3dcc
 
 
 
 
 
 
4718250
b7f3dcc
4718250
 
b7f3dcc
4718250
b7f3dcc
 
 
 
 
4718250
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
058ae1e
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
932d1ec
 
 
 
 
 
 
 
 
 
 
 
b7f3dcc
 
4718250
b7f3dcc
 
 
524e7b6
 
 
 
b7f3dcc
 
 
 
4718250
 
b7f3dcc
524e7b6
 
b7f3dcc
 
524e7b6
b7f3dcc
 
524e7b6
b7f3dcc
 
 
4718250
b7f3dcc
 
4718250
b7f3dcc
 
4718250
 
058ae1e
4718250
524e7b6
 
 
 
 
 
932d1ec
 
524e7b6
 
 
b7f3dcc
058ae1e
b7f3dcc
058ae1e
b7f3dcc
 
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
import gradio as gr
import uuid
import logging
import re
import json
import os
import tempfile
from agent_module import chat_agent_stream
from translation_module import localize_init

logger = logging.getLogger("app.ui")

PREMIUM_CSS = """
body, .gradio-container { background-color: #0b0f19 !important; color: #e0e6ed !important; font-family: 'Inter', sans-serif !important; }
.main-container { max-width: 900px !important; margin: 0 auto !important; min-height: 600px !important; max-height: 90vh !important; display: flex !important; flex-direction: column !important; padding: 10px !important; background: #0b0f19 !important; }
.header-row { display: flex !important; align-items: center !important; justify-content: space-between !important; gap: 10px !important; margin-bottom: 10px !important; padding: 10px !important; background: rgba(255, 255, 255, 0.05) !important; border-radius: 12px !important; }
.chat-window { flex-grow: 1 !important; border-radius: 12px !important; border: 1px solid rgba(255, 255, 255, 0.1) !important; background: rgba(0, 0, 0, 0.2) !important; overflow-y: auto !important; }
.input-area { margin-top: 10px !important; background: rgba(255, 255, 255, 0.05) !important; padding: 10px !important; border-radius: 12px !important; flex-shrink: 0 !important; }
footer { display: none !important; }
@media (max-width: 768px) { .main-container { padding: 5px !important; height: auto !important; max-height: none !important; } .header-row { padding: 5px !important; flex-wrap: wrap !important; } }
"""

def save_and_clear(msg):
    return "", msg

def export_chat(history):
    if not history: return None
    with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as tf:
        json.dump(history, tf, indent=2)
        return tf.name

def import_chat(file):
    if file is None: return gr.update(), []
    try:
        with open(file.name, 'r') as f:
            data = json.load(f)
            if isinstance(data, list):
                return data, data
    except Exception as e:
        logger.error(f"Import error: {e}")
    return gr.update(), gr.update()

def clear_messages(lang_name="English"):
    name_map = {"German": "de", "French": "fr", "Spanish": "es", "Italian": "it", 
                "Portuguese": "pt", "Russian": "ru", "Japanese": "ja", "Chinese": "zh"}
    code = name_map.get(lang_name, "en")
    loc = localize_init(code)
    welcome = [{"role": "assistant", "content": loc["welcome"]}]
    return welcome, welcome

def chat_wrapper(message, history, short_answers=False, lang="English"):
    if history is None: history = []
    
    history.append({"role": "user", "content": message})
    history.append({"role": "assistant", "content": ""})

    # Intent detection is now handled internally by chat_agent_stream
    for part in chat_agent_stream(message, history[:-2], user_lang=lang, short_answers=short_answers):
        if part == "__TURN_END__":
            history.append({"role": "assistant", "content": ""})
            yield history, history
        else:
            history[-1]["content"] = part
            yield history, history
    
    yield history, history

def on_load(request: gr.Request):
    headers = dict(request.headers)
    lang_code = "en"
    try:
        if "accept-language" in headers:
            lang_code = headers.get("accept-language").split(",")[0].split("-")[0]
    except: pass

    loc = localize_init(lang_code)
    welcome = [{"role": "assistant", "content": loc["welcome"]}]
    return welcome, loc["lang"], gr.update(label=loc["label_short"]), gr.update(placeholder=loc["placeholder"])

def build_demo(enable_reload=True):
    with gr.Blocks(css=PREMIUM_CSS, title="Sage 6.5 - Oracle Intermediary") as demo:
        history_state = gr.State([])
        lang_state = gr.State("English")
        saved_msg = gr.State("")

        # Default Static Localization for Immediate Render
        default_loc = localize_init("en") 
        default_welcome = [{"role": "assistant", "content": default_loc["welcome"]}]

        with gr.Column(elem_classes="main-container"):
            with gr.Row(elem_classes="header-row"):
                gr.Markdown("## 🔮 Sage 6.5")
                with gr.Row():
                    import_btn = gr.UploadButton("📥 Import", file_types=[".json"], size="sm")
                    export_btn = gr.DownloadButton("📤 Export", size="sm")
            
            # BEST PRACTICE: Init with value, don't wait for load
            chatbot = gr.Chatbot(value=default_welcome, elem_classes="chat-window", type="messages", bubble_full_width=False, show_label=False)
            
            with gr.Row(elem_classes="input-area"):
                msg_input = gr.Textbox(placeholder=default_loc["placeholder"], show_label=False, container=False, scale=4)
                submit_btn = gr.Button("➤", scale=1, variant="primary")
            
            short_answers = gr.Checkbox(label=default_loc["label_short"], value=False)

        # Wire Events
        msg_input.submit(save_and_clear, [msg_input], [msg_input, saved_msg], queue=False).then(
            chat_wrapper, [saved_msg, history_state, short_answers, lang_state], [chatbot, history_state]
        )
        submit_btn.click(save_and_clear, [msg_input], [msg_input, saved_msg], queue=False).then(
            chat_wrapper, [saved_msg, history_state, short_answers, lang_state], [chatbot, history_state]
        )

        export_btn.click(export_chat, [history_state], [export_btn])
        import_btn.upload(import_chat, [import_btn], [chatbot, history_state])
        chatbot.clear(clear_messages, [lang_state], [chatbot, history_state])

        # State Sync must happen after init
        # We manually sync state with the default value so backend has it
        def sync_init():
             return default_welcome, default_welcome
        
        # If reload is enabled (client-side), we still try to localize dynamically, but we have a safe fallback now.
        if enable_reload:
            demo.load(on_load, None, [chatbot, lang_state, short_answers, msg_input])
        else:
            # If no reload, ensure history state matches visual init
            demo.load(sync_init, None, [history_state, chatbot]) # Sync state

        api_history = gr.Chatbot(visible=False, type="messages")
        api_chat_btn = gr.Button("API CHAT", visible=False)
        api_chat_btn.click(chat_wrapper, [msg_input, api_history, short_answers, lang_state], [chatbot, history_state], api_name="chat")
    
    return demo