Promotingai commited on
Commit
b17b364
·
verified ·
1 Parent(s): 78ba775

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +377 -0
app.py ADDED
@@ -0,0 +1,377 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import base64
3
+ import os
4
+ from openai import OpenAI
5
+ import json
6
+ from settings_mgr import generate_download_settings_js, generate_upload_settings_js
7
+
8
+ from doc2json import process_docx
9
+
10
+ dump_controls = False
11
+ log_to_console = False
12
+
13
+ temp_files = []
14
+
15
+ # constants
16
+ image_embed_prefix = "🖼️🆙 "
17
+ audio_embed_prefix = "🎙️🆙 "
18
+
19
+ def encode_image(image_data):
20
+ """Generates a prefix for image base64 data in the required format for the
21
+ four known image formats: png, jpeg, gif, and webp.
22
+
23
+ Args:
24
+ image_data: The image data, encoded in base64.
25
+
26
+ Returns:
27
+ A string containing the prefix.
28
+ """
29
+
30
+ # Get the first few bytes of the image data.
31
+ magic_number = image_data[:4]
32
+
33
+ # Check the magic number to determine the image type.
34
+ if magic_number.startswith(b'\x89PNG'):
35
+ image_type = 'png'
36
+ elif magic_number.startswith(b'\xFF\xD8'):
37
+ image_type = 'jpeg'
38
+ elif magic_number.startswith(b'GIF89a'):
39
+ image_type = 'gif'
40
+ elif magic_number.startswith(b'RIFF'):
41
+ if image_data[8:12] == b'WEBP':
42
+ image_type = 'webp'
43
+ else:
44
+ # Unknown image type.
45
+ raise Exception("Unknown image type")
46
+ else:
47
+ # Unknown image type.
48
+ raise Exception("Unknown image type")
49
+
50
+ return f"data:image/{image_type};base64,{base64.b64encode(image_data).decode('utf-8')}"
51
+
52
+ def add_text(history, text):
53
+ history = history + [(text, None)]
54
+ return history, gr.Textbox(value="", interactive=False)
55
+
56
+ def add_file(history, files):
57
+ for file in files:
58
+ if file.name.endswith(".docx"):
59
+ content = process_docx(file.name)
60
+ else:
61
+ with open(file.name, mode="rb") as f:
62
+ content = f.read()
63
+
64
+ if isinstance(content, bytes):
65
+ content = content.decode('utf-8', 'replace')
66
+ else:
67
+ content = str(content)
68
+
69
+ fn = os.path.basename(file.name)
70
+ history = history + [(f'```{fn}\n{content}\n```', None)]
71
+
72
+ os.remove(file.name)
73
+
74
+ return history
75
+
76
+ def add_img(history, files):
77
+ for file in files:
78
+ temp_files.append(file.name)
79
+
80
+ if log_to_console:
81
+ print(f"add_img {file.name}")
82
+
83
+ if file.name.endswith((".mp3", ".mp4", ".mpeg", ".mpga", ".m4a", ".wav", ".webm")):
84
+ prefix = audio_embed_prefix
85
+ else:
86
+ prefix = image_embed_prefix
87
+
88
+ history = history + [(prefix + file.name, None)]
89
+
90
+ gr.Info(f"Media added as {file.name}")
91
+
92
+ return history
93
+
94
+ def submit_text(txt_value):
95
+ return add_text([chatbot, txt_value], [chatbot, txt_value])
96
+
97
+ def undo(history):
98
+ history.pop()
99
+ return history
100
+
101
+ def dump(history):
102
+ return str(history)
103
+
104
+ def load_settings():
105
+ # Dummy Python function, actual loading is done in JS
106
+ pass
107
+
108
+ def save_settings(acc, sec, prompt, temp, tokens, model):
109
+ # Dummy Python function, actual saving is done in JS
110
+ pass
111
+
112
+ def process_values_js():
113
+ return """
114
+ () => {
115
+ return ["oai_key", "system_prompt", "seed"];
116
+ }
117
+ """
118
+
119
+ def bot(message, history, oai_key, system_prompt, seed, temperature, max_tokens, model):
120
+ try:
121
+ client = OpenAI(
122
+ api_key=oai_key
123
+ )
124
+
125
+ if model == "whisper":
126
+ result = ""
127
+ whisper_prompt = system_prompt
128
+ for human, assi in history:
129
+ if human is not None:
130
+ if human.startswith(audio_embed_prefix):
131
+ audio_fn = human.lstrip(audio_embed_prefix)
132
+ with open(audio_fn, "rb") as f:
133
+ transcription = client.audio.transcriptions.create(
134
+ model="whisper-1",
135
+ prompt=whisper_prompt,
136
+ file=f,
137
+ response_format="text"
138
+ )
139
+ whisper_prompt += f"\n{transcription}"
140
+ result += f"\n``` transcript {audio_fn}\n {transcription}\n```"
141
+ else:
142
+ whisper_prompt += f"\n{human}"
143
+ if assi is not None:
144
+ whisper_prompt += f"\n{assi}"
145
+ else:
146
+ seed_i = None
147
+ if seed:
148
+ seed_i = int(seed)
149
+
150
+ if log_to_console:
151
+ print(f"bot history: {str(history)}")
152
+
153
+ history_openai_format = []
154
+ user_msg_parts = []
155
+ if system_prompt:
156
+ history_openai_format.append({"role": "system", "content": system_prompt})
157
+ for human, assi in history:
158
+ if human is not None:
159
+ if human.startswith(image_embed_prefix):
160
+ with open(human.lstrip(image_embed_prefix), mode="rb") as f:
161
+ content = f.read()
162
+ user_msg_parts.append({"type": "image_url",
163
+ "image_url":{"url": encode_image(content)}})
164
+ else:
165
+ user_msg_parts.append({"type": "text", "text": human})
166
+
167
+ if assi is not None:
168
+ if user_msg_parts:
169
+ history_openai_format.append({"role": "user", "content": user_msg_parts})
170
+ user_msg_parts = []
171
+
172
+ history_openai_format.append({"role": "assistant", "content": assi})
173
+
174
+ if message:
175
+ user_msg_parts.append({"type": "text", "text": human})
176
+
177
+ if user_msg_parts:
178
+ history_openai_format.append({"role": "user", "content": user_msg_parts})
179
+
180
+ if log_to_console:
181
+ print(f"br_prompt: {str(history_openai_format)}")
182
+
183
+ response = client.chat.completions.create(
184
+ model=model,
185
+ messages= history_openai_format,
186
+ temperature=temperature,
187
+ seed=seed_i,
188
+ max_tokens=max_tokens
189
+ )
190
+
191
+ if log_to_console:
192
+ print(f"br_response: {str(response)}")
193
+
194
+ result = response.choices[0].message.content
195
+
196
+ history[-1][1] = result
197
+ if log_to_console:
198
+ print(f"br_result: {str(history)}")
199
+
200
+ except Exception as e:
201
+ raise gr.Error(f"Error: {str(e)}")
202
+
203
+ return "", history
204
+
205
+ def import_history(history, file):
206
+ with open(file.name, mode="rb") as f:
207
+ content = f.read()
208
+
209
+ if isinstance(content, bytes):
210
+ content = content.decode('utf-8', 'replace')
211
+ else:
212
+ content = str(content)
213
+ os.remove(file.name)
214
+
215
+ # Deserialize the JSON content
216
+ import_data = json.loads(content)
217
+
218
+ # Check if 'history' key exists for backward compatibility
219
+ if 'history' in import_data:
220
+ history = import_data['history']
221
+ system_prompt.value = import_data.get('system_prompt', '') # Set default if not present
222
+ else:
223
+ # Assume it's an old format with only history data
224
+ history = import_data
225
+
226
+ return history, system_prompt.value # Return system prompt value to be set in the UI
227
+
228
+ with gr.Blocks(delete_cache=(86400, 86400)) as demo:
229
+ gr.Markdown("# OAI Chat (Nils' Version™️)")
230
+ with gr.Accordion("Startup"):
231
+ gr.Markdown("""Use of this interface permitted under the terms and conditions of the
232
+ [MIT license](https://github.com/ndurner/oai_chat/blob/main/LICENSE).
233
+ Third party terms and conditions apply, particularly
234
+ those of the LLM vendor (OpenAI) and hosting provider (Hugging Face).""")
235
+
236
+ oai_key = gr.Textbox(label="OpenAI API Key", elem_id="oai_key")
237
+ model = gr.Dropdown(label="Model", value="gpt-4-turbo", allow_custom_value=True, elem_id="model",
238
+ choices=["gpt-4-turbo", "gpt-4-turbo-preview", "gpt-4-1106-preview", "gpt-4", "gpt-4-vision-preview", "gpt-3.5-turbo", "gpt-3.5-turbo-16k", "gpt-3.5-turbo-1106", "whisper"])
239
+ system_prompt = gr.TextArea("You are a helpful yet diligent AI assistant. Answer faithfully and factually correct. Respond with 'I do not know' if uncertain.", label="System Prompt", lines=3, max_lines=250, elem_id="system_prompt")
240
+ seed = gr.Textbox(label="Seed", elem_id="seed")
241
+ temp = gr.Slider(0, 1, label="Temperature", elem_id="temp", value=1)
242
+ max_tokens = gr.Slider(1, 4000, label="Max. Tokens", elem_id="max_tokens", value=800)
243
+ save_button = gr.Button("Save Settings")
244
+ load_button = gr.Button("Load Settings")
245
+ dl_settings_button = gr.Button("Download Settings")
246
+ ul_settings_button = gr.Button("Upload Settings")
247
+
248
+ load_button.click(load_settings, js="""
249
+ () => {
250
+ let elems = ['#oai_key textarea', '#system_prompt textarea', '#seed textarea', '#temp input', '#max_tokens input', '#model'];
251
+ elems.forEach(elem => {
252
+ let item = document.querySelector(elem);
253
+ let event = new InputEvent('input', { bubbles: true });
254
+ item.value = localStorage.getItem(elem.split(" ")[0].slice(1)) || '';
255
+ item.dispatchEvent(event);
256
+ });
257
+ }
258
+ """)
259
+
260
+ save_button.click(save_settings, [oai_key, system_prompt, seed, temp, max_tokens, model], js="""
261
+ (oai, sys, seed, temp, ntok, model) => {
262
+ localStorage.setItem('oai_key', oai);
263
+ localStorage.setItem('system_prompt', sys);
264
+ localStorage.setItem('seed', seed);
265
+ localStorage.setItem('temp', document.querySelector('#temp input').value);
266
+ localStorage.setItem('max_tokens', document.querySelector('#max_tokens input').value);
267
+ localStorage.setItem('model', model);
268
+ }
269
+ """)
270
+
271
+ control_ids = [('oai_key', '#oai_key textarea'),
272
+ ('system_prompt', '#system_prompt textarea'),
273
+ ('seed', '#seed textarea'),
274
+ ('temp', '#temp input'),
275
+ ('max_tokens', '#max_tokens input'),
276
+ ('model', '#model')]
277
+ controls = [oai_key, system_prompt, seed, temp, max_tokens, model]
278
+
279
+ dl_settings_button.click(None, controls, js=generate_download_settings_js("oai_chat_settings.bin", control_ids))
280
+ ul_settings_button.click(None, None, None, js=generate_upload_settings_js(control_ids))
281
+
282
+ chatbot = gr.Chatbot(
283
+ [],
284
+ elem_id="chatbot",
285
+ show_copy_button=True,
286
+ height=350
287
+ )
288
+
289
+ with gr.Row():
290
+ btn = gr.UploadButton("📁 Upload", size="sm", file_count="multiple")
291
+ img_btn = gr.UploadButton("🖼️ Upload", size="sm", file_count="multiple", file_types=["image", "audio"])
292
+ undo_btn = gr.Button("↩️ Undo")
293
+ undo_btn.click(undo, inputs=[chatbot], outputs=[chatbot])
294
+
295
+ clear = gr.ClearButton(chatbot, value="🗑️ Clear")
296
+
297
+ with gr.Row():
298
+ txt = gr.TextArea(
299
+ scale=4,
300
+ show_label=False,
301
+ placeholder="Enter text and press enter, or upload a file",
302
+ container=False,
303
+ lines=3,
304
+ )
305
+ submit_btn = gr.Button("🚀 Send", scale=0)
306
+ submit_click = submit_btn.click(add_text, [chatbot, txt], [chatbot, txt], queue=False).then(
307
+ bot, [txt, chatbot, oai_key, system_prompt, seed, temp, max_tokens, model], [txt, chatbot],
308
+ )
309
+ submit_click.then(lambda: gr.Textbox(interactive=True), None, [txt], queue=False)
310
+
311
+ if dump_controls:
312
+ with gr.Row():
313
+ dmp_btn = gr.Button("Dump")
314
+ txt_dmp = gr.Textbox("Dump")
315
+ dmp_btn.click(dump, inputs=[chatbot], outputs=[txt_dmp])
316
+
317
+ txt_msg = txt.submit(add_text, [chatbot, txt], [chatbot, txt], queue=False).then(
318
+ bot, [txt, chatbot, oai_key, system_prompt, seed, temp, max_tokens, model], [txt, chatbot],
319
+ )
320
+ txt_msg.then(lambda: gr.Textbox(interactive=True), None, [txt], queue=False)
321
+ file_msg = btn.upload(add_file, [chatbot, btn], [chatbot], queue=False, postprocess=False)
322
+ img_msg = img_btn.upload(add_img, [chatbot, img_btn], [chatbot], queue=False, postprocess=False)
323
+
324
+ with gr.Accordion("Import/Export", open = False):
325
+ import_button = gr.UploadButton("History Import")
326
+ export_button = gr.Button("History Export")
327
+ export_button.click(lambda: None, [chatbot, system_prompt], js="""
328
+ (chat_history, system_prompt) => {
329
+ const export_data = {
330
+ history: chat_history,
331
+ system_prompt: system_prompt
332
+ };
333
+ const history_json = JSON.stringify(export_data);
334
+ const blob = new Blob([history_json], {type: 'application/json'});
335
+ const url = URL.createObjectURL(blob);
336
+ const a = document.createElement('a');
337
+ a.href = url;
338
+ a.download = 'chat_history.json';
339
+ document.body.appendChild(a);
340
+ a.click();
341
+ document.body.removeChild(a);
342
+ URL.revokeObjectURL(url);
343
+ }
344
+ """)
345
+ dl_button = gr.Button("File download")
346
+ dl_button.click(lambda: None, [chatbot], js="""
347
+ (chat_history) => {
348
+ // Attempt to extract content enclosed in backticks with an optional filename
349
+ const contentRegex = /```(\\S*\\.(\\S+))?\\n?([\\s\\S]*?)```/;
350
+ const match = contentRegex.exec(chat_history[chat_history.length - 1][1]);
351
+ if (match && match[3]) {
352
+ // Extract the content and the file extension
353
+ const content = match[3];
354
+ const fileExtension = match[2] || 'txt'; // Default to .txt if extension is not found
355
+ const filename = match[1] || `download.${fileExtension}`;
356
+ // Create a Blob from the content
357
+ const blob = new Blob([content], {type: `text/${fileExtension}`});
358
+ // Create a download link for the Blob
359
+ const url = URL.createObjectURL(blob);
360
+ const a = document.createElement('a');
361
+ a.href = url;
362
+ // If the filename from the chat history doesn't have an extension, append the default
363
+ a.download = filename.includes('.') ? filename : `${filename}.${fileExtension}`;
364
+ document.body.appendChild(a);
365
+ a.click();
366
+ document.body.removeChild(a);
367
+ URL.revokeObjectURL(url);
368
+ } else {
369
+ // Inform the user if the content is malformed or missing
370
+ alert('Sorry, the file content could not be found or is in an unrecognized format.');
371
+ }
372
+ }
373
+ """)
374
+ import_button.upload(import_history, inputs=[chatbot, import_button], outputs=[chatbot, system_prompt])
375
+
376
+ demo.unload(lambda: [os.remove(file) for file in temp_files])
377
+ demo.queue().launch()