darsoarafa commited on
Commit
6b6c410
·
verified ·
1 Parent(s): acfe0e8

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +633 -0
  2. produk.html +418 -0
app.py ADDED
@@ -0,0 +1,633 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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, ModelRetry, RunContext
27
+
28
+ from pydantic_ai.exceptions import UnexpectedModelBehavior
29
+ from pydantic_ai.messages import (
30
+ ModelMessage,
31
+ ModelMessagesTypeAdapter,
32
+ ModelRequest,
33
+ ModelResponse,
34
+ TextPart,
35
+ UserPromptPart,
36
+ )
37
+ from pydantic_ai.models.openai import OpenAIModel
38
+ #from pydantic_ai import OpenAIEmbeddings
39
+
40
+ #from sentence_transformers import SentenceTransformer
41
+ #embedding_model = SentenceTransformer("all-mpnet-base-v2")
42
+
43
+ class EmbeddingRequest(BaseModel):
44
+ text: str
45
+
46
+ class EmbeddingResponse(BaseModel):
47
+ embeddings: List[float]
48
+
49
+ abc = """
50
+ Anda adalah pelayan yang ramah, mampu memberikan info tentang Toko Teh "Arafatea", yaitu toko yang menjual 4 Kategori Produk yaitu (1) Teh Premium, (2) Teh Herbal, (3) Camilan/Snack dan (4) Buah Kering. Untuk Kategori Teh Premium ada 5 produk yaitu Teh Hitam, Teh Hijau, Teh Putih, Teh Genmaicha dan Matcha. Dalam kategori Teh Herbal ada 5 produk yaitu Pandan Tea, Mint Tea, Orange Tea, Teh Buah, dan Teh Rempah. Untuk kategori "Snack" meliputi 4 produk yaitu Greentea-Chocobar, Coffee-Chocobar, Rice-Cracker-Greentea, Rice-Cracker-Coffee; Untuk kategori "Buah Kering" meliputi 8 nama produk meliputi Buah Kering Mangga, Buah Kering Nanas, Buah Kering Lemon, dan Buah Kering Strawberry; Jus Mangga Kering, Jus Nanas Kering, Jus Buah Naga Kering, dan Jus Stawberry Kering. Tidak ada produk selain produk yang masuk 4 kategori tersebut. Alamat website www.arafatea.com, atau www.shopee.co.id/arafatea nomor telepon toko Arafatea adalah 08170218404
51
+ """
52
+
53
+
54
+
55
+
56
+ #await onFetchResponse(response)
57
+ #get_embeddings({"text":abc})
58
+
59
+
60
+ model = OpenAIModel(
61
+ 'gemma-2-2b-it', #'llama-3.2-1b-instruct',
62
+ base_url='http://localhost:1234/v1',
63
+ api_key='your-local-api-key',
64
+ )
65
+ model2 = OpenAIModel(
66
+ 'gemma-2-2b-it', #'llama-3.2-1b-instruct',
67
+ base_url='http://localhost:52199/api/v1/openai',
68
+ api_key='D6K06DS-P474SK6-P2ACYYC-K5516S1',
69
+ )
70
+ model1 = OpenAIModel(
71
+ 'gemma-2-2b-it', #'llama-3.2-1b-instruct',
72
+ base_url='http://localhost:52199/api/v1',
73
+ api_key='D6K06DS-P474SK6-P2ACYYC-K5516S1',
74
+ )
75
+
76
+ # Model Pydantic untuk Sales Order
77
+ class product1(BaseModel):
78
+ ds_productCode: Optional[str] = "" #str = Field(..., min_length=1, max_length=20)
79
+ ds_productName: str = Field(..., min_length=1, max_length=100)
80
+ ds_quantity: int = Field(..., ge=1)
81
+ ds_unitPrice: float = Field(..., ge=0)
82
+ ds_itemAmt: Optional[float] = 0
83
+
84
+ # 'if-token-present' means nothing will be sent (and the example will work) if you don't have logfire configured
85
+ logfire.configure(send_to_logfire='if-token-present')
86
+
87
+ class product(BaseModel):
88
+ ds_productCode: Optional[str] = "" #str = Field(..., min_length=1, max_length=20)
89
+ ds_productName: Optional[str] = ""
90
+ ds_quantity: Optional[int] = 0
91
+ ds_unitPrice: Optional[float] = 0
92
+ ds_itemAmt: Optional[float] = 0
93
+
94
+ class SalesOrder(BaseModel):
95
+ ds_salesOrderId: Optional[str] = ""
96
+ ds_salesDate: Optional[str] = ""
97
+ ds_customerName: str = Field(..., min_length=1, max_length=100)
98
+ ds_customerAddress: Optional[str] = ""
99
+ ds_items: List[product] = Field(..., min_items=1)
100
+ ds_shippingCost:Optional[float] = 0
101
+ ds_shippingAddress: Optional[str] = ""
102
+ ds_totalAmount:Optional[float] = 0
103
+ #ds_paymentTerms: Optional[str] = ""
104
+ os.environ['GEMINI_API_KEY'] = 'AIzaSyAsVIHsPIIfDBTb2K6VNdNlMt05t8x3mtE'
105
+ #agent= Agent('gemini-1.5-flash', result_type=SalesOrder)
106
+
107
+ # # Create a system prompt to guide the model
108
+ SYSTEM_PROMPT = """
109
+ Anda adalah pelayan yang ramah, mampu memberikan info tentang Toko Teh "Arafatea", yaitu toko yang menjual 4 Kategori Produk yaitu (1) Teh Premium, (2) Teh Herbal, (3) Camilan/Snack dan (4) Buah Kering. Untuk Kategori Teh Premium ada 5 produk yaitu P11-Teh Hitam, P12-Teh Hijau, P13-Teh Putih, P14-Teh Genmaicha dan P15-Matcha (Serbuk Teh Hijau). Dalam kategori Teh Herbal ada 5 produk yaitu P21-Pandan Tea, P22-Mint Tea, P23-Orange Tea, P24-Teh Buah, dan P25-Teh Rempah. Untuk kategori "Snack" meliputi 4 produk yaitu P31-Greentea-Chocobar, P32-Coffee-Chocobar, P33-Rice-Cracker-Greentea, P34-Rice-Cracker-Coffee; Untuk kategori "Buah Kering" meliputi 8 nama produk meliputi P41 Buah Mangga Kering, P42-Buah Nanas Kering, P43-Buah Lemon Kering, dan P44-Buah Strawberry Kering; P45-Jus Mangga Kering, P46-Jus Nanas Kering, P47-Jus Buah Naga Kering, dan P48-Jus Stawberry Kering. Tidak ada produk selain produk yang masuk 4 kategori tersebut. Alamat website www.arafatea.com, atau www.shopee.co.id/arafatea nomor telepon toko Arafatea adalah 08170218404.
110
+
111
+ Berikut ini adalah informasi tentang masing-masing produk.
112
+
113
+ P11-Teh Hitam, adalah teh hitam kualitas terbaik, ditanam di dataran tinggi Bandung dengan hara tanah baik, menghasilkan teh hitam berkualitas.
114
+ P12-Teh Hijau, adalah teh hijau kualitas terbaik, ditanam di dataran tinggi Bandung dengan hara tanah baik, menghasilkan teh hijau berkualitas.
115
+ P13-Teh Putih, adalah teh putih yang dibuat hanya dari pucuk daun teh yang belum mekar. Dikeringkan dengan mesin dehydrator pada suhu rendah, secara hygienis dan otomatis.
116
+ P14-Teh Genmaicha, adalah teh hijau yang dihasilkan dari varietas tanaman Teh Yabugita (Japanese style), terkenal karena rasanya yang enak tanpa rasa sepet/pahit.
117
+ P15-Matcha (Serbuk Teh Hijau), adalah daun teh hijau kualitas terbaik yang dijadikan serbuk, biasa dinikmati sebagai macha murni dan matcha latte (dengan tambahan krimer)
118
+
119
+ P21 Pandan Tea, adalah sinergi Teh hijau ditambah daun pandan, menghasilkan teh hijau berkualitas dengan aroma harum dari daun pandan.
120
+ P22 Mint Tea, adalah sinergi Teh hijau dan daun mint yang menyegarkan.
121
+ P23 Orange Tea, adalah sinergi Teh hitam dan irisan buah jeruk, rasanya enak menyegarkan khas teh buah jeruk.
122
+ P24 Teh Buah, adalah teh hitam yang bersinergi dengan beberapaa macam buah termasuk lemon dan nanas.
123
+ P25 Teh Rempah, adalah teh hijau dengan campuran herbal untuk meningkatkan imunitas, terutama untuk menghadapi cuaca hujan dan konsidi polusi.
124
+
125
+ P31-Greentea-Chocobar, adalah coklat berbentuk batang dengan dilapisi teh hijau terbaik, mengandung zat aktif l-theanine dalaam dosis cukup menenangkan.
126
+ P32-Coffee-Chocobar, adalah coklat batang dilapisi serbuk kopi, enak menyegarkan.
127
+ P33-Rice-Cracker-Greentea, adalah nasi ketan yang dilapisi teh hijau, sungguh enak sekali, sumber energi dan meningkatkan fokus.
128
+ P34-Rice-Cracker-Coffee, adalah sumber energi karena mengandung karbohidrat dari nasi ketan, dan kesegaran dari kopi.
129
+
130
+ P41 Buah Mangga Kering, berupa irisan tipis buah mangga, dikeringkan dengan mesin dehydrator suhu rendah. Asli buah, tanpa tambahan apapun, 100 persen alami, sumber serat dan vitamin.
131
+
132
+ P42 Buah Nanas Kering, berupa irisan tipis buah nanas, dikeringkan dengan mesin dehydrator suhu rendah. Asli buah, tanpa tambahan apapun, 100 persen alami, sumber serat dan vitamin.
133
+
134
+ P43 Buah Naga Kering, berupa irisan tipis buah naga, dikeringkan dengan mesin dehydrator suhu rendah. Asli buah, tanpa tambahan apapun, 100 persen alami, sumber serat dan vitamin.
135
+
136
+ P44 Buah Strawberry Kering, berupa irisan tipis buah Strawberry, dikeringkan dengan mesin dehydrator suhu rendah. Asli buah, tanpa tambahan apapun, 100 persen alami, sumber serat dan vitamin.
137
+
138
+ P45 Jus Mangga Kering, biasa disebut Manggo fruit leather, dibuat dari buah Mangga yang matang di pohon, dibersihkan dan di-jus, kemudian dikeringkan dengan mesin dehydrator suhu rendah. Hasilnya jus buah Mangga yang kering berbentuk lembaran, awet dan 100 persen alami.
139
+
140
+ P46 Jus Nanas Kering, biasa disebut Pineapple fruit leather, dibuat dari buah Nanas yang matang di pohon, dibersihkan dan di-jus, kemudian dikeringkan dengan mesin dehydrator suhu rendah. Hasilnya jus buah Nanas yang kering berbentuk lembaran, awet dan 100 persen alami.
141
+
142
+ P47 Jus Buah Naga Kering, biasa disebut Dragon fruit leather, dibuat dari buah Naga yang matang di pohon, dibersihkan dan di-jus, kemudian dikeringkan dengan mesin dehydrator suhu rendah. Hasilnya buah Naga kering berbentuk lembaran, awet dan 100 persen alami.
143
+
144
+ P48 Jus Strawberry Kering, biasa disebut Strawberry fruit leather, dibuat dari buah strawberry yang matang di pohon, dibersihkan dan di-jus, kemudian dikeringkan dengan mesin dehydrator suhu rendah. Hasilnya strawberry kering berbentuk lembaran, awet dan 100 persen alami.
145
+ """
146
+ #agent3 = Agent(model=ollama_model, result_type=PetList, retries=3, system_prompt=SYSTEM_PROMPT)
147
+ #agent3 = Agent(model=ollama_model, retries=3, system_prompt=SYSTEM_PROMPT)
148
+
149
+
150
+ #INI SAJA. SALAH SATU
151
+ #agent = Agent('gemini-1.5-flash', system_prompt=SYSTEM_PROMPT) # OK-Gemini
152
+ #agent = Agent(model) # OK-Lokal
153
+ agent = Agent(model, system_prompt=SYSTEM_PROMPT) # OK-Lokal
154
+
155
+ THIS_DIR = Path(__file__).parent
156
+
157
+
158
+ @asynccontextmanager
159
+ async def lifespan(_app: fastapi.FastAPI):
160
+ async with Database.connect() as db:
161
+ yield {'db': db}
162
+
163
+
164
+ app = fastapi.FastAPI(lifespan=lifespan)
165
+ logfire.instrument_fastapi(app)
166
+
167
+
168
+ @app.get('/')
169
+ async def index() -> FileResponse:
170
+ print(THIS_DIR / 'produk.html')
171
+ return FileResponse((THIS_DIR / 'produk.html'), media_type='text/html')
172
+
173
+
174
+ @app.get('/chat_app.ts')
175
+ async def main_ts() -> FileResponse:
176
+ """Get the raw typescript code, it's compiled in the browser, forgive me."""
177
+ return FileResponse((THIS_DIR / 'chat_app.ts'), media_type='text/plain')
178
+
179
+
180
+ async def get_db(request: Request) -> Database:
181
+ return request.state.db
182
+
183
+ @app.get('/chat/')
184
+ async def get_chat(database: Database = Depends(get_db)) -> Response:
185
+ msgs = await database.get_messages()
186
+ return Response(
187
+ b'\n'.join(json.dumps(to_chat_message(m)).encode('utf-8') for m in msgs),
188
+ media_type='text/plain',
189
+ )
190
+
191
+ @app.get('/embed/')
192
+ async def get_embeddings(request: EmbeddingRequest):
193
+ print(request.text)
194
+ embeddings_result = await embedding_model.encode(request.text)
195
+ abcde = embeddings_result.tolist()
196
+ print(abcde)
197
+ return EmbeddingResponse(embeddings=embeddings_result.tolist())
198
+
199
+ class ChatMessage(TypedDict):
200
+ """Format of messages sent to the browser."""
201
+ role: Literal['user', 'model']
202
+ timestamp: str
203
+ content: str
204
+
205
+
206
+ def to_chat_message(m: ModelMessage) -> ChatMessage:
207
+ first_part = m.parts[0]
208
+ if isinstance(m, ModelRequest):
209
+ if isinstance(first_part, UserPromptPart):
210
+ return {
211
+ 'role': 'user',
212
+ 'timestamp': first_part.timestamp.isoformat(),
213
+ 'content': first_part.content,
214
+ }
215
+ elif isinstance(m, ModelResponse):
216
+ if isinstance(first_part, TextPart):
217
+ return {
218
+ 'role': 'model',
219
+ 'timestamp': m.timestamp.isoformat(),
220
+ 'content': first_part.content,
221
+ }
222
+ raise UnexpectedModelBehavior(f'Unexpected message type for chat app: {m}')
223
+
224
+ def to_ds_message(m: ModelMessage) -> ChatMessage:
225
+
226
+ if isinstance(m, ModelRequest):
227
+ first_part = m.parts[0]
228
+ if isinstance(first_part, UserPromptPart):
229
+ return {
230
+ 'role': 'user',
231
+ 'timestamp': first_part.timestamp.isoformat(),
232
+ 'content': first_part.content,
233
+ }
234
+ elif isinstance(m, ModelResponse):
235
+ first_part = m.parts[0]
236
+ if isinstance(first_part, TextPart):
237
+ return {
238
+ 'role': 'model',
239
+ 'timestamp': m.timestamp.isoformat(),
240
+ 'content': first_part.content,
241
+ }
242
+ raise UnexpectedModelBehavior(f'Unexpected ds-message type for chat app: {m}')
243
+
244
+
245
+
246
+
247
+ @app.post('/chat1/')
248
+ async def post_chat1(
249
+ prompt: Annotated[str, fastapi.Form()], database: Database = Depends(get_db)
250
+ ) -> StreamingResponse:
251
+ async def stream_messages():
252
+ """Streams new line delimited JSON `Message`s to the client."""
253
+ # stream the user prompt so that can be displayed straight away
254
+ yield (
255
+ json.dumps(
256
+ {
257
+ 'role': 'user',
258
+ 'timestamp': datetime.now(tz=timezone.utc).isoformat(),
259
+ 'content': prompt,
260
+ }
261
+ ).encode('utf-8')
262
+ + b'\n'
263
+ )
264
+ # get the chat history so far to pass as context to the agent
265
+ messages = await database.get_messages()
266
+ # run the agent with the user prompt and the chat history
267
+ async with agent.run_stream(prompt, message_history=messages) as result:
268
+ async for text in result.stream(debounce_by=0.01):
269
+ # text here is a `str` and the frontend wants
270
+ # JSON encoded ModelResponse, so we create one
271
+ m = ModelResponse.from_text(content=text, timestamp=result.timestamp())
272
+ yield json.dumps(to_chat_message(m)).encode('utf-8') + b'\n'
273
+
274
+ # add new messages (e.g. the user prompt and the agent response in this case) to the database
275
+ #print("---",result.new_messages_json(),"---")
276
+ #print("***",prompt,"***")
277
+ #await database.add_messages(result.new_messages_json())
278
+ print("** selesai **")
279
+ return StreamingResponse(stream_messages(), media_type='text/plain')
280
+
281
+ @app.post('/chat/')
282
+ async def post_chat(
283
+ prompt: Annotated[str, fastapi.Form()], database: Database = Depends(get_db)
284
+ ) -> StreamingResponse:
285
+ async def stream_messages():
286
+ """Streams new line delimited JSON `Message`s to the client."""
287
+ # stream the user prompt so that can be displayed straight away
288
+ yield (
289
+ json.dumps(
290
+ {
291
+ 'role': 'user',
292
+ 'timestamp': datetime.now(tz=timezone.utc).isoformat(),
293
+ 'content': prompt,
294
+ }
295
+ ).encode('utf-8')
296
+ + b'\n'
297
+ )
298
+ # get the chat history so far to pass as context to the agent
299
+ messages = await database.get_messages()
300
+ # run the agent with the user prompt and the chat history
301
+ async with agent.run_stream(prompt, message_history=messages) as result:
302
+ async for text in result.stream(debounce_by=0.01):
303
+ # text here is a `str` and the frontend wants
304
+ # JSON encoded ModelResponse, so we create one
305
+ m = ModelResponse.from_text(content=text, timestamp=result.timestamp())
306
+ yield json.dumps(to_chat_message(m)).encode('utf-8') + b'\n'
307
+
308
+ # add new messages (e.g. the user prompt and the agent response in this case) to the database
309
+ #print("---",result.new_messages_json(),"---")
310
+ #print("***",prompt,"***")
311
+ #await database.add_messages(result.new_messages_json())
312
+ async def ds_messages(prompt1):
313
+ try:
314
+ #prompt2=f"Ekstrak data Sales Order dari teks: {prompt1}. Hari ini adalah tanggal {date.today()}"
315
+ #prompt2=f"{prompt1}. Hari ini adalah tanggal {date.today()}"
316
+ prompt2=f"{prompt1}"
317
+ yield (
318
+ json.dumps(
319
+ {
320
+ 'role': 'user',
321
+ 'timestamp': datetime.now(tz=timezone.utc).isoformat(),
322
+ 'content': prompt1,
323
+ }
324
+ ).encode('utf-8')
325
+ + b'\n'
326
+ )
327
+ #messages = await database.get_messages()
328
+ async with agent.run_stream(prompt2) as result:
329
+ async for text in result.stream(debounce_by=0.1):
330
+ m = ModelResponse.from_text(content=text, timestamp=result.timestamp())
331
+ yield json.dumps(to_ds_message(m)).encode('utf-8') + b'\n'
332
+
333
+ ##print(result.usage())
334
+ await database.add_messages(result.new_messages_json())
335
+ return
336
+ darso = json.loads(result.new_messages_json())
337
+ #print(darso)
338
+ darso0= darso[0]
339
+ #darso0.pop(darso0['parts'][0])
340
+ #print("00|------------------")
341
+ #print("01|", darso0 )
342
+ #print("01a|", darso0['parts'][0] )
343
+ #print("01b|", darso0['parts'][1] )
344
+ darso0['parts'][0] = darso0['parts'][1]
345
+ #print("01?|", darso0 )
346
+ #print("01??|", darso )
347
+ #print("02|------------------")
348
+ darso1= darso[1]
349
+ #print("1|", darso1)
350
+ darso2= json.loads(json.dumps(darso1))
351
+ #print("2|",darso2['parts'][0])
352
+ darso3= darso2['parts'][0]
353
+ darso4= json.loads(json.dumps(darso3))
354
+ #print("4|",darso4['content'])
355
+ darso5= darso4['content']
356
+ darso5=darso5.split('```', 2)
357
+ darso5=darso5[1]
358
+ #print("5a|",darso5)
359
+ darso5=darso5.replace('json', '')
360
+ print("5|",darso5,"|")
361
+ try:
362
+ darso6= json.loads(darso5) #json
363
+ darso7= SalesOrder.model_validate(darso6)
364
+ except:
365
+ darso6= "ERR"
366
+ print("6|",darso6,"|")
367
+ if "ds_items" in darso5:
368
+ cek_str="ds_items"
369
+ else:
370
+ cek_str="--"
371
+ if darso6=="ERR":
372
+ ds_id = time.time()
373
+ ds_salesOrderId = "ERR"
374
+ ds_salesDate = 'ERR'
375
+ ds_customerName="-"
376
+ ds_customerAddress="-"
377
+
378
+ ds_productName1 = "Produk1 --- "
379
+ ds_quantity1 = 1
380
+ ds_unitPrice1 = 0
381
+ ds_itemAmt1 = 0
382
+
383
+ ds_productName2 = "Produk2 --- "
384
+ ds_quantity2 = 0
385
+ ds_unitPrice2 = 0
386
+ ds_itemAmt2 = 0
387
+
388
+ ds_productName3 = "Produk3 --- "
389
+ ds_quantity3 = 0
390
+ ds_unitPrice3 = 0
391
+ ds_itemAmt3 = 0
392
+ ds_shippingAddress=""
393
+ ds_shippingCost=0
394
+ ds_totalAmount=0
395
+ else:
396
+ ds_id = time.time()
397
+ ds_salesOrderId = "OK"
398
+ ds_salesDate = 'OK'
399
+ try:
400
+ ds_salesOrderId = darso7.ds_salesOrderId
401
+ print("7|ds_salesOrderId")
402
+ ds_salesDate = darso7.ds_salesDate
403
+ print("7|ds_salesDate")
404
+ ds_customerName=f"""{darso7.ds_customerName}"""
405
+ print("7|ds_customerName:",ds_customerName)
406
+ ds_customerAddress=f"""{darso7.ds_customerAddress}"""
407
+ print("7|ds_customerAddress:", len(darso7.ds_items))
408
+ ds_productName1 = darso7.ds_items[0].ds_productName
409
+ print("7|ds_productName1")
410
+ ds_quantity1 = darso7.ds_items[0].ds_quantity
411
+ print("7|ds_quantity1")
412
+ ds_unitPrice1 = darso7.ds_items[0].ds_unitPrice
413
+ print("7|ds_unitPrice1")
414
+ ds_itemAmt1 = darso7.ds_items[0].ds_itemAmt
415
+ print("7|ds_itemAmt1")
416
+ ds_productName2 = "-"
417
+ ds_quantity2 = 0
418
+ ds_unitPrice2 = 0
419
+ ds_itemAmt2 = 0
420
+ ds_productName3 = "-"
421
+ ds_quantity3 = 0
422
+ ds_unitPrice3 = 0
423
+ ds_itemAmt3 = 0
424
+
425
+ if len(darso7.ds_items)>1:
426
+ ds_productName2 = darso7.ds_items[1].ds_productName
427
+ ds_quantity2 = darso7.ds_items[1].ds_quantity
428
+ ds_unitPrice2 = darso7.ds_items[1].ds_unitPrice
429
+ ds_itemAmt2 = darso7.ds_items[1].ds_itemAmt
430
+ if len(darso7.ds_items)>2:
431
+ ds_productName3 = darso7.ds_items[2].ds_productName
432
+ ds_quantity3 = darso7.ds_items[2].ds_quantity
433
+ ds_unitPrice3 = darso7.ds_items[2].ds_unitPrice
434
+ ds_itemAmt3 = darso7.ds_items[2].ds_itemAmt
435
+
436
+ ds_shippingCost=darso7.ds_shippingCost
437
+ print("7|ds_shippingCost")
438
+ ds_shippingAddress=f"""{darso7.ds_shippingAddress}"""
439
+ print("7|ds_shippingAddress")
440
+ ds_totalAmount=darso7.ds_totalAmount
441
+ print("7|ds_totalAmount")
442
+ except:
443
+ ds_salesOrderId = "OK2"
444
+ ds_salesDate = 'OK2'
445
+ ds_customerName="-"
446
+ ds_customerAddress="-"
447
+
448
+ ds_productName1 = "Produk1"
449
+ ds_quantity1 = 0
450
+ ds_unitPrice1 = 0
451
+ ds_itemAmt1 = 0
452
+
453
+ ds_productName2 = "Produk2"
454
+ ds_quantity2 = 0
455
+ ds_unitPrice2 = 0
456
+ ds_itemAmt2 = 0
457
+
458
+ ds_productName3 = "Produk3"
459
+ ds_quantity3 = 0
460
+ ds_unitPrice3 = 0
461
+ ds_itemAmt3 = 0
462
+ ds_shippingAddress=""
463
+ ds_shippingCost=0
464
+ ds_totalAmount=0
465
+
466
+ formDs = f"""
467
+
468
+ <form id="myaiForm{ds_id}" action="javascript:abcde({ds_id});" class="form-container">
469
+ <h3>Pesanan</h3>
470
+ <table>
471
+ <tr>
472
+ <td><label for="ds_salesOrderId"><b>SO#</b></label><input type="text" placeholder="" name="ds_salesOrderId" value="{ds_salesOrderId}"></td>
473
+ <td><label for="ds_salesDate"><b>Date</b></label><input type="text" placeholder="" name="ds_salesDate" value="{ds_salesDate}"></td>
474
+ </tr>
475
+ <tr>
476
+ <td colspan="2"><label for="ds_customerName"><b>Customer</b></label><input type="text" placeholder="" name="ds_customerName" value="{ds_customerName}"></td>
477
+ </tr>
478
+ <tr>
479
+ <td colspan="2"><label for="ds_customerAddress"><b>Alamat</b></label><input type="text" placeholder="" name="ds_customerAddress" value="{ds_customerAddress}"></td>
480
+ </tr>
481
+ </table style="width:100%">
482
+ <b>Item Barang:</b>
483
+ <table>
484
+ <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>
485
+ <tr>
486
+ <td><input type="text" placeholder="-" name="ds_productName1" value="{ds_productName1}"></td>
487
+ <td><input type="text" style="text-align: center;" name="ds_quantity1" value={ds_quantity1}></td>
488
+ <td><input type="text" style="text-align: center;" name="ds_unitPrice1" value={ds_unitPrice1}></td>
489
+ <td><input type="text" style="text-align: center;" name="ds_itemAmt1" value={ds_itemAmt1}></td>
490
+ </tr>
491
+ <tr>
492
+ <td><input type="text" placeholder="-" name="ds_productName2" value="{ds_productName2}"></td>
493
+ <td><input type="text" style="text-align: center;" name="ds_quantity2" value={ds_quantity2}></td>
494
+ <td><input type="text" style="text-align: center;" name="ds_unitPrice2" value={ds_unitPrice2}></td>
495
+ <td><input type="text" style="text-align: center;" name="ds_itemAmt2" value={ds_itemAmt2}></td>
496
+ </tr>
497
+ <tr>
498
+ <td><input type="text" placeholder="-" name="ds_productName3" value="{ds_productName3}"></td>
499
+ <td><input type="text" style="text-align: center;" name="ds_quantity3" value={ds_quantity3}></td>
500
+ <td><input type="text" style="text-align: center;" name="ds_unitPrice3" value={ds_unitPrice3}></td>
501
+ <td><input type="text" style="text-align: center;" name="ds_itemAmt3" value={ds_itemAmt3}></td>
502
+ </tr>
503
+ </table>
504
+ <table>
505
+ <tr>
506
+ <td style="text-align: center;"><b>Ongkir</b></td>
507
+ <td style="text-align: center;"><b>Total</b></td>
508
+ </tr>
509
+ <tr>
510
+ <td><input type="text" style="text-align: center;" placeholder="0" name="ds_shippingCost" value={ds_shippingCost}></td>
511
+ <td><input type="text" style="text-align: center;" placeholder="0" name="ds_totalAmount" value={ds_totalAmount}></td>
512
+ </tr>
513
+ <tr>
514
+ <td colspan="2"><label for="ds_shippingAddress"><b></b></label><input type="text" placeholder="" name="ds_shippingAddress" value="{ds_shippingAddress}"></td>
515
+ </tr>
516
+ </table>
517
+ <button type="submit" class="btn">Submit</button>
518
+ <button type="button" class="btn cancel" onclick="closeAiForm({ds_id})">Close</button>
519
+ </form>
520
+ <form id="myaiForm2{ds_id}" class="form-container" style="display:none;">
521
+ <button type="button" class="btn umum" onclick="openAiForm({ds_id})">Open Form</button>
522
+ </form>
523
+ """
524
+ m = ModelResponse.from_text(content=formDs, timestamp=result.timestamp())
525
+ yield json.dumps(to_ds_message(m)).encode('utf-8') + b'\n'
526
+ print("OK:")
527
+ #await database.add_messages(result.new_messages_json())
528
+ await database.add_messages(json.dumps(darso))
529
+ ##print(len(items))
530
+ #darso7 = SalesOrder.model_validate(darso6)
531
+ #print("[--",darso7.ds_customerName,"--]")
532
+ #darso8 = darso7.ds_items[0]
533
+ ##, len(darso7.ds_items)
534
+ #print("[--",darso8.ds_productName,"--]")
535
+ except ValueError as e:
536
+ print(e)
537
+ if prompt[0] == "@" :
538
+ #print("@@@", prompt, "@@@")
539
+ nn = len(prompt)
540
+ prompt = prompt[1:nn]
541
+ print(">>>", prompt, "<<<")
542
+ return StreamingResponse(ds_messages(prompt), media_type='text/plain')
543
+ elif prompt[0] != "@" :
544
+ #print("biasa")
545
+ return StreamingResponse(stream_messages(), media_type='text/plain')
546
+ print("** selesai **")
547
+ return StreamingResponse(stream_messages(), media_type='text/plain')
548
+
549
+ P = ParamSpec('P')
550
+ R = TypeVar('R')
551
+
552
+
553
+ @dataclass
554
+ class Database:
555
+ """Rudimentary database to store chat messages in SQLite.
556
+
557
+ The SQLite standard library package is synchronous, so we
558
+ use a thread pool executor to run queries asynchronously.
559
+ """
560
+
561
+ con: sqlite3.Connection
562
+ _loop: asyncio.AbstractEventLoop
563
+ _executor: ThreadPoolExecutor
564
+
565
+ @classmethod
566
+ @asynccontextmanager
567
+ async def connect(
568
+ cls, file: Path = THIS_DIR / '.chat_messages.sqlite'
569
+ ) -> AsyncIterator[Database]:
570
+ with logfire.span('connect to DB'):
571
+ loop = asyncio.get_event_loop()
572
+ executor = ThreadPoolExecutor(max_workers=1)
573
+ con = await loop.run_in_executor(executor, cls._connect, file)
574
+ slf = cls(con, loop, executor)
575
+ try:
576
+ yield slf
577
+ finally:
578
+ await slf._asyncify(con.close)
579
+
580
+ @staticmethod
581
+ def _connect(file: Path) -> sqlite3.Connection:
582
+ con = sqlite3.connect(str(file))
583
+ con = logfire.instrument_sqlite3(con)
584
+ cur = con.cursor()
585
+ cur.execute(
586
+ 'CREATE TABLE IF NOT EXISTS messages (id INT PRIMARY KEY, message_list TEXT);'
587
+ )
588
+ con.commit()
589
+ return con
590
+
591
+ async def add_messages(self, messages: bytes):
592
+ await self._asyncify(
593
+ self._execute,
594
+ 'INSERT INTO messages (message_list) VALUES (?);',
595
+ messages,
596
+ commit=True,
597
+ )
598
+ await self._asyncify(self.con.commit)
599
+
600
+ async def get_messages(self) -> list[ModelMessage]:
601
+ c = await self._asyncify(
602
+ self._execute, 'SELECT message_list FROM messages order by id asc'
603
+ )
604
+ rows = await self._asyncify(c.fetchall)
605
+ messages: list[ModelMessage] = []
606
+ for row in rows:
607
+ messages.extend(ModelMessagesTypeAdapter.validate_json(row[0]))
608
+ return messages
609
+
610
+ def _execute(
611
+ self, sql: LiteralString, *args: Any, commit: bool = False
612
+ ) -> sqlite3.Cursor:
613
+ cur = self.con.cursor()
614
+ cur.execute(sql, args)
615
+ if commit:
616
+ self.con.commit()
617
+ return cur
618
+
619
+ async def _asyncify(
620
+ self, func: Callable[P, R], *args: P.args, **kwargs: P.kwargs
621
+ ) -> R:
622
+ return await self._loop.run_in_executor( # type: ignore
623
+ self._executor,
624
+ partial(func, **kwargs),
625
+ *args, # type: ignore
626
+ )
627
+
628
+
629
+ if __name__ == '__main__':
630
+ import uvicorn
631
+ uvicorn.run(
632
+ 'app:app', reload=True, host="0.0.0.0", port=8000, reload_dirs=[str(THIS_DIR)]
633
+ )
produk.html ADDED
@@ -0,0 +1,418 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Produk</title>
7
+
8
+ <script src="//cdnjs.cloudflare.com/ajax/libs/annyang/2.6.1/annyang.min.js"></script>
9
+ <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
10
+ <script>
11
+ "use strict";
12
+ var mode_pesanan = false;
13
+ var mode_tanya = false;
14
+ var bicara=annyang
15
+ // first we make sure annyang started succesfully
16
+ if (bicara) {
17
+ // define the functions our commands will run.
18
+ var hello = function(text) {
19
+ $("#prompt-input").slideDown("slow");
20
+ scrollTo("#prompt-input");
21
+ if (mode_tanya==false) {
22
+ $("#prompt-input").val(""+text)
23
+ mode_tanya = true
24
+ } else {
25
+ let prev= $("#prompt-input").val()
26
+ $("#prompt-input").val(prev+" "+text)
27
+ }
28
+ //console.log($("#prompt-input").val())
29
+ $("#AppTitle").val("Tanya")
30
+ };
31
+
32
+ var pesanan = function(text) {
33
+ $("#prompt-input").slideDown("slow");
34
+ scrollTo("#prompt-input");
35
+ if (mode_pesanan==false) {
36
+ $("#prompt-input").val(""+text)
37
+ mode_pesanan = true
38
+ } else {
39
+ let prev= $("#prompt-input").val()
40
+ $("#prompt-input").val(prev+" "+text)
41
+ }
42
+ //console.log($("#prompt-input").val())
43
+ $("#AppTitle").val("Pesanan")
44
+ };
45
+
46
+ var ulangi = function(text) {
47
+ $("#prompt-input").slideDown("slow");
48
+ scrollTo("#prompt-input");
49
+ $("#prompt-input").val("")
50
+ mode_pesanan = false
51
+ };
52
+
53
+ var tutup = function() {
54
+ $("#hello1").hide();
55
+ $("#section_hello").hide();
56
+ $("#section_warna").hide();
57
+ $("#section_rasa").hide();
58
+ $("#section_aroma").hide();
59
+ scrollTo("#section_hello");
60
+ };
61
+
62
+ var submitButton = function() {
63
+ $("#button-input").slideDown("slow");
64
+ scrollTo("#button-input");
65
+ $("#button-input").click()
66
+ console.log("LANJUT")
67
+ mode_pesanan = false
68
+ mode_tanya = false
69
+ $("#AppTitle").val("-")
70
+ };
71
+
72
+ var getStarted = function() {
73
+ window.location.href = 'https://arafaasia.com';
74
+ }
75
+
76
+ // define our commands.
77
+ // * The key is the phrase you want your users to say.
78
+ // * The value is the action to do.
79
+ // You can pass a function, a function name (as a string), or write your function as part of the commands object.
80
+ var commands = {
81
+ 'halo *teman': hello,
82
+ 'tanya *teman': hello,
83
+ 'pesanan *teman': pesanan,
84
+ 'lanjut': submitButton,
85
+ 'masuk': submitButton,
86
+ 'ulangi': ulangi,
87
+ 'ulang': ulangi,
88
+ 'ganti': ulangi,
89
+ '*teman': pesanan,
90
+ };
91
+
92
+ // OPTIONAL: activate debug mode for detailed logging in the console
93
+ bicara.debug();
94
+ bicara.addCommands(commands);
95
+ bicara.setLanguage('id-ID'); // en
96
+ // Start listening. You can call this here, or attach this call to an event, button, etc.
97
+ bicara.start();
98
+ } else {
99
+ $(document).ready(function() {
100
+ $('#unsupported').fadeIn('fast');
101
+ });
102
+ }
103
+
104
+ var scrollTo = function(identifier, speed=1000) {
105
+ //console.log(identifier, speed)
106
+ $(identifier).show();
107
+ $('html, body').animate({
108
+ // scrollTop: $(identifier).offset().top
109
+ }, speed || 1000);
110
+ }
111
+ </script>
112
+
113
+ <style>
114
+ .table {
115
+ display:table;
116
+
117
+ height:100%;
118
+ border:1px solid #000;
119
+ }
120
+ .row2 {
121
+ display:table-row;
122
+ height:100%;
123
+ }
124
+ .cell1, .cell2, .cell3 {
125
+ display:table-cell;
126
+ width:33%;
127
+ height:auto;
128
+ border:1px solid #CCC;
129
+ }
130
+ #section_warna {
131
+ background-color: #ADAD03;
132
+ color: #fff;
133
+ }
134
+ #section_rasa {
135
+ background-color: #03AD77;
136
+ color: #fff;
137
+ }
138
+ #section_aroma {
139
+ background-color: #6503AD;
140
+ color: #fff;
141
+ }
142
+ </style>
143
+ <style>
144
+ figure {
145
+ display: flex inline;
146
+ justify-content: space-between;
147
+ border: 1px #cccccc solid;
148
+ padding: 4px;
149
+ margin: auto;
150
+ }
151
+
152
+ figcaption {
153
+ background-color: #ADAD03;
154
+ color: white;
155
+ font-style: italic;
156
+ padding: 0px;
157
+ text-align: center;
158
+ font-size: 14px;
159
+ }
160
+
161
+
162
+ .container {
163
+ background: white;
164
+ margin: 0 auto;
165
+ padding: 5%;
166
+ width: 90%;
167
+ }
168
+ .img_ds1 {
169
+ width: 80%;
170
+ height: auto
171
+ horizontal-align: center;
172
+ }
173
+ .img_ds {
174
+ width: 100%;
175
+ height: 100%; // auto
176
+ vertical-align: middle;
177
+ }
178
+
179
+ .pics_in_a_row {
180
+ display: flex;
181
+ }
182
+
183
+ .img1 { flex: 1.5; }
184
+ .img2 { flex: 1.5; }
185
+ .img3 { flex: 1.5; }
186
+
187
+ .pics_in_a_row {
188
+ margin: 25px 0;
189
+ }
190
+
191
+ .pics_in_a_row > div:not(:last-child) {
192
+ margin-right: 2%;
193
+ }
194
+
195
+ </style>
196
+ <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
197
+ <style>
198
+ main {
199
+ max-width: 700px;
200
+ }
201
+ #conversation .user::before {
202
+ content: 'Anda: ';
203
+ font-weight: bold;
204
+ display: block;
205
+ }
206
+ #conversation .model::before {
207
+ content: 'AI: ';
208
+ font-weight: bold;
209
+ display: block;
210
+ }
211
+ #conversation .form::before {
212
+ content: 'Form: ';
213
+ font-weight: bold;
214
+ display: block;
215
+ }
216
+ #spinner {
217
+ opacity: 0;
218
+ transition: opacity 500ms ease-in;
219
+ width: 30px;
220
+ height: 30px;
221
+ border: 3px solid #222;
222
+ border-bottom-color: transparent;
223
+ border-radius: 50%;
224
+ animation: rotation 1s linear infinite;
225
+ }
226
+ @keyframes rotation {
227
+ 0% { transform: rotate(0deg); }
228
+ 100% { transform: rotate(360deg); }
229
+ }
230
+ #spinner.active {
231
+ opacity: 1;
232
+ }
233
+ </style>
234
+ <style>
235
+ {box-sizing: border-box;}
236
+
237
+ /* Button used to open the contact form - fixed at the bottom of the page */
238
+ .open-button {
239
+ background-color: #555;
240
+ color: white;
241
+ padding: 10px 10px;
242
+ border: none;
243
+ cursor: pointer;
244
+ opacity: 0.8;
245
+ position: fixed;
246
+ bottom: 23px;
247
+ right: 30%;
248
+ width: 280px;
249
+ }
250
+
251
+ /* The popup form - hidden by default */
252
+ .form-popup {
253
+ display: none;
254
+ position: fixed;
255
+ bottom: 0;
256
+ right: 30%;
257
+ border: 3px solid #f1f1f1;
258
+ z-index: 9;
259
+ }
260
+
261
+ /* Add styles to the form container */
262
+ .form-container {
263
+ max-width: 300px;
264
+ padding: 10px;
265
+ background-color: white;
266
+ font-size: 12px;
267
+ }
268
+
269
+ /* Full-width input fields */
270
+ .form-container input[type=text], .form-container input[type=password] {
271
+ width: 100%;
272
+ padding: 2px;
273
+ margin: 1px 0 2px 0;
274
+ border: none;
275
+ background: #f1f1f1;
276
+ }
277
+
278
+ /* When the inputs get focus, do something */
279
+ .form-container input[type=text]:focus, .form-container input[type=password]:focus {
280
+ background-color: #ddd;
281
+ outline: none;
282
+ }
283
+
284
+ /* Set a style for the submit/login button */
285
+ .form-container .btn {
286
+ background-color: #04AA6D;
287
+ color: white;
288
+ padding: 10px 10px;
289
+ border: none;
290
+ cursor: pointer;
291
+ width: 100%;
292
+ margin-bottom:10px;
293
+ opacity: 0.8;
294
+ }
295
+
296
+ /* Add a red background color to the cancel button */
297
+ .form-container .cancel {
298
+ background-color: red;
299
+ }
300
+ .form-container .umum {
301
+ background-color: grey;
302
+ }
303
+ /* Add some hover effects to buttons */
304
+ .form-container .btn:hover, .open-button:hover {
305
+ opacity: 1;
306
+ }
307
+ </style>
308
+ <script>
309
+ function openForm() {
310
+ document.getElementById("myForm").style.display = "block";
311
+ }
312
+ function closeForm() {
313
+ document.getElementById("myForm").style.display = "none";
314
+ }
315
+ function closeAiForm(id) {
316
+ document.getElementById("myaiForm"+id).style.display = "none";
317
+ document.getElementById("myaiForm2"+id).style.display = "block";
318
+ }
319
+ function openAiForm(id) {
320
+ document.getElementById("myaiForm"+id).style.display = "block";
321
+ document.getElementById("myaiForm2"+id).style.display = "none";
322
+ }
323
+ function abcde(id) {
324
+ alert("abcde:"+id);
325
+ }
326
+ </script>
327
+ </head>
328
+ <body>
329
+ <main class="border rounded mx-auto my-5 p-4">
330
+ <h1 id="AppTitle">ARAFATEA</h1>
331
+ <p>Produk-produk All-Natural</p>
332
+ <p>"-", lanjutkan dengan "masuk" atau "lanjut"</p>
333
+ <div id="conversation" class="px-2"></div>
334
+ <div class="d-flex justify-content-center mb-3">
335
+ <div id="spinner"></div>
336
+ </div>
337
+ <form method="post">
338
+ <input id="prompt-input" name="prompt" class="form-control"/>
339
+ <div class="d-flex justify-content-end">
340
+ <button id="button-input" class="btn btn-primary mt-2">Send</button>
341
+ </div>
342
+ </form>
343
+ <div id="error" class="d-none text-danger">
344
+ Erro occurred, check the browser developer console for more information.
345
+ </div>
346
+ </main>
347
+
348
+ <!--
349
+ <button class="open-button" onclick="openForm()">Open Form</button>
350
+ <div class="form-popup" id="myForm">
351
+ <form action="" class="form-container">
352
+ <h3>Pesanan</h3>
353
+ <table>
354
+ <tr>
355
+ <td><label for="ds_salesOrderId"><b>SO#</b></label><input type="text" placeholder="SO#" name="ds_salesOrderId" required></td>
356
+ <td><label for="ds_salesDate"><b>Date</b></label><input type="text" placeholder="Date" name="ds_salesDate" required></td>
357
+ </tr>
358
+ </table>
359
+
360
+ <label for="ds_customerName"><b></b></label><input type="text" placeholder="Cust" name="ds_customerName" required>
361
+ <label for="ds_customerAddress"><b></b></label><input type="text" placeholder="Addr" name="ds_customerAddress" required>
362
+
363
+ <table>
364
+ <tr><th>Prod</th><th>Qty</th><th>Prc</th><th>Rp</th></tr>
365
+ <tr>
366
+ <td><input type="text" placeholder="-" name="ds_productName1"></td>
367
+ <td><input type="text" placeholder="0" name="ds_quantity1"></td>
368
+ <td><input type="text" placeholder="0" name="ds_unitPrice1"></td>
369
+ <td><input type="text" placeholder="0" name="ds_itemAmt1"></td>
370
+ </tr>
371
+ <tr>
372
+ <td><input type="text" placeholder="-" name="ds_productName2"></td>
373
+ <td><input type="text" placeholder="0" name="ds_quantity2"></td>
374
+ <td><input type="text" placeholder="0" name="ds_unitPrice2"></td>
375
+ <td><input type="text" placeholder="0" name="ds_itemAmt2"></td>
376
+ </tr>
377
+ <tr>
378
+ <td><input type="text" placeholder="-" name="ds_productName3"></td>
379
+ <td><input type="text" placeholder="0" name="ds_quantity3"></td>
380
+ <td><input type="text" placeholder="0" name="ds_unitPrice3"></td>
381
+ <td><input type="text" placeholder="0" name="ds_itemAmt3"></td>
382
+ </tr>
383
+ </table>
384
+
385
+ <table>
386
+ <tr>
387
+ <td><label for="ds_shippingCost"><b>Ongkir</b></label><input type="text" placeholder="Ongkir" name="ds_shippingCost" required></td>
388
+ <td><label for="ds_totalAmount"><b>Total</b></label><input type="text" placeholder="Total" name="ds_totalAmount" required></td>
389
+ </tr>
390
+ </table>
391
+ <button type="submit" class="btn">Submit</button>
392
+ <button type="button" class="btn cancel" onclick="closeForm()">Close</button>
393
+ </form>
394
+ </div>
395
+ -->
396
+ </body>
397
+ </html>
398
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/typescript/5.6.3/typescript.min.js" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
399
+ <script type="module">
400
+ // to let me write TypeScript, without adding the burden of npm we do a dirty, non-production-ready hack
401
+ // and transpile the TypeScript code in the browser
402
+ // this is (arguably) A neat demo trick, but not suitable for production!
403
+ async function loadTs() {
404
+ const response = await fetch('/chat_app.ts');
405
+ const tsCode = await response.text();
406
+ const jsCode = window.ts.transpile(tsCode, { target: "es2015" });
407
+ let script = document.createElement('script');
408
+ script.type = 'module';
409
+ script.text = jsCode;
410
+ document.body.appendChild(script);
411
+ }
412
+
413
+ loadTs().catch((e) => {
414
+ console.error(e);
415
+ document.getElementById('error').classList.remove('d-none');
416
+ document.getElementById('spinner').classList.remove('active');
417
+ });
418
+ </script>