Nexari-Research commited on
Commit
50fa059
Β·
verified Β·
1 Parent(s): 9d57afd

Update ui.py

Browse files
Files changed (1) hide show
  1. ui.py +30 -156
ui.py CHANGED
@@ -1,164 +1,38 @@
1
- # ui.py - FIXED (replace previous ui.py with this file)
2
  import gradio as gr
3
- import html
4
- import re
5
- from typing import List, Tuple, Generator
6
-
7
- def _detect_status_from_text(text: str) -> Tuple[str, str, str]:
8
- """
9
- Return (status_text, emoji_icon, css_class)
10
- """
11
- if not text:
12
- return ("Responding...", "🌐", "status-general")
13
- t = text.lower()
14
-
15
- # heuristics for "code" detection
16
- code_kw = ["code", "html", "css", "javascript", "python", "java", "c++",
17
- "function", "def ", "class ", "console.log", "npm", "react", "vue", "sql", "query"]
18
- if any(k in t for k in code_kw) or re.search(r"[<>]{1}\/?[a-zA-Z]+", t) or re.search(r"\bdef\b|\bfunction\b|\breturn\b", t):
19
- return ("Generating code...", "🧩", "status-code")
20
-
21
- search_kw = ["search", "find", "look up", "google", "who is", "what is", "wiki", "latest", "news", "open link"]
22
- if any(k in t for k in search_kw):
23
- return ("Searching the web...", "πŸ”Ž", "status-search")
24
-
25
- time_kw = ["time", "date", "what time", "timezone", "clock", "current time"]
26
- if any(k in t for k in time_kw):
27
- return ("Checking time...", "⏱️", "status-time")
28
-
29
- # default
30
- return ("Responding...", "🌐", "status-general")
31
-
32
 
33
  def create_ui(generate_fn):
34
  """
35
- Build a Blocks-based chat UI with a nicer, context-aware status indicator.
36
-
37
- generate_fn(messages) -> reply string (synchronous) or may raise exceptions.
38
- """
39
-
40
- css = r"""
41
- body { background-color: #0b0f19; color: #e8eef8; }
42
- .gradio-container { font-family: Inter, system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial; max-width: 980px !important; margin: auto; }
43
- footer { visibility: hidden; display: none; }
44
- .status-pill {
45
- display:inline-flex;
46
- align-items:center;
47
- gap:10px;
48
- padding:8px 14px;
49
- border-radius:999px;
50
- font-weight:600;
51
- font-size:14px;
52
- box-shadow: 0 6px 22px rgba(2,6,23,0.6), inset 0 1px 0 rgba(255,255,255,0.02);
53
- transition: transform .18s ease, opacity .22s ease;
54
- min-height:42px;
55
- max-width: 92%;
56
- white-space:nowrap;
57
- overflow:hidden;
58
- text-overflow:ellipsis;
59
- }
60
- .status-pill .dot {
61
- display:inline-block;
62
- width:8px;
63
- height:8px;
64
- border-radius:50%;
65
- background:rgba(255,255,255,0.95);
66
- box-shadow: 0 0 0 rgba(255,255,255,0.0);
67
- margin-left:2px;
68
- transform-origin:center;
69
- animation: pulse 1.2s infinite;
70
- }
71
- @keyframes pulse {
72
- 0% { transform: scale(1); opacity: 0.9; }
73
- 50% { transform: scale(1.6); opacity: 0.45; }
74
- 100% { transform: scale(1); opacity: 0.9; }
75
- }
76
-
77
- .status-general { background: linear-gradient(180deg, rgba(34,40,55,0.9), rgba(10,12,18,0.95)); color: #E6F0FF; border: 1px solid rgba(255,255,255,0.04); }
78
- .status-code { background: linear-gradient(180deg, rgba(10,20,30,0.95), rgba(6,10,16,0.95)); color: #E2FFD9; border: 1px solid rgba(0,255,200,0.06); }
79
- .status-search { background: linear-gradient(180deg, rgba(18,12,36,0.95), rgba(6,6,12,0.95)); color: #FFF6D9; border: 1px solid rgba(255,220,50,0.06); }
80
- .status-time { background: linear-gradient(180deg, rgba(8,18,10,0.95), rgba(4,8,6,0.95)); color: #DFF0FF; border: 1px solid rgba(100,200,255,0.04); }
81
-
82
- .status-small { font-size:13px; padding:6px 10px; min-height:36px; }
83
- .status-hidden { opacity: 0; transform: translateY(6px) scale(0.98); pointer-events: none; }
84
-
85
- @media (max-width:600px) {
86
- .status-pill { font-size:13px; padding:7px 12px; gap:8px; min-height:40px; }
87
- }
88
  """
89
-
90
- def _set_status_component(text: str, icon: str, css_class: str) -> str:
91
- # sanitize and build html content
92
- safe_text = html.escape(text)
93
- html_content = (
94
- f'<div class="status-pill {css_class} status-small">'
95
- f'<span style="font-size:16px;line-height:16px;margin-right:8px">{html.escape(icon)}</span>'
96
- f'<span>{safe_text}</span>'
97
- f'<span style="margin-left:8px" class="dot" id="nexari_dot"></span>'
98
- f'</div>'
99
- )
100
- return html_content
101
-
102
- def _finalize_status_done() -> str:
103
- done_html = (
104
- '<div class="status-pill status-general status-small">'
105
- '<span style="font-size:16px;line-height:16px;margin-right:8px">βœ…</span>'
106
- '<span>Done β€” Ready</span>'
107
- '</div>'
108
- )
109
- return done_html
110
-
111
- with gr.Blocks(css=css) as demo:
112
  gr.Markdown("## Nexari AI β€” Research Backend")
113
- with gr.Row():
114
- chatbot = gr.Chatbot(elem_id="nexari_chatbot", label="Nexari").style(height=520)
115
- with gr.Row():
116
- # initial status HTML (will be replaced by generator yields)
117
- status_html = gr.HTML(
118
- """
119
- <div class="status-pill status-general status-small">
120
- <span style="font-size:16px;line-height:16px;margin-right:8px">🌐</span>
121
- <span>Ready</span>
122
- </div>
123
- """
124
- )
125
- with gr.Row():
126
- txt = gr.Textbox(show_label=False, placeholder="Ask Nexari-G1 (try: 'create a calculator in HTML')", lines=1)
127
- send = gr.Button("Send", variant="primary")
128
-
129
  state = gr.State([])
130
 
131
- # Generator-based submit to allow immediate status update
132
- def submit(message: str, history: List[tuple]) -> Generator:
133
- # compute and yield working status immediately
134
- status_text, icon, css_class = _detect_status_from_text(message)
135
- working_html = _set_status_component(status_text, icon, css_class)
136
-
137
- # yield current chat (unchanged), empty textbox, and working status so UI shows pill immediately
138
- # Note: order of outputs must match the components we will update: [chatbot, txt, status_html]
139
- yield history, "", working_html
140
-
141
- # Now perform generation (blocking). This will happen after browser updated UI.
142
- try:
143
- messages = []
144
- for human, ai in history:
145
- messages.append({"role": "user", "content": human})
146
- messages.append({"role": "assistant", "content": ai})
147
- messages.append({"role": "user", "content": message})
148
-
149
- reply = generate_fn(messages) or "Sorry, no response."
150
-
151
- except Exception as e:
152
- reply = f"Internal UI error: {e}"
153
-
154
- new_history = history + [(message, reply)]
155
- done_html = _finalize_status_done()
156
-
157
- # final yield updates the chat (with reply), clears textbox, and shows Done badge
158
- yield new_history, "", done_html
159
-
160
- # wire submit to enter and button; queue=True ensures generator yields are processed
161
- txt.submit(submit, [txt, state], [chatbot, txt, status_html], queue=True)
162
- send.click(submit, [txt, state], [chatbot, txt, status_html], queue=True)
163
-
164
- return demo
 
1
+ # ui.py - UPDATED (replace original)
2
  import gradio as gr
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
 
4
  def create_ui(generate_fn):
5
  """
6
+ Build a Blocks-based chat UI that returns a gr.Blocks object.
7
+ generate_fn receives a list of messages (user/assistant pairs as dicts)
8
+ and should return a reply string (this wrapper uses it as a placeholder).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
  """
10
+ with gr.Blocks(css="""
11
+ body { background-color: #0b0f19; color: #e0e0e0; }
12
+ .gradio-container { font-family: 'Inter', sans-serif; max-width: 900px !important; margin: auto; }
13
+ footer { visibility: hidden; display: none; }
14
+ """) as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
  gr.Markdown("## Nexari AI β€” Research Backend")
16
+ chatbot = gr.Chatbot(elem_id="nexari_chatbot", label="Nexari").style(height=500)
17
+ txt = gr.Textbox(show_label=False, placeholder="Type your message and press Enter")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  state = gr.State([])
19
 
20
+ def submit(message, history):
21
+ # history is list of (human, ai) pairs in Gradio; convert to messages
22
+ messages = []
23
+ for human, ai in history:
24
+ messages.append({"role": "user", "content": human})
25
+ messages.append({"role": "assistant", "content": ai})
26
+ messages.append({"role": "user", "content": message})
27
+ # call generator wrapper (synchronous placeholder)
28
+ reply = generate_fn(messages)
29
+ # append to history
30
+ history = history + [(message, reply)]
31
+ return history, ""
32
+
33
+ txt.submit(submit, [txt, state], [chatbot, txt], queue=False)
34
+ # Also allow clicking a send button
35
+ send = gr.Button("Send")
36
+ send.click(submit, [txt, state], [chatbot, txt], queue=False)
37
+
38
+ return demo