Deadmon commited on
Commit
1004b9c
·
verified ·
1 Parent(s): 054eef1

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +362 -0
app.py ADDED
@@ -0,0 +1,362 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ```python
2
+ import gradio as gr
3
+ import asyncio
4
+ import json
5
+ import html
6
+ from openai import AzureOpenAI
7
+ from azure.identity import DefaultAzureCredential, get_bearer_token_provider
8
+ from tiktoken import get_encoding
9
+ import sqlite3
10
+ import uuid
11
+ import datetime
12
+ import difflib
13
+
14
+ # Reusing ConversationMemory and TextEditor classes from previous artifact
15
+ class ConversationMemory:
16
+ def __init__(self, db_path="conversation.db"):
17
+ self.conn = sqlite3.connect(db_path)
18
+ self.create_table()
19
+ self.tokenizer = get_encoding("cl100k_base")
20
+
21
+ def create_table(self):
22
+ self.conn.execute("""
23
+ CREATE TABLE IF NOT EXISTS conversation_chunks (
24
+ chunk_id TEXT PRIMARY KEY,
25
+ text TEXT,
26
+ role TEXT,
27
+ timestamp DATETIME,
28
+ intent TEXT,
29
+ token_count INTEGER,
30
+ embedding BLOB
31
+ )
32
+ """)
33
+ self.conn.commit()
34
+
35
+ def add_chunk(self, text, role, intent="general"):
36
+ chunk_id = str(uuid.uuid4())
37
+ tokens = self.tokenizer.encode(text)
38
+ token_count = len(tokens)
39
+ timestamp = datetime.datetime.now().isoformat()
40
+ self.conn.execute("""
41
+ INSERT INTO conversation_chunks (chunk_id, text, role, timestamp, intent, token_count)
42
+ VALUES (?, ?, ?, ?, ?, ?)
43
+ """, (chunk_id, text, role, timestamp, intent, token_count))
44
+ self.conn.commit()
45
+ return chunk_id
46
+
47
+ def get_chunk(self, chunk_id):
48
+ cursor = self.conn.execute("SELECT * FROM conversation_chunks WHERE chunk_id = ?", (chunk_id,))
49
+ row = cursor.fetchone()
50
+ if row:
51
+ return {
52
+ "chunk_id": row[0], "text": row[1], "role": row[2],
53
+ "timestamp": row[3], "intent": row[4], "token_count": row[5]
54
+ }
55
+ return None
56
+
57
+ def update_chunk(self, chunk_id, text):
58
+ tokens = self.tokenizer.encode(text)
59
+ token_count = len(tokens)
60
+ self.conn.execute("""
61
+ UPDATE conversation_chunks SET text = ?, token_count = ?
62
+ WHERE chunk_id = ?
63
+ """, (text, token_count, chunk_id))
64
+ self.conn.commit()
65
+
66
+ def get_recent_chunks(self, limit=10):
67
+ cursor = self.conn.execute("SELECT * FROM conversation_chunks ORDER BY timestamp DESC LIMIT ?", (limit,))
68
+ return [{"chunk_id": row[0], "text": row[1], "role": row[2], "timestamp": row[3], "intent": row[4], "token_count": row[5]} for row in cursor]
69
+
70
+ class TextEditor:
71
+ def __init__(self, memory):
72
+ self.memory = memory
73
+ self.clipboard = ""
74
+
75
+ def cut(self, chunk_id, start, end):
76
+ chunk = self.memory.get_chunk(chunk_id)
77
+ if chunk:
78
+ self.clipboard = chunk['text'][start:end]
79
+ chunk['text'] = chunk['text'][:start] + chunk['text'][end:]
80
+ self.memory.update_chunk(chunk_id, chunk['text'])
81
+ return chunk['text']
82
+
83
+ def copy(self, chunk_id, start, end):
84
+ chunk = self.memory.get_chunk(chunk_id)
85
+ if chunk:
86
+ self.clipboard = chunk['text'][start:end]
87
+ return self.clipboard
88
+
89
+ def paste(self, chunk_id, position):
90
+ chunk = self.memory.get_chunk(chunk_id)
91
+ if chunk:
92
+ chunk['text'] = chunk['text'][:position] + self.clipboard + chunk['text'][position:]
93
+ self.memory.update_chunk(chunk_id, chunk['text'])
94
+ return chunk['text']
95
+
96
+ def add_prefix(self, chunk_id, prefix):
97
+ chunk = self.memory.get_chunk(chunk_id)
98
+ if chunk:
99
+ chunk['text'] = prefix + chunk['text']
100
+ self.memory.update_chunk(chunk_id, chunk['text'])
101
+ return chunk['text']
102
+
103
+ def add_suffix(self, chunk_id, suffix):
104
+ chunk = self.memory.get_chunk(chunk_id)
105
+ if chunk:
106
+ chunk['text'] = chunk['text'] + suffix
107
+ self.memory.update_chunk(chunk_id, chunk['text'])
108
+ return chunk['text']
109
+
110
+ def diff(self, chunk_id, original_text):
111
+ chunk = self.memory.get_chunk(chunk_id)
112
+ if chunk:
113
+ differ = difflib.Differ()
114
+ diff = list(differ.compare(original_text.splitlines(), chunk['text'].splitlines()))
115
+ return '\n'.join(diff)
116
+ return ""
117
+
118
+ class OpenAIApi:
119
+ def __init__(self, preprompt="", endpoint="https://T-App-GPT4o.openai.azure.com/openai/v1/", model="gpt-4o"):
120
+ token_provider = get_bearer_token_provider(
121
+ DefaultAzureCredential(), "https://cognitiveservices.azure.com/.default"
122
+ )
123
+ self.client = AzureOpenAI(
124
+ azure_endpoint=endpoint,
125
+ azure_ad_token_provider=token_provider,
126
+ api_version="2025-01-01-preview"
127
+ )
128
+ self.model = model
129
+ self.preprompt = preprompt
130
+ self.memory = ConversationMemory()
131
+ self.editor = TextEditor(self.memory)
132
+ self.functions = [
133
+ {
134
+ "type": "function",
135
+ "name": "cut_text",
136
+ "description": "Cut text from a conversation chunk.",
137
+ "parameters": {
138
+ "type": "object",
139
+ "properties": {
140
+ "chunk_id": {"type": "string", "description": "ID of the conversation chunk"},
141
+ "start": {"type": "integer", "description": "Start index"},
142
+ "end": {"type": "integer", "description": "End index"}
143
+ },
144
+ "required": ["chunk_id", "start", "end"]
145
+ }
146
+ },
147
+ {
148
+ "type": "function",
149
+ "name": "copy_text",
150
+ "description": "Copy text from a conversation chunk to clipboard.",
151
+ "parameters": {
152
+ "type": "object",
153
+ "properties": {
154
+ "chunk_id": {"type": "string", "description": "ID of the conversation chunk"},
155
+ "start": {"type": "integer", "description": "Start index"},
156
+ "end": {"type": "integer", "description": "End index"}
157
+ },
158
+ "required": ["chunk_id", "start", "end"]
159
+ }
160
+ },
161
+ {
162
+ "type": "function",
163
+ "name": "paste_text",
164
+ "description": "Paste clipboard content into a conversation chunk.",
165
+ "parameters": {
166
+ "type": "object",
167
+ "properties": {
168
+ "chunk_id": {"type": "string", "description": "ID of the conversation chunk"},
169
+ "position": {"type": "integer", "description": "Position to paste"}
170
+ },
171
+ "required": ["chunk_id", "position"]
172
+ }
173
+ },
174
+ {
175
+ "type": "function",
176
+ "name": "add_prefix",
177
+ "description": "Add a prefix to a conversation chunk.",
178
+ "parameters": {
179
+ "type": "object",
180
+ "properties": {
181
+ "chunk_id": {"type": "string", "description": "ID of the conversation chunk"},
182
+ "prefix": {"type": "string", "description": "Prefix to add"}
183
+ },
184
+ "required": ["chunk_id", "prefix"]
185
+ }
186
+ },
187
+ {
188
+ "type": "function",
189
+ "name": "add_suffix",
190
+ "description": "Add a suffix to a conversation chunk.",
191
+ "parameters": {
192
+ "type": "object",
193
+ "properties": {
194
+ "chunk_id": {"type": "string", "description": "ID of the conversation chunk"},
195
+ "suffix": {"type": "string", "description": "Suffix to add"}
196
+ },
197
+ "required": ["chunk_id", "suffix"]
198
+ }
199
+ }
200
+ ]
201
+
202
+ async def fetch_response(self, raw_prompt, continue_response=False):
203
+ sanitized_prompt = html.escape(raw_prompt.strip())
204
+ chunk_id = self.memory.add_chunk(sanitized_prompt, "user")
205
+
206
+ messages = []
207
+ if self.preprompt:
208
+ messages.append({"role": "system", "content": self.preprompt})
209
+ context = self.memory.get_recent_chunks(limit=5)
210
+ messages.extend({"role": c["role"], "content": c["text"]} for c in context)
211
+ messages.append({"role": "user", "content": sanitized_prompt})
212
+
213
+ try:
214
+ response = await self.client.chat.completions.create(
215
+ model=self.model,
216
+ messages=messages,
217
+ temperature=0.5,
218
+ max_tokens=4000,
219
+ top_p=1.0,
220
+ frequency_penalty=0,
221
+ presence_penalty=0,
222
+ tools=self.functions,
223
+ stream=True
224
+ )
225
+
226
+ full_response = ""
227
+ tool_calls = []
228
+ async for chunk in response:
229
+ if chunk.choices and chunk.choices[0].delta.content:
230
+ full_response += chunk.choices[0].delta.content
231
+ if chunk.choices and chunk.choices[0].delta.tool_calls:
232
+ tool_calls.extend(chunk.choices[0].delta.tool_calls)
233
+
234
+ response_chunk_id = self.memory.add_chunk(full_response, "assistant")
235
+
236
+ for tool_call in tool_calls:
237
+ if tool_call.type == "function":
238
+ func_name = tool_call.function.name
239
+ args = json.loads(tool_call.function.arguments)
240
+ if func_name == "cut_text":
241
+ result = self.editor.cut(args["chunk_id"], args["start"], args["end"])
242
+ self.memory.add_chunk(f"Cut result: {result}", "system")
243
+ elif func_name == "copy_text":
244
+ result = self.editor.copy(args["chunk_id"], args["start"], args["end"])
245
+ self.memory.add_chunk(f"Copy result: {result}", "system")
246
+ elif func_name == "paste_text":
247
+ result = self.editor.paste(args["chunk_id"], args["position"])
248
+ self.memory.add_chunk(f"Paste result: {result}", "system")
249
+ elif func_name == "add_prefix":
250
+ result = self.editor.add_prefix(args["chunk_id"], args["prefix"])
251
+ self.memory.add_chunk(f"Prefix result: {result}", "system")
252
+ elif func_name == "add_suffix":
253
+ result = self.editor.add_suffix(args["chunk_id"], args["suffix"])
254
+ self.memory.add_chunk(f"Suffix result: {result}", "system")
255
+
256
+ continue_flag = len(self.memory.tokenizer.encode(full_response)) >= 4000
257
+
258
+ return {"content": full_response, "continue": continue_flag, "chunk_id": response_chunk_id}
259
+
260
+ except Exception as e:
261
+ error_msg = f"API Error: {str(e)}"
262
+ self.memory.add_chunk(error_msg, "system")
263
+ return {"error": error_msg}
264
+
265
+ # Gradio UI
266
+ async def chat_submit(user_input, chat_history, preprompt):
267
+ api = OpenAIApi(preprompt=preprompt)
268
+ response = await api.fetch_response(user_input)
269
+ if "error" in response:
270
+ return chat_history + [[user_input, f"Error: {response['error']}"]], preprompt
271
+ chat_history.append([user_input, response["content"]])
272
+ return chat_history, preprompt
273
+
274
+ def get_history():
275
+ memory = ConversationMemory()
276
+ return memory.get_recent_chunks(limit=10)
277
+
278
+ def select_chunk(evt: gr.SelectData):
279
+ return evt.value["chunk_id"], evt.value["text"]
280
+
281
+ async def edit_cut(chunk_id, start, end):
282
+ api = OpenAIApi()
283
+ result = api.editor.cut(chunk_id, int(start), int(end))
284
+ return result, api.editor.diff(chunk_id, result)
285
+
286
+ async def edit_copy(chunk_id, start, end):
287
+ api = OpenAIApi()
288
+ result = api.editor.copy(chunk_id, int(start), int(end))
289
+ return result, ""
290
+
291
+ async def edit_paste(chunk_id, position):
292
+ api = OpenAIApi()
293
+ result = api.editor.paste(chunk_id, int(position))
294
+ return result, api.editor.diff(chunk_id, result)
295
+
296
+ async def edit_prefix(chunk_id, prefix):
297
+ api = OpenAIApi()
298
+ result = api.editor.add_prefix(chunk_id, prefix)
299
+ return result, api.editor.diff(chunk_id, result)
300
+
301
+ async def edit_suffix(chunk_id, suffix):
302
+ api = OpenAIApi()
303
+ result = api.editor.add_suffix(chunk_id, suffix)
304
+ return result, api.editor.diff(chunk_id, result)
305
+
306
+ def create_ui():
307
+ with gr.Blocks(title="Azure OpenAI Chat & Text Editor") as demo:
308
+ gr.Markdown("# Azure OpenAI Chat with Text Editing")
309
+
310
+ with gr.Tab("Chat"):
311
+ chatbot = gr.Chatbot(label="Conversation")
312
+ user_input = gr.Textbox(label="Your Message", placeholder="Type your message or editing command...")
313
+ preprompt = gr.Textbox(label="System Prompt", value="You are a helpful assistant with text editing capabilities.")
314
+ submit_btn = gr.Button("Send")
315
+ submit_btn.click(
316
+ fn=chat_submit,
317
+ inputs=[user_input, chatbot, preprompt],
318
+ outputs=[chatbot, preprompt]
319
+ )
320
+
321
+ with gr.Tab("Conversation History"):
322
+ history = gr.Dataframe(
323
+ label="Recent Chunks",
324
+ headers=["chunk_id", "text", "role", "timestamp", "intent", "token_count"],
325
+ datatype=["str", "str", "str", "str", "str", "number"]
326
+ )
327
+ history_btn = gr.Button("Refresh History")
328
+ history_btn.click(fn=get_history, outputs=history)
329
+
330
+ with gr.Tab("Text Editor"):
331
+ chunk_id = gr.Textbox(label="Selected Chunk ID")
332
+ chunk_text = gr.Textbox(label="Chunk Text", interactive=False)
333
+ history.select(fn=select_chunk, outputs=[chunk_id, chunk_text])
334
+
335
+ with gr.Row():
336
+ start = gr.Number(label="Start Index", precision=0)
337
+ end = gr.Number(label="End Index", precision=0)
338
+ position = gr.Number(label="Paste Position", precision=0)
339
+ with gr.Row():
340
+ prefix = gr.Textbox(label="Prefix")
341
+ suffix = gr.Textbox(label="Suffix")
342
+ with gr.Row():
343
+ cut_btn = gr.Button("Cut")
344
+ copy_btn = gr.Button("Copy")
345
+ paste_btn = gr.Button("Paste")
346
+ prefix_btn = gr.Button("Add Prefix")
347
+ suffix_btn = gr.Button("Add Suffix")
348
+ diff_output = gr.Textbox(label="Diff Output", interactive=False)
349
+
350
+ cut_btn.click(fn=edit_cut, inputs=[chunk_id, start, end], outputs=[chunk_text, diff_output])
351
+ copy_btn.click(fn=edit_copy, inputs=[chunk_id, start, end], outputs=[chunk_text, diff_output])
352
+ paste_btn.click(fn=edit_paste, inputs=[chunk_id, position], outputs=[chunk_text, diff_output])
353
+ prefix_btn.click(fn=edit_prefix, inputs=[chunk_id, prefix], outputs=[chunk_text, diff_output])
354
+ suffix_btn.click(fn=edit_suffix, inputs=[chunk_id, suffix], outputs=[chunk_text, diff_output])
355
+
356
+ gr.Markdown(f"Current Time: {datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S %Z')}")
357
+
358
+ return demo
359
+
360
+ if __name__ == "__main__":
361
+ demo = create_ui()
362
+ demo.launch()