darsoarafa commited on
Commit
555fc92
·
verified ·
1 Parent(s): 91cfc88

Upload 2 files

Browse files
Files changed (1) hide show
  1. app.py +528 -0
app.py ADDED
@@ -0,0 +1,528 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from __future__ import annotations as _annotations
2
+ import os
3
+ import asyncio
4
+ import json
5
+ import sqlite3
6
+ import datetime
7
+ import fastapi
8
+ import logfire
9
+ import time
10
+ from collections.abc import AsyncIterator
11
+ from concurrent.futures.thread import ThreadPoolExecutor
12
+ from contextlib import asynccontextmanager
13
+ from dataclasses import dataclass
14
+ from datetime import datetime, timezone, date
15
+ from functools import partial
16
+ from pathlib import Path
17
+ from typing import Annotated, Any, Callable, Literal, TypeVar
18
+
19
+ from pydantic import BaseModel, Field, ValidationError, model_validator
20
+ from typing import List, Optional, Dict
21
+
22
+ from fastapi import Depends, Request
23
+ from fastapi.responses import FileResponse, Response, StreamingResponse
24
+ from typing_extensions import LiteralString, ParamSpec, TypedDict
25
+
26
+ from pydantic_ai import Agent
27
+ from pydantic_ai.exceptions import UnexpectedModelBehavior
28
+ from pydantic_ai.messages import (
29
+ ModelMessage,
30
+ ModelMessagesTypeAdapter,
31
+ ModelRequest,
32
+ ModelResponse,
33
+ TextPart,
34
+ UserPromptPart,
35
+ )
36
+ from pydantic_ai.models.openai import OpenAIModel
37
+
38
+ model = OpenAIModel(
39
+ 'gemma-2-2b-it',
40
+ base_url='http://localhost:1234/v1',
41
+ api_key='your-local-api-key',
42
+ )
43
+ model11 = OpenAIModel(
44
+ 'mistral-7b-instruct-v0.3',
45
+ base_url='http://localhost:1234/v1',
46
+ api_key='your-local-api-key',
47
+ )
48
+
49
+ #DeepSeek Key: sk-694660c67c3947e4853019473f30240d https://api.deepseek.com
50
+ # Model Pydantic untuk Sales Order
51
+ class product1(BaseModel):
52
+ ds_productCode: Optional[str] = "" #str = Field(..., min_length=1, max_length=20)
53
+ ds_productName: str = Field(..., min_length=1, max_length=100)
54
+ ds_quantity: int = Field(..., ge=1)
55
+ ds_unitPrice: float = Field(..., ge=0)
56
+ ds_itemAmt: Optional[float] = 0
57
+
58
+ # 'if-token-present' means nothing will be sent (and the example will work) if you don't have logfire configured
59
+ logfire.configure(send_to_logfire='if-token-present')
60
+
61
+ class product(BaseModel):
62
+ ds_productCode: Optional[str] = "" #str = Field(..., min_length=1, max_length=20)
63
+ ds_productName: Optional[str] = ""
64
+ ds_quantity: Optional[int] = 0
65
+ ds_unitPrice: Optional[float] = 0
66
+ ds_itemAmt: Optional[float] = 0
67
+
68
+ class SalesOrder(BaseModel):
69
+ ds_salesOrderId: Optional[str] = ""
70
+ ds_salesDate: Optional[str] = ""
71
+ ds_customerName: str = Field(..., min_length=1, max_length=100)
72
+ ds_customerAddress: Optional[str] = ""
73
+ ds_items: List[product] = Field(..., min_items=1)
74
+ ds_shippingCost:Optional[float] = 0
75
+ ds_shippingAddress: Optional[str] = ""
76
+ ds_totalAmount:Optional[float] = 0
77
+ #ds_paymentTerms: Optional[str] = ""
78
+ os.environ['GEMINI_API_KEY'] = 'AIzaSyAsVIHsPIIfDBTb2K6VNdNlMt05t8x3mtE'
79
+ #agent= Agent('gemini-1.5-flash', result_type=SalesOrder)
80
+
81
+ # # Create a system prompt to guide the model
82
+ SYSTEM_PROMPT = """
83
+ You are a helper that extracts SalesOrder information from text and formats it as a SalesOrder class.
84
+ """
85
+ #agent3 = Agent(model=ollama_model, result_type=PetList, retries=3, system_prompt=SYSTEM_PROMPT)
86
+ #agent3 = Agent(model=ollama_model, retries=3, system_prompt=SYSTEM_PROMPT)
87
+
88
+
89
+
90
+ #agent = Agent('gemini-1.5-flash') # OK-1
91
+ agent = Agent(model) # OK-2
92
+
93
+
94
+
95
+ #agent = Agent(model, result_type=SalesOrder) # belum bisa untuk local
96
+ #agent = Agent(model, system_prompt=SYSTEM_PROMPT) # belum bisa untuk local
97
+ #agent= Agent('gemini-1.5-flash', system_prompt=SYSTEM_PROMPT) # ERR
98
+ THIS_DIR = Path(__file__).parent
99
+
100
+
101
+ @asynccontextmanager
102
+ async def lifespan(_app: fastapi.FastAPI):
103
+ async with Database.connect() as db:
104
+ yield {'db': db}
105
+
106
+
107
+ app = fastapi.FastAPI(lifespan=lifespan)
108
+ logfire.instrument_fastapi(app)
109
+
110
+
111
+ @app.get('/')
112
+ async def index() -> FileResponse:
113
+ return FileResponse((THIS_DIR / 'chat_app.html'), media_type='text/html')
114
+
115
+
116
+ @app.get('/chat_app.ts')
117
+ async def main_ts() -> FileResponse:
118
+ """Get the raw typescript code, it's compiled in the browser, forgive me."""
119
+ return FileResponse((THIS_DIR / 'chat_app.ts'), media_type='text/plain')
120
+
121
+
122
+ async def get_db(request: Request) -> Database:
123
+ return request.state.db
124
+
125
+ @app.get('/chat/')
126
+ async def get_chat(database: Database = Depends(get_db)) -> Response:
127
+ msgs = await database.get_messages()
128
+ return Response(
129
+ b'\n'.join(json.dumps(to_chat_message(m)).encode('utf-8') for m in msgs),
130
+ media_type='text/plain',
131
+ )
132
+
133
+
134
+
135
+ class ChatMessage(TypedDict):
136
+ """Format of messages sent to the browser."""
137
+
138
+ role: Literal['user', 'model']
139
+ timestamp: str
140
+ content: str
141
+
142
+
143
+ def to_chat_message(m: ModelMessage) -> ChatMessage:
144
+ first_part = m.parts[0]
145
+ if isinstance(m, ModelRequest):
146
+ if isinstance(first_part, UserPromptPart):
147
+ return {
148
+ 'role': 'user',
149
+ 'timestamp': first_part.timestamp.isoformat(),
150
+ 'content': first_part.content,
151
+ }
152
+ elif isinstance(m, ModelResponse):
153
+ if isinstance(first_part, TextPart):
154
+ return {
155
+ 'role': 'model',
156
+ 'timestamp': m.timestamp.isoformat(),
157
+ 'content': first_part.content,
158
+ }
159
+ raise UnexpectedModelBehavior(f'Unexpected message type for chat app: {m}')
160
+
161
+ def to_ds_message(m: ModelMessage) -> SalesOrder:
162
+ first_part = m.parts[0]
163
+ if isinstance(m, ModelRequest):
164
+ if isinstance(first_part, UserPromptPart):
165
+ return {
166
+ 'role': 'user',
167
+ 'timestamp': first_part.timestamp.isoformat(),
168
+ 'content': first_part.content,
169
+ }
170
+ elif isinstance(m, ModelResponse):
171
+ if isinstance(first_part, TextPart):
172
+ return {
173
+ 'role': 'model',
174
+ 'timestamp': m.timestamp.isoformat(),
175
+ 'content': first_part.content,
176
+ }
177
+ raise UnexpectedModelBehavior(f'Unexpected message type for chat app: {m}')
178
+
179
+ def to_form_message(m: ModelMessage) -> SalesOrder:
180
+ first_part = m.parts[0]
181
+ if isinstance(m, ModelResponse):
182
+ if isinstance(first_part, TextPart):
183
+ return {
184
+ 'role': 'form',
185
+ 'timestamp': m.timestamp.isoformat(),
186
+ 'content': first_part.content,
187
+ }
188
+ raise UnexpectedModelBehavior(f'Unexpected message type for chat app: {m}')
189
+ @app.post('/chat/')
190
+ async def post_chat(
191
+ prompt: Annotated[str, fastapi.Form()], database: Database = Depends(get_db)
192
+ ) -> StreamingResponse:
193
+ async def stream_messages():
194
+ """Streams new line delimited JSON `Message`s to the client."""
195
+ # stream the user prompt so that can be displayed straight away
196
+ yield (
197
+ json.dumps(
198
+ {
199
+ 'role': 'user',
200
+ 'timestamp': datetime.now(tz=timezone.utc).isoformat(),
201
+ 'content': prompt,
202
+ }
203
+ ).encode('utf-8')
204
+ + b'\n'
205
+ )
206
+ # get the chat history so far to pass as context to the agent
207
+ messages = await database.get_messages()
208
+ # run the agent with the user prompt and the chat history
209
+ async with agent.run_stream(prompt, message_history=messages) as result:
210
+ async for text in result.stream(debounce_by=0.01):
211
+ # text here is a `str` and the frontend wants
212
+ # JSON encoded ModelResponse, so we create one
213
+ m = ModelResponse.from_text(content=text, timestamp=result.timestamp())
214
+ yield json.dumps(to_chat_message(m)).encode('utf-8') + b'\n'
215
+
216
+ # add new messages (e.g. the user prompt and the agent response in this case) to the database
217
+ #print("---",result.new_messages_json(),"---")
218
+ #print("***",prompt,"***")
219
+ await database.add_messages(result.new_messages_json())
220
+ async def ds_messages(prompt1):
221
+ #Nama pembeli: Bu Lurah, alamat Bekasi Barat, hari ini membeli Teh Putih dua kaleng harga 110000 per kaleng, juga membeli Teh Hitam 3 kaleng, harga per kaleng 60000. Ongkos kirim ke Bekasi Barat sebesar 36 ribu
222
+ #nama pembeli Mas Anang alamat di Jalan Cisitu no.5 Bandung, membeli Chocobar 5 batang harga 15 ribu per batang, dan membeli Rice Cracker 4 buah harga 20 ribu per buah, ongkos kirim ke Jalan Cisitu no.5 sebesar 7 ribu rupiah
223
+ try:
224
+ prompt2=f"Ekstrak data Sales Order dari teks: {prompt1}. Format output yang diinginkan hanya berupa JSON saja sesuai class Sales Order. Tidak usah ada penjelasan lain. Sekali lagi: Output hanya JSON saja. Hari ini adalah tanggal {date.today()}. untuk Nomor Sales Order pasangkan dengan key ds_salesOrderId, untuk Tanggal Sales pasangkan dengan key ds_salesDate, untuk Nama Customer pasangkan dengan Key ds_customerName, untuk Alamat Customer pasangkan dengan key ds_customerAddress, untuk Daftar Item Barang pasangkan dengan key ds_items, untuk Kode Barang pasangkan dengan key ds_productCode, untuk Nama Barang pasangkan dengan key ds_productName, untuk Quantity pasangkan dengan key ds_quantity, untuk Unit Price pasangkan dengan key ds_unitPrice, untuk Total Nilai Per Baris Barang pasangkan dengan key ds_itemAmt. Setelah Daftar Item Barang, selanjutnya untuk Ongkos Kirim pasangkan dengan key ds_shippingCost, untuk Alamat Pengiriman pasangkan dengan key ds_shippingAddress, untuk Total Nilai Sales Order pasangkan dengan key ds_totalAmount"
225
+ #prompt2=f"Ekstrak data Sales Order dari teks: {prompt1}. Hari ini adalah tanggal {date.today()}"
226
+ yield (
227
+ json.dumps(
228
+ {
229
+ 'role': 'user',
230
+ 'timestamp': datetime.now(tz=timezone.utc).isoformat(),
231
+ 'content': prompt1,
232
+ }
233
+ ).encode('utf-8')
234
+ + b'\n'
235
+ )
236
+ messages = await database.get_messages()
237
+ async with agent.run_stream(prompt2, message_history=messages) as result:
238
+ async for text in result.stream(debounce_by=0.1):
239
+ m = ModelResponse.from_text(content=text, timestamp=result.timestamp())
240
+ yield json.dumps(to_ds_message(m)).encode('utf-8') + b'\n'
241
+
242
+ ##print(result.usage())
243
+ #await database.add_messages(result.new_messages_json())
244
+ darso = json.loads(result.new_messages_json())
245
+ darso1= darso[1]
246
+ #print("1|", darso1)
247
+ darso2= json.loads(json.dumps(darso1))
248
+ #print("2|",darso2['parts'][0])
249
+ darso3= darso2['parts'][0]
250
+ darso4= json.loads(json.dumps(darso3))
251
+ #print("4|",darso4['content'])
252
+ darso5= darso4['content']
253
+ darso5=darso5.split('```', 2)
254
+ darso5=darso5[1]
255
+ #print("5a|",darso5)
256
+ darso5=darso5.replace('json', '')
257
+ print("5|",darso5,"|")
258
+ try:
259
+ darso6= json.loads(darso5) #json
260
+ darso7= SalesOrder.model_validate(darso6)
261
+ except:
262
+ darso6= "ERR"
263
+ print("6|",darso6,"|")
264
+ if "ds_items" in darso5:
265
+ cek_str="ds_items"
266
+ else:
267
+ cek_str="--"
268
+ if darso6=="ERR":
269
+ ds_id = time.time()
270
+ ds_salesOrderId = "ERR"
271
+ ds_salesDate = 'ERR'
272
+ ds_customerName="-"
273
+ ds_customerAddress="-"
274
+
275
+ ds_productName1 = "Produk1 --- "
276
+ ds_quantity1 = 1
277
+ ds_unitPrice1 = 0
278
+ ds_itemAmt1 = 0
279
+
280
+ ds_productName2 = "Produk2 --- "
281
+ ds_quantity2 = 0
282
+ ds_unitPrice2 = 0
283
+ ds_itemAmt2 = 0
284
+
285
+ ds_productName3 = "Produk3 --- "
286
+ ds_quantity3 = 0
287
+ ds_unitPrice3 = 0
288
+ ds_itemAmt3 = 0
289
+ ds_shippingAddress=""
290
+ ds_shippingCost=0
291
+ ds_totalAmount=0
292
+ else:
293
+ ds_id = time.time()
294
+ ds_salesOrderId = "OK"
295
+ ds_salesDate = 'OK'
296
+ try:
297
+ ds_salesOrderId = darso7.ds_salesOrderId
298
+ print("7|ds_salesOrderId")
299
+ ds_salesDate = darso7.ds_salesDate
300
+ print("7|ds_salesDate")
301
+ ds_customerName=f"""{darso7.ds_customerName}"""
302
+ print("7|ds_customerName:",ds_customerName)
303
+ ds_customerAddress=f"""{darso7.ds_customerAddress}"""
304
+ print("7|ds_customerAddress:", len(darso7.ds_items))
305
+ ds_productName1 = darso7.ds_items[0].ds_productName
306
+ print("7|ds_productName1")
307
+ ds_quantity1 = darso7.ds_items[0].ds_quantity
308
+ print("7|ds_quantity1")
309
+ ds_unitPrice1 = darso7.ds_items[0].ds_unitPrice
310
+ print("7|ds_unitPrice1")
311
+ ds_itemAmt1 = darso7.ds_items[0].ds_itemAmt
312
+ print("7|ds_itemAmt1")
313
+ ds_productName2 = "-"
314
+ ds_quantity2 = 0
315
+ ds_unitPrice2 = 0
316
+ ds_itemAmt2 = 0
317
+ ds_productName3 = "-"
318
+ ds_quantity3 = 0
319
+ ds_unitPrice3 = 0
320
+ ds_itemAmt3 = 0
321
+
322
+ if len(darso7.ds_items)>1:
323
+ ds_productName2 = darso7.ds_items[1].ds_productName
324
+ ds_quantity2 = darso7.ds_items[1].ds_quantity
325
+ ds_unitPrice2 = darso7.ds_items[1].ds_unitPrice
326
+ ds_itemAmt2 = darso7.ds_items[1].ds_itemAmt
327
+ if len(darso7.ds_items)>2:
328
+ ds_productName3 = darso7.ds_items[2].ds_productName
329
+ ds_quantity3 = darso7.ds_items[2].ds_quantity
330
+ ds_unitPrice3 = darso7.ds_items[2].ds_unitPrice
331
+ ds_itemAmt3 = darso7.ds_items[2].ds_itemAmt
332
+
333
+ ds_shippingCost=darso7.ds_shippingCost
334
+ print("7|ds_shippingCost")
335
+ ds_shippingAddress=f"""{darso7.ds_shippingAddress}"""
336
+ print("7|ds_shippingAddress")
337
+ ds_totalAmount=darso7.ds_totalAmount
338
+ print("7|ds_totalAmount")
339
+ except:
340
+ ds_salesOrderId = "OK2"
341
+ ds_salesDate = 'OK2'
342
+ ds_customerName="-"
343
+ ds_customerAddress="-"
344
+
345
+ ds_productName1 = "Produk1"
346
+ ds_quantity1 = 0
347
+ ds_unitPrice1 = 0
348
+ ds_itemAmt1 = 0
349
+
350
+ ds_productName2 = "Produk2"
351
+ ds_quantity2 = 0
352
+ ds_unitPrice2 = 0
353
+ ds_itemAmt2 = 0
354
+
355
+ ds_productName3 = "Produk3"
356
+ ds_quantity3 = 0
357
+ ds_unitPrice3 = 0
358
+ ds_itemAmt3 = 0
359
+ ds_shippingAddress=""
360
+ ds_shippingCost=0
361
+ ds_totalAmount=0
362
+
363
+ formDs = f"""
364
+
365
+ <form id="myaiForm{ds_id}" action="javascript:abcde({ds_id});" class="form-container">
366
+ <h3>Pesanan</h3>
367
+ <table>
368
+ <tr>
369
+ <td><label for="ds_salesOrderId"><b>SO#</b></label><input type="text" placeholder="" name="ds_salesOrderId" value="{ds_salesOrderId}"></td>
370
+ <td><label for="ds_salesDate"><b>Date</b></label><input type="text" placeholder="" name="ds_salesDate" value="{ds_salesDate}"></td>
371
+ </tr>
372
+ <tr>
373
+ <td colspan="2"><label for="ds_customerName"><b>Customer</b></label><input type="text" placeholder="" name="ds_customerName" value="{ds_customerName}"></td>
374
+ </tr>
375
+ <tr>
376
+ <td colspan="2"><label for="ds_customerAddress"><b>Alamat</b></label><input type="text" placeholder="" name="ds_customerAddress" value="{ds_customerAddress}"></td>
377
+ </tr>
378
+ </table style="width:100%">
379
+ <b>Item Barang:</b>
380
+ <table>
381
+ <tr><th>Prod</th><th style="text-align: center;">Qty</th><th style="text-align: center;">Prc</th><th style="text-align: center;">Rp</th></tr>
382
+ <tr>
383
+ <td><input type="text" placeholder="-" name="ds_productName1" value="{ds_productName1}"></td>
384
+ <td><input type="text" style="text-align: center;" name="ds_quantity1" value={ds_quantity1}></td>
385
+ <td><input type="text" style="text-align: center;" name="ds_unitPrice1" value={ds_unitPrice1}></td>
386
+ <td><input type="text" style="text-align: center;" name="ds_itemAmt1" value={ds_itemAmt1}></td>
387
+ </tr>
388
+ <tr>
389
+ <td><input type="text" placeholder="-" name="ds_productName2" value="{ds_productName2}"></td>
390
+ <td><input type="text" style="text-align: center;" name="ds_quantity2" value={ds_quantity2}></td>
391
+ <td><input type="text" style="text-align: center;" name="ds_unitPrice2" value={ds_unitPrice2}></td>
392
+ <td><input type="text" style="text-align: center;" name="ds_itemAmt2" value={ds_itemAmt2}></td>
393
+ </tr>
394
+ <tr>
395
+ <td><input type="text" placeholder="-" name="ds_productName3" value="{ds_productName3}"></td>
396
+ <td><input type="text" style="text-align: center;" name="ds_quantity3" value={ds_quantity3}></td>
397
+ <td><input type="text" style="text-align: center;" name="ds_unitPrice3" value={ds_unitPrice3}></td>
398
+ <td><input type="text" style="text-align: center;" name="ds_itemAmt3" value={ds_itemAmt3}></td>
399
+ </tr>
400
+ </table>
401
+ <table>
402
+ <tr>
403
+ <td style="text-align: center;"><b>Ongkir</b></td>
404
+ <td style="text-align: center;"><b>Total</b></td>
405
+ </tr>
406
+ <tr>
407
+ <td><input type="text" style="text-align: center;" placeholder="0" name="ds_shippingCost" value={ds_shippingCost}></td>
408
+ <td><input type="text" style="text-align: center;" placeholder="0" name="ds_totalAmount" value={ds_totalAmount}></td>
409
+ </tr>
410
+ <tr>
411
+ <td colspan="2"><label for="ds_shippingAddress"><b></b></label><input type="text" placeholder="" name="ds_shippingAddress" value="{ds_shippingAddress}"></td>
412
+ </tr>
413
+ </table>
414
+ <button type="submit" class="btn">Submit</button>
415
+ <button type="button" class="btn cancel" onclick="closeAiForm({ds_id})">Close</button>
416
+ </form>
417
+ <form id="myaiForm2{ds_id}" class="form-container" style="display:none;">
418
+ <button type="button" class="btn umum" onclick="openAiForm({ds_id})">Open Form</button>
419
+ </form>
420
+ """
421
+ m = ModelResponse.from_text(content=formDs, timestamp=result.timestamp())
422
+ yield json.dumps(to_form_message(m)).encode('utf-8') + b'\n'
423
+ print("OK")
424
+ ##print(len(items))
425
+ #darso7 = SalesOrder.model_validate(darso6)
426
+ #print("[--",darso7.ds_customerName,"--]")
427
+ #darso8 = darso7.ds_items[0]
428
+ ##, len(darso7.ds_items)
429
+ #print("[--",darso8.ds_productName,"--]")
430
+ except ValueError as e:
431
+ print(e)
432
+ if prompt[0] == "@" :
433
+ #print("@@@", prompt, "@@@")
434
+ nn = len(prompt)
435
+ prompt = prompt[1:nn]
436
+ print(">>>", prompt, "<<<")
437
+ return StreamingResponse(ds_messages(prompt), media_type='text/plain')
438
+ elif prompt[0] != "@" :
439
+ print("biasa")
440
+ return StreamingResponse(stream_messages(), media_type='text/plain')
441
+ print("** selesai **")
442
+ return StreamingResponse(stream_messages(), media_type='text/plain')
443
+
444
+ P = ParamSpec('P')
445
+ R = TypeVar('R')
446
+
447
+
448
+ @dataclass
449
+ class Database:
450
+ """Rudimentary database to store chat messages in SQLite.
451
+
452
+ The SQLite standard library package is synchronous, so we
453
+ use a thread pool executor to run queries asynchronously.
454
+ """
455
+
456
+ con: sqlite3.Connection
457
+ _loop: asyncio.AbstractEventLoop
458
+ _executor: ThreadPoolExecutor
459
+
460
+ @classmethod
461
+ @asynccontextmanager
462
+ async def connect(
463
+ cls, file: Path = THIS_DIR / '.chat_messages.sqlite'
464
+ ) -> AsyncIterator[Database]:
465
+ with logfire.span('connect to DB'):
466
+ loop = asyncio.get_event_loop()
467
+ executor = ThreadPoolExecutor(max_workers=1)
468
+ con = await loop.run_in_executor(executor, cls._connect, file)
469
+ slf = cls(con, loop, executor)
470
+ try:
471
+ yield slf
472
+ finally:
473
+ await slf._asyncify(con.close)
474
+
475
+ @staticmethod
476
+ def _connect(file: Path) -> sqlite3.Connection:
477
+ con = sqlite3.connect(str(file))
478
+ con = logfire.instrument_sqlite3(con)
479
+ cur = con.cursor()
480
+ cur.execute(
481
+ 'CREATE TABLE IF NOT EXISTS messages (id INT PRIMARY KEY, message_list TEXT);'
482
+ )
483
+ con.commit()
484
+ return con
485
+
486
+ async def add_messages(self, messages: bytes):
487
+ await self._asyncify(
488
+ self._execute,
489
+ 'INSERT INTO messages (message_list) VALUES (?);',
490
+ messages,
491
+ commit=True,
492
+ )
493
+ await self._asyncify(self.con.commit)
494
+
495
+ async def get_messages(self) -> list[ModelMessage]:
496
+ c = await self._asyncify(
497
+ self._execute, 'SELECT message_list FROM messages order by id asc'
498
+ )
499
+ rows = await self._asyncify(c.fetchall)
500
+ messages: list[ModelMessage] = []
501
+ for row in rows:
502
+ messages.extend(ModelMessagesTypeAdapter.validate_json(row[0]))
503
+ return messages
504
+
505
+ def _execute(
506
+ self, sql: LiteralString, *args: Any, commit: bool = False
507
+ ) -> sqlite3.Cursor:
508
+ cur = self.con.cursor()
509
+ cur.execute(sql, args)
510
+ if commit:
511
+ self.con.commit()
512
+ return cur
513
+
514
+ async def _asyncify(
515
+ self, func: Callable[P, R], *args: P.args, **kwargs: P.kwargs
516
+ ) -> R:
517
+ return await self._loop.run_in_executor( # type: ignore
518
+ self._executor,
519
+ partial(func, **kwargs),
520
+ *args, # type: ignore
521
+ )
522
+
523
+
524
+ if __name__ == '__main__':
525
+ import uvicorn
526
+ uvicorn.run(
527
+ 'app:app', reload=True, reload_dirs=[str(THIS_DIR)]
528
+ )