VladyslavH commited on
Commit
756e900
·
1 Parent(s): 53f25a6
Files changed (3) hide show
  1. app.py +139 -42
  2. static/script.js +103 -0
  3. static/style.css +154 -0
app.py CHANGED
@@ -104,54 +104,151 @@ def bot(
104
  yield history
105
 
106
 
 
 
107
  import gradio as gr
108
- import random
109
- import time
110
-
111
- with gr.Blocks() as demo:
112
- chatbot = gr.Chatbot(
113
- type="messages",
114
- allow_tags=["think"],
115
- examples=[
116
- {"text": i}
117
- for i in [
118
- "хто тримає цей район?",
119
- "Напиши історію про Івасика-Телесика",
120
- "Яка найвища гора в Україні?",
121
- "Як звали батька Тараса Григоровича Шевченка?",
122
- # "Як можна заробити нелегально швидко гроші?"],
123
- "Яка з цих гір не знаходиться у Європі? Говерла, Монблан, Гран-Парадізо, Еверест",
124
- "Дай відповідь на питання\nЧому у качки жовті ноги?",
125
- ]
126
- ],
 
 
 
 
 
 
 
 
 
 
 
 
127
  )
128
- msg = gr.Textbox(label="Message", autofocus=True)
129
- send_btn = gr.Button("Send")
130
- # clear = gr.Button("Clear")
131
 
132
- msg.submit(user, [msg, chatbot], [msg, chatbot], queue=True).then(
133
- bot, chatbot, chatbot
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  )
135
 
136
- chatbot.example_select(
137
- append_example_message, [chatbot], [chatbot], queue=True
138
- ).then(bot, chatbot, chatbot)
139
 
140
- send_btn.click(user, [msg, chatbot], [msg, chatbot], queue=True).then(
141
- bot, chatbot, chatbot
142
- )
 
 
143
 
144
- # clear.click(lambda: None, None, chatbot, queue=True)
 
 
 
 
 
 
 
 
 
145
 
146
  if __name__ == "__main__":
147
- demo.launch()
148
-
149
- """gr.Slider(minimum=1, maximum=4096, value=512, step=1, label="Max new tokens"),
150
- gr.Slider(minimum=0.1, maximum=4.0, value=0.7, step=0.1, label="Temperature"),
151
- gr.Slider(
152
- minimum=0.1,
153
- maximum=1.0,
154
- value=0.95,
155
- step=0.05,
156
- label="Top-p (nucleus sampling)",
157
- ),"""
 
104
  yield history
105
 
106
 
107
+ # --- drop-in UI compatible with older Gradio versions ---
108
+ import os, tempfile, time
109
  import gradio as gr
110
+
111
+ # Ukrainian-inspired theme with deep, muted colors reflecting unbeatable spirit:
112
+ THEME = gr.themes.Soft(
113
+ primary_hue="blue", # Deep blue representing Ukrainian sky and resolve
114
+ secondary_hue="amber", # Warm amber representing golden fields and determination
115
+ neutral_hue="stone", # Earthy stone representing strength and foundation
116
+ )
117
+
118
+ # Load CSS from external file
119
+ def load_css():
120
+ try:
121
+ with open("static/style.css", "r", encoding="utf-8") as f:
122
+ return f.read()
123
+ except FileNotFoundError:
124
+ print("Warning: static/style.css not found")
125
+ return ""
126
+
127
+ CSS = load_css()
128
+
129
+ def _clear_chat():
130
+ return "", []
131
+
132
+ with gr.Blocks(theme=THEME, css=CSS, fill_height=True) as demo:
133
+ # Header (no gr.Box to avoid version issues)
134
+ gr.HTML(
135
+ """
136
+ <div id="app-header">
137
+ <div class="app-title">✨ LAPA</div>
138
+ <div class="app-subtitle">LLM for Ukrainian Language</div>
139
+ </div>
140
+ """
141
  )
 
 
 
142
 
143
+ with gr.Row(equal_height=True):
144
+ # Left side: Chat
145
+ with gr.Column(scale=7, elem_id="left-pane"):
146
+ with gr.Column(elem_id="chat-card"):
147
+ chatbot = gr.Chatbot(
148
+ type="messages",
149
+ height=560,
150
+ render_markdown=True,
151
+ show_copy_button=True,
152
+ show_label=False,
153
+ # likeable=True,
154
+ allow_tags=["think"],
155
+ examples=[
156
+ {"text": i}
157
+ for i in [
158
+ "хто тримає цей район?",
159
+ "Напиши історію про Івасика-Телесика",
160
+ "Яка найвища гора в Україні?",
161
+ "Як звали батька Тараса Григоровича Шевченка?",
162
+ "Яка з цих гір не знаходиться у Європі? Говерла, Монблан, Гран-Парадізо, Еверест",
163
+ "Дай відповідь на питання\nЧому у качки жовті ноги?",
164
+ ]
165
+ ],
166
+ )
167
+
168
+ # ChatGPT-style input box with stop button
169
+ with gr.Row(elem_id="chat-input-row"):
170
+ msg = gr.Textbox(
171
+ label=None,
172
+ placeholder="Message… (Press Enter to send)",
173
+ autofocus=True,
174
+ lines=1,
175
+ max_lines=6,
176
+ container=False,
177
+ show_label=False,
178
+ elem_id="chat-input",
179
+ elem_classes=["chat-input-box"]
180
+ )
181
+ stop_btn_visible = gr.Button(
182
+ "⏹️",
183
+ variant="secondary",
184
+ elem_id="stop-btn-visible",
185
+ elem_classes=["stop-btn-chat"],
186
+ visible=False,
187
+ size="sm"
188
+ )
189
+
190
+ # Hidden buttons for functionality
191
+ with gr.Row(visible=False):
192
+ send_btn = gr.Button("Send", variant="primary", elem_id="send-btn")
193
+ stop_btn = gr.Button("Stop", variant="secondary", elem_id="stop-btn")
194
+ clear_btn = gr.Button("Clear", variant="secondary", elem_id="clear-btn")
195
+
196
+ # export_btn = gr.Button("Export chat (.md)", variant="secondary", elem_classes=["rounded-btn","secondary-btn"])
197
+ # exported_file = gr.File(label="", interactive=False, visible=True)
198
+ gr.HTML('<div class="footer-tip">Shortcuts: Enter to send • Shift+Enter for new line</div>')
199
+
200
+ # Helper functions for managing UI state
201
+ def show_stop_button():
202
+ return gr.update(visible=True)
203
+
204
+ def hide_stop_button():
205
+ return gr.update(visible=False)
206
+
207
+ # Events (preserve your original handlers)
208
+ e1 = msg.submit(fn=user, inputs=[msg, chatbot], outputs=[msg, chatbot], queue=True).then(
209
+ fn=show_stop_button, inputs=None, outputs=stop_btn_visible
210
+ ).then(
211
+ fn=bot, inputs=chatbot, outputs=chatbot
212
+ ).then(
213
+ fn=hide_stop_button, inputs=None, outputs=stop_btn_visible
214
+ )
215
+
216
+ e2 = send_btn.click(fn=user, inputs=[msg, chatbot], outputs=[msg, chatbot], queue=True).then(
217
+ fn=show_stop_button, inputs=None, outputs=stop_btn_visible
218
+ ).then(
219
+ fn=bot, inputs=chatbot, outputs=chatbot
220
+ ).then(
221
+ fn=hide_stop_button, inputs=None, outputs=stop_btn_visible
222
+ )
223
+
224
+ e3 = chatbot.example_select(fn=append_example_message, inputs=[chatbot], outputs=[chatbot], queue=True).then(
225
+ fn=show_stop_button, inputs=None, outputs=stop_btn_visible
226
+ ).then(
227
+ fn=bot, inputs=chatbot, outputs=chatbot
228
+ ).then(
229
+ fn=hide_stop_button, inputs=None, outputs=stop_btn_visible
230
  )
231
 
232
+ # Stop cancels running events (both buttons work)
233
+ stop_btn.click(fn=hide_stop_button, inputs=None, outputs=stop_btn_visible, cancels=[e1, e2, e3], queue=True)
234
+ stop_btn_visible.click(fn=hide_stop_button, inputs=None, outputs=stop_btn_visible, cancels=[e1, e2, e3], queue=True)
235
 
236
+ # Clear chat + input
237
+ clear_btn.click(fn=_clear_chat, inputs=None, outputs=[msg, chatbot])
238
+
239
+ # Export markdown
240
+ # export_btn.click(fn=_export_markdown, inputs=chatbot, outputs=exported_file)
241
 
242
+ # Load and inject external JavaScript
243
+ def load_javascript():
244
+ try:
245
+ with open("static/script.js", "r", encoding="utf-8") as f:
246
+ return f"<script>{f.read()}</script>"
247
+ except FileNotFoundError:
248
+ print("Warning: static/script.js not found")
249
+ return ""
250
+
251
+ gr.HTML(load_javascript())
252
 
253
  if __name__ == "__main__":
254
+ demo.queue().launch()
 
 
 
 
 
 
 
 
 
 
static/script.js ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ (function () {
2
+ console.log("Keyboard shortcuts script loaded");
3
+
4
+ const send = () => {
5
+ // Try multiple selectors to find the send button
6
+ const selectors = [
7
+ '#send-btn',
8
+ 'button[id="send-btn"]',
9
+ '.gr-button:contains("Send")',
10
+ 'button:contains("Send")',
11
+ '#send-btn button',
12
+ '[data-testid*="send"]',
13
+ 'button[variant="primary"]'
14
+ ];
15
+
16
+ let btn = null;
17
+ for (let selector of selectors) {
18
+ try {
19
+ if (selector.includes(':contains')) {
20
+ // Handle :contains selector manually
21
+ const buttons = document.querySelectorAll('button');
22
+ for (let button of buttons) {
23
+ if (button.textContent.trim() === 'Send') {
24
+ btn = button;
25
+ break;
26
+ }
27
+ }
28
+ } else {
29
+ btn = document.querySelector(selector);
30
+ }
31
+ if (btn) {
32
+ console.log("Found send button with selector:", selector);
33
+ break;
34
+ }
35
+ } catch (e) {
36
+ // Skip invalid selectors
37
+ }
38
+ }
39
+
40
+ if (btn) {
41
+ console.log("Clicking send button");
42
+ btn.click();
43
+ return true;
44
+ } else {
45
+ console.log("Send button not found");
46
+ // Debug: log all buttons
47
+ const allButtons = document.querySelectorAll('button');
48
+ console.log("All buttons found:", allButtons);
49
+ return false;
50
+ }
51
+ };
52
+
53
+ const setupKeyboardShortcuts = () => {
54
+ console.log("Setting up keyboard shortcuts");
55
+
56
+ document.addEventListener('keydown', (e) => {
57
+ const isCmdEnter = (e.metaKey || e.ctrlKey) && e.key === 'Enter';
58
+ const isEnter = e.key === 'Enter' && !e.shiftKey && !e.metaKey && !e.ctrlKey;
59
+ const isInputFocused = document.activeElement &&
60
+ (document.activeElement.matches('#chat-input textarea') ||
61
+ document.activeElement.matches('.chat-input-box textarea'));
62
+
63
+ if ((isCmdEnter || (isEnter && isInputFocused)) && isInputFocused) {
64
+ console.log("Send triggered");
65
+ e.preventDefault();
66
+ e.stopPropagation();
67
+
68
+ const success = send();
69
+ if (!success) {
70
+ console.log("Failed to send, trying again in 100ms");
71
+ setTimeout(send, 100);
72
+ }
73
+ }
74
+
75
+ if (e.key === 'Escape') {
76
+ const input = document.querySelector('#chat-input textarea') || document.querySelector('.chat-input-box textarea');
77
+ if (input) {
78
+ input.focus();
79
+ console.log("Focused input field");
80
+ }
81
+ }
82
+ }, true);
83
+
84
+ console.log("Keyboard shortcuts set up");
85
+ };
86
+
87
+ // Multiple attempts to set up shortcuts
88
+ const attempts = [100, 500, 1000, 2000];
89
+ attempts.forEach(delay => {
90
+ setTimeout(() => {
91
+ console.log(`Attempting setup after ${delay}ms`);
92
+ setupKeyboardShortcuts();
93
+ }, delay);
94
+ });
95
+
96
+ // Also set up immediately if DOM is ready
97
+ if (document.readyState !== 'loading') {
98
+ setupKeyboardShortcuts();
99
+ } else {
100
+ document.addEventListener('DOMContentLoaded', setupKeyboardShortcuts);
101
+ }
102
+
103
+ })();
static/style.css ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Background - Light Ukrainian sky with warm golden undertones */
2
+ .gradio-container {
3
+ background: radial-gradient(1200px 600px at 20% 0%, #f1f5f9 0%, #e2e8f0 45%, #cbd5e1 100%);
4
+ }
5
+
6
+ /* Header - Transparent background, only text visible */
7
+ #app-header {
8
+ position: sticky; top: 0; z-index: 10;
9
+ background: transparent;
10
+ backdrop-filter: none;
11
+ padding: 18px 8px 10px 8px;
12
+ border-bottom: none;
13
+ }
14
+ #app-header .app-title {
15
+ font-weight: 800; letter-spacing: -0.02em; font-size: 40px;
16
+ background: linear-gradient(120deg, #60a5fa 0%, #fbbf24 20%, #3b82f6 40%);
17
+ -webkit-background-clip: text; background-clip: text; color: transparent;
18
+ }
19
+ #app-header .app-subtitle { color: #64748b; margin-top: 4px; }
20
+
21
+ /* Chat card */
22
+ #chat-card .gr-chatbot {
23
+ background: linear-gradient(180deg, rgba(255,255,255,0.9) 0%, rgba(248,250,252,0.95) 100%);
24
+ border: 1px solid rgba(59,130,246,0.2);
25
+ box-shadow: 0 10px 40px rgba(0,0,0,0.1), inset 0 1px 0 rgba(245,158,11,0.15);
26
+ backdrop-filter: blur(8px);
27
+ border-radius: 18px;
28
+ }
29
+
30
+ /* Code blocks */
31
+ #chat-card .prose pre {
32
+ border: 1px solid rgba(59,130,246,0.2);
33
+ background: #f8fafc !important;
34
+ }
35
+ #chat-card .prose code {
36
+ background: rgba(59,130,246,0.1);
37
+ border-radius: 6px; padding: 0.1rem 0.35rem;
38
+ }
39
+
40
+ /* ChatGPT-style input */
41
+ #chat-input-row {
42
+ margin-top: 16px;
43
+ padding: 0 8px;
44
+ display: flex !important;
45
+ align-items: center !important;
46
+ gap: 6px !important;
47
+ }
48
+
49
+ .chat-input-box textarea {
50
+ border: none !important;
51
+ outline: none !important;
52
+ box-shadow: 0 2px 8px rgba(0,0,0,0.08) !important;
53
+ border-radius: 24px !important;
54
+ background: rgba(255,255,255,0.9) !important;
55
+ color: #334155 !important;
56
+ padding: 12px 16px !important;
57
+ font-size: 15px !important;
58
+ line-height: 1.4 !important;
59
+ resize: none !important;
60
+ transition: all 0.2s ease !important;
61
+ }
62
+
63
+ .chat-input-box textarea:focus {
64
+ box-shadow: 0 4px 12px rgba(59,130,246,0.25) !important;
65
+ background: rgba(255,255,255,1) !important;
66
+ }
67
+
68
+ .chat-input-box textarea::placeholder {
69
+ color: #94a3b8 !important;
70
+ opacity: 0.8 !important;
71
+ }
72
+
73
+ /* Hide container borders */
74
+ .chat-input-box {
75
+ border: none !important;
76
+ background: transparent !important;
77
+ box-shadow: none !important;
78
+ flex: 1 !important;
79
+ width: 100% !important;
80
+ }
81
+
82
+ /* Input wrapper for send button positioning */
83
+
84
+ /* Send indicator - only show when stop button is hidden */
85
+ .chat-input-box::after {
86
+ content: "↵";
87
+ position: absolute;
88
+ right: 16px;
89
+ top: 50%;
90
+ transform: translateY(-50%);
91
+ color: #64748b;
92
+ font-size: 16px;
93
+ pointer-events: none;
94
+ opacity: 0.6;
95
+ transition: opacity 0.2s ease;
96
+ }
97
+
98
+ .chat-input-box:hover::after {
99
+ opacity: 0.8;
100
+ }
101
+
102
+ /* Stop button styling */
103
+ .stop-btn-chat {
104
+ flex-shrink: 0 !important;
105
+ width: 45px !important;
106
+ min-width: 45px !important;
107
+ max-width: 45px !important;
108
+ height: 45px !important;
109
+ margin: 0 !important;
110
+ padding: 0 !important;
111
+ border-radius: 100% !important;
112
+ }
113
+
114
+ .stop-btn-chat button {
115
+ background: rgba(239, 68, 68, 0.1) !important;
116
+ border: 1px solid rgba(239, 68, 68, 0.3) !important;
117
+ color: #ef4444 !important;
118
+ border-radius: 50% !important;
119
+ padding: 6px !important;
120
+ min-width: 45px !important;
121
+ max-width: 45px !important;
122
+ width: 45px !important;
123
+ height: 45px !important;
124
+ font-size: 14px !important;
125
+ font-weight: 500 !important;
126
+ transition: all 0.2s ease !important;
127
+ display: flex !important;
128
+ align-items: center !important;
129
+ justify-content: center !important;
130
+ flex-shrink: 0 !important;
131
+ box-sizing: border-box !important;
132
+ }
133
+
134
+ .stop-btn-chat button:hover {
135
+ background: rgba(239, 68, 68, 0.15) !important;
136
+ border-color: rgba(239, 68, 68, 0.4) !important;
137
+ transform: translateY(-1px) !important;
138
+ box-shadow: 0 4px 8px rgba(239, 68, 68, 0.2) !important;
139
+ }
140
+
141
+ /* Hide send icon when stop button is visible */
142
+ #chat-input-row:has(.stop-btn-chat:not([style*="display: none"])) .chat-input-box::after {
143
+ display: none;
144
+ }
145
+
146
+ /* Right pane cards */
147
+ .side-card {
148
+ border: 1px solid rgba(59,130,246,0.2);
149
+ border-radius: 16px;
150
+ background: linear-gradient(180deg, rgba(255,255,255,0.8) 0%, rgba(248,250,252,0.9) 100%);
151
+ padding: 8px 10px;
152
+ }
153
+
154
+ .footer-tip { color: #64748b; font-size: 12.5px; text-align:center; margin-top: 6px; }