VolodymyrHula commited on
Commit
a0d7d94
·
1 Parent(s): a0f95e7

Uploaded three AI FAQ bot

Browse files
.gitignore ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ .env
2
+ .idea
3
+ .vscode
4
+ __pycache__
5
+ *.pyc
app.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ import os
2
+ from interface import demo
3
+
4
+ if __name__ == "__main__":
5
+ demo.launch(server_name="0.0.0.0", server_port=7860, inbrowser=True)
data/GetCustomers.json ADDED
@@ -0,0 +1,95 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "customers": [
3
+ {
4
+ "customerID": 451930,
5
+ "firstName": "Test",
6
+ "lastName": "Test",
7
+ "company": "",
8
+ "customerType": 3,
9
+ "customerStatus": 1,
10
+ "email": "testtest@gmail.com",
11
+ "phone": "8011111111",
12
+ "phone2": "",
13
+ "mobilePhone": "",
14
+ "fax": "",
15
+ "mainAddress1": "test1",
16
+ "mainAddress2": "test",
17
+ "mainCity": "test",
18
+ "mainState": "JHR",
19
+ "mainZip": "93000",
20
+ "mainCountry": "MY",
21
+ "mainCounty": "Utah",
22
+ "mailAddress1": "1441 Innovation Way",
23
+ "mailAddress2": "",
24
+ "mailCity": "Lehi",
25
+ "mailState": "JHR",
26
+ "mailZip": "84043",
27
+ "mailCountry": "MY",
28
+ "mailCounty": "Utah",
29
+ "otherAddress1": "1441 Innovation Way",
30
+ "otherAddress2": "",
31
+ "otherCity": "Lehi",
32
+ "otherState": "JHR",
33
+ "otherZip": "84043",
34
+ "otherCountry": "MY",
35
+ "otherCounty": "UT",
36
+ "loginName": "IvoryTest",
37
+ "enrollerID": 1,
38
+ "sponsorID": 0,
39
+ "rankID": 40,
40
+ "birthDate": "1987-01-03T00:00:00",
41
+ "field1": "",
42
+ "field2": "",
43
+ "field3": "",
44
+ "field4": "",
45
+ "field5": "",
46
+ "field6": "",
47
+ "field7": "0",
48
+ "field8": "",
49
+ "field9": "",
50
+ "field10": "0",
51
+ "field11": "1",
52
+ "field12": "2026-01-01 12:00:00",
53
+ "field13": "",
54
+ "field14": "",
55
+ "field15": "",
56
+ "date1": "2020-11-02T11:45:24",
57
+ "date2": "2023-02-16T20:48:20",
58
+ "date3": null,
59
+ "date4": null,
60
+ "date5": null,
61
+ "currencyCode": "myr",
62
+ "payableToName": "",
63
+ "defaultWarehouseID": 11,
64
+ "payableType": 8,
65
+ "checkThreshold": 0.0000,
66
+ "priceType": 3,
67
+ "languageID": 0,
68
+ "gender": 0,
69
+ "salesTaxID": "",
70
+ "vatRegistration": "",
71
+ "isSalesTaxExempt": false,
72
+ "isSubscribedToBroadcasts": true,
73
+ "createdDate": "2020-11-02T10:43:24",
74
+ "modifiedDate": "2025-10-21T16:21:05.843",
75
+ "middleName": "",
76
+ "nameSuffix": "",
77
+ "mainAddress3": "",
78
+ "mailAddress3": "",
79
+ "otherAddress3": "",
80
+ "binaryPlacementPreference": 11,
81
+ "useBinaryHoldingTank": false,
82
+ "mainAddressVerified": false,
83
+ "mailAddressVerified": false,
84
+ "otherAddressVerified": false,
85
+ "customerKey": null,
86
+ "enrollerKey": null,
87
+ "sponsorKey": null,
88
+ "payableTyID": 8,
89
+ "canLogin": true,
90
+ "genderID": "U",
91
+ "genderDescription": "Unspecified"
92
+ }
93
+ ],
94
+ "recordCount": 1
95
+ }
data/GetRealTimeCommissions.json ADDED
@@ -0,0 +1,66 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "commissions": [
3
+ {
4
+ "customerID": 451930,
5
+ "periodType": 1,
6
+ "periodID": 377,
7
+ "periodDescription": "Week 139 10/13-10/19",
8
+ "currencyCode": "usd",
9
+ "commissionTotal": 42758.1575,
10
+ "bonuses": [
11
+ {
12
+ "description": "Team Commissions",
13
+ "amount": 22500.0000,
14
+ "bonusID": 26
15
+ },
16
+ {
17
+ "description": "Leadership Matching Bonus",
18
+ "amount": 16334.5000,
19
+ "bonusID": 27
20
+ },
21
+ {
22
+ "description": "Global Leadership Pool Bonus (GLPB)",
23
+ "amount": 3417.1916,
24
+ "bonusID": 28
25
+ },
26
+ {
27
+ "description": "Founders Pool Bonus (FPB)",
28
+ "amount": 506.4659,
29
+ "bonusID": 29
30
+ }
31
+ ],
32
+ "customerKey": null
33
+ },
34
+ {
35
+ "customerID": 451930,
36
+ "periodType": 1,
37
+ "periodID": 378,
38
+ "periodDescription": "Week 140 10/20-10/26",
39
+ "currencyCode": "usd",
40
+ "commissionTotal": 35335.7143,
41
+ "bonuses": [
42
+ {
43
+ "description": "Team Commissions",
44
+ "amount": 20385.0000,
45
+ "bonusID": 26
46
+ },
47
+ {
48
+ "description": "Leadership Matching Bonus",
49
+ "amount": 12906.2500,
50
+ "bonusID": 27
51
+ },
52
+ {
53
+ "description": "Global Leadership Pool Bonus (GLPB)",
54
+ "amount": 1854.4252,
55
+ "bonusID": 28
56
+ },
57
+ {
58
+ "description": "Founders Pool Bonus (FPB)",
59
+ "amount": 190.0391,
60
+ "bonusID": 29
61
+ }
62
+ ],
63
+ "customerKey": null
64
+ }
65
+ ]
66
+ }
data/GetVolumes.json ADDED
@@ -0,0 +1,213 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "volumes": [
3
+ {
4
+ "customerID": 451930,
5
+ "periodType": 1,
6
+ "periodID": 364,
7
+ "periodDescription": "Week 126 7/14-7/20",
8
+ "volume1": 1.0000,
9
+ "volume2": 0.0000,
10
+ "volume3": -250.0000,
11
+ "volume4": 1315.0000,
12
+ "volume5": 2485.0000,
13
+ "volume6": 0.0000,
14
+ "volume7": 58.0000,
15
+ "volume8": 1.0000,
16
+ "volume9": 59.0000,
17
+ "volume10": 0.0000,
18
+ "volume11": 18.0000,
19
+ "volume12": 1.0000,
20
+ "volume13": 0.0000,
21
+ "volume14": 1.0000,
22
+ "volume15": 0.0000,
23
+ "volume16": 2145.0000,
24
+ "volume17": 0.0000,
25
+ "volume18": 180.0000,
26
+ "volume19": 1565.0000,
27
+ "volume20": 510.0000,
28
+ "volume21": 2454.0000,
29
+ "volume22": 3254.0000,
30
+ "volume23": 4594.0000,
31
+ "volume24": 1.0000,
32
+ "volume25": 0.0000,
33
+ "volume26": 0.0000,
34
+ "volume27": 0.0000,
35
+ "volume28": 0.0000,
36
+ "volume29": 0.0000,
37
+ "volume30": 0.0000,
38
+ "volume31": 7236.0000,
39
+ "volume32": 740.0000,
40
+ "volume33": 760.0000,
41
+ "volume34": 0.0000,
42
+ "volume35": 6000.0000,
43
+ "volume36": 84.0000,
44
+ "volume37": 6760.0000,
45
+ "volume38": 84.0000,
46
+ "volume39": 6000.0000,
47
+ "volume40": 84.0000,
48
+ "volume41": 2314.0000,
49
+ "volume42": 300.0000,
50
+ "volume43": 3014.0000,
51
+ "volume44": 400.0000,
52
+ "volume45": 4114.0000,
53
+ "volume46": 640.0000,
54
+ "volume47": 6760.0000,
55
+ "volume48": 84.0000,
56
+ "volume49": 0.0000,
57
+ "volume50": 0.0000,
58
+ "volume51": 6760.0000,
59
+ "volume52": 84.0000,
60
+ "volume53": 0.0000,
61
+ "volume54": 0.0000,
62
+ "volume55": 84.0000,
63
+ "volume56": 6760.0000,
64
+ "volume57": 0.0000,
65
+ "volume58": 1.0000,
66
+ "volume59": 1.0000,
67
+ "volume60": 0.0000,
68
+ "volume61": 1.0000,
69
+ "volume62": 0.0000,
70
+ "volume63": 0.0000,
71
+ "volume64": 1.0000,
72
+ "volume65": 1.0000,
73
+ "volume66": 0.0000,
74
+ "volume67": 0.0000,
75
+ "volume68": 0.0000,
76
+ "volume69": 510.0000,
77
+ "volume70": 0.0000,
78
+ "volume71": 0.0000,
79
+ "volume72": 0.0000,
80
+ "volume73": 0.0000,
81
+ "volume74": 0.0000,
82
+ "volume75": 40.0000,
83
+ "volume76": 40.0000,
84
+ "volume77": 40.0000,
85
+ "volume78": 40.0000,
86
+ "volume79": 40.0000,
87
+ "volume80": 40.0000,
88
+ "volume81": 0.0000,
89
+ "volume82": 40.0000,
90
+ "volume83": 1.0000,
91
+ "volume84": 830.0000,
92
+ "volume85": 0.0000,
93
+ "volume86": 1.0000,
94
+ "volume87": 0.0000,
95
+ "volume88": 0.0000,
96
+ "volume89": 0.0000,
97
+ "volume90": 0.0000,
98
+ "volume91": 2.0000,
99
+ "volume92": 0.0000,
100
+ "volume93": 0.0000,
101
+ "volume94": 0.0000,
102
+ "volume95": 31.0000,
103
+ "volume96": 0.0000,
104
+ "volume97": 0.0000,
105
+ "volume98": 0.0000,
106
+ "volume99": 0.0000,
107
+ "volume100": 0.0000,
108
+ "rankID": 40,
109
+ "paidRankID": 40,
110
+ "volume101": 390.0000,
111
+ "volume102": 390.0000,
112
+ "volume103": 0.0000,
113
+ "volume104": 2205.0000,
114
+ "volume105": 1.0000,
115
+ "volume106": 0.0000,
116
+ "volume107": -250.0000,
117
+ "volume108": 0.0000,
118
+ "volume109": 9.0000,
119
+ "volume110": 0.0000,
120
+ "volume111": 0.0000,
121
+ "volume112": 1.0000,
122
+ "volume113": 61.0000,
123
+ "volume114": 6.0000,
124
+ "volume115": 5.0000,
125
+ "volume116": 0.0000,
126
+ "volume117": 67.0000,
127
+ "volume118": 0.0000,
128
+ "volume119": 0.0000,
129
+ "volume120": 5.0000,
130
+ "volume121": 1860.0000,
131
+ "volume122": 0.0000,
132
+ "volume123": 0.0000,
133
+ "volume124": 1535.0000,
134
+ "volume125": 0.0000,
135
+ "volume126": 0.0000,
136
+ "volume127": 0.0000,
137
+ "volume128": 1535.0000,
138
+ "volume129": 74200.0000,
139
+ "volume130": 200.0000,
140
+ "volume131": 0.0000,
141
+ "volume132": 1430.0000,
142
+ "volume133": 0.0000,
143
+ "volume134": 0.0000,
144
+ "volume135": 0.0000,
145
+ "volume136": 2915.0000,
146
+ "volume137": 0.0000,
147
+ "volume138": 0.0000,
148
+ "volume139": 0.0000,
149
+ "volume140": 0.0000,
150
+ "volume141": 0.0000,
151
+ "volume142": 0.0000,
152
+ "volume143": 10111.0000,
153
+ "volume144": 0.0000,
154
+ "volume145": 0.0000,
155
+ "volume146": 0.0000,
156
+ "volume147": 0.0000,
157
+ "volume148": 0.0000,
158
+ "volume149": 0.0000,
159
+ "volume150": 0.0000,
160
+ "volume151": 830.0000,
161
+ "volume152": 740.0000,
162
+ "volume153": 4.0000,
163
+ "volume154": 0.0000,
164
+ "volume155": 830.0000,
165
+ "volume156": 830.0000,
166
+ "volume157": 0.0000,
167
+ "volume158": 0.0000,
168
+ "volume159": 125.0000,
169
+ "volume160": 830.0000,
170
+ "volume161": 0.0000,
171
+ "volume162": 0.0000,
172
+ "volume163": 0.0000,
173
+ "volume164": 0.0000,
174
+ "volume165": 0.0000,
175
+ "volume166": 0.0000,
176
+ "volume167": 2288.0000,
177
+ "volume168": 7.0000,
178
+ "volume169": 0.0000,
179
+ "volume170": 0.0000,
180
+ "volume171": 0.0000,
181
+ "volume172": 0.0000,
182
+ "volume173": 2265.0000,
183
+ "volume174": 240.0000,
184
+ "volume175": 240.0000,
185
+ "volume176": 130.0000,
186
+ "volume177": 130.0000,
187
+ "volume178": 0.0000,
188
+ "volume179": 110.0000,
189
+ "volume180": 240.0000,
190
+ "volume181": 240.0000,
191
+ "volume182": 240.0000,
192
+ "volume183": 0.0000,
193
+ "volume184": 0.0000,
194
+ "volume185": 0.0000,
195
+ "volume186": 0.0000,
196
+ "volume187": 0.0000,
197
+ "volume188": 0.0000,
198
+ "volume189": 0.0000,
199
+ "volume190": 3220.0000,
200
+ "volume191": 2265.0000,
201
+ "volume192": 2265.0000,
202
+ "volume193": 2265.0000,
203
+ "volume194": 650.0000,
204
+ "volume195": 3.0000,
205
+ "volume196": 2485.0000,
206
+ "volume197": 0.0000,
207
+ "volume198": 0.0000,
208
+ "volume199": 1315.0000,
209
+ "volume200": 0.0000,
210
+ "customerKey": null
211
+ }
212
+ ]
213
+ }
data_service.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+ from typing import Dict, Any, Optional
5
+ import logging
6
+
7
+ logging.basicConfig(level=logging.INFO)
8
+ logger = logging.getLogger(__name__)
9
+
10
+
11
+ class DataService:
12
+ def __init__(self, data_dir: str = "data"):
13
+ """
14
+ Initialize data service.
15
+
16
+ Args:
17
+ data_dir: Path to data directory (default: "data")
18
+ """
19
+ self.data_dir = Path(data_dir)
20
+
21
+ if not self.data_dir.exists():
22
+ raise ValueError(f"Data directory '{data_dir}' does not exist")
23
+
24
+ logger.info(f"DataService initialized with directory: {self.data_dir}")
25
+
26
+ def get_data(self, filename: str) -> Optional[Dict[str, Any]]:
27
+ """
28
+ Read and return complete data from JSON file.
29
+
30
+ Args:
31
+ filename: Name of JSON file (e.g., "GetCustomers.json")
32
+
33
+ Returns:
34
+ Dictionary with complete JSON data or None if error
35
+ """
36
+ file_path = self.data_dir / filename
37
+
38
+ try:
39
+ if not file_path.exists():
40
+ logger.error(f"File not found: {file_path}")
41
+ return None
42
+
43
+ with open(file_path, 'r', encoding='utf-8') as f:
44
+ data = json.load(f)
45
+
46
+ logger.info(f"Successfully loaded data from {filename}")
47
+ return data
48
+
49
+ except json.JSONDecodeError as e:
50
+ logger.error(f"Invalid JSON in {filename}: {e}")
51
+ return None
52
+ except Exception as e:
53
+ logger.error(f"Error reading {filename}: {e}")
54
+ return None
55
+
generate_service.py ADDED
@@ -0,0 +1,203 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ import json
4
+ from openai import OpenAI
5
+ from dotenv import load_dotenv
6
+ from data_service import DataService
7
+
8
+ # Load environment variables from .env file
9
+ load_dotenv(override=True)
10
+
11
+ # Configure logging
12
+ logging.basicConfig(
13
+ level=logging.INFO,
14
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
15
+ )
16
+ logger = logging.getLogger(__name__)
17
+
18
+
19
+ class OpenAIService:
20
+ def __init__(self, api_key=None, assistant_id=None, data_dir="data"):
21
+ """
22
+ Initialize OpenAI service with Assistant API.
23
+
24
+ Args:
25
+ api_key: OpenAI API key (defaults to OPENAI_API_KEY env var)
26
+ assistant_id: OpenAI Assistant ID (defaults to ASSISTANT_ID env var)
27
+ data_dir: Path to data directory for DataService (default: "data")
28
+ """
29
+ logger.info("Initializing OpenAI service...")
30
+
31
+ self.api_key = api_key or os.getenv("OPENAI_API_KEY")
32
+ self.assistant_id = assistant_id or os.getenv("ASSISTANT_ID")
33
+
34
+ if not self.api_key:
35
+ logger.error("OpenAI API key not found in environment variables")
36
+ raise ValueError("OpenAI API key is required. Set OPENAI_API_KEY environment variable.")
37
+ if not self.assistant_id:
38
+ logger.error("Assistant ID not found in environment variables")
39
+ raise ValueError("Assistant ID is required. Set ASSISTANT_ID environment variable.")
40
+
41
+ logger.info(f"API key loaded (length: {len(self.api_key)})")
42
+ logger.info(f"Assistant ID: {self.assistant_id}")
43
+
44
+ self.client = OpenAI(api_key=self.api_key)
45
+ self.thread = None
46
+ self.data_service = DataService(data_dir)
47
+
48
+ logger.info("OpenAI service initialized successfully")
49
+
50
+ def create_thread(self):
51
+ """Create a new conversation thread."""
52
+ logger.info("Creating new conversation thread...")
53
+ self.thread = self.client.beta.threads.create()
54
+ logger.info(f"Thread created successfully: {self.thread.id}")
55
+ return self.thread.id
56
+
57
+ def get_or_create_thread(self):
58
+ """Get existing thread or create a new one."""
59
+ if not self.thread:
60
+ logger.info("No existing thread found, creating new thread")
61
+ self.create_thread()
62
+ else:
63
+ logger.info(f"Using existing thread: {self.thread.id}")
64
+ return self.thread.id
65
+
66
+ def execute_tool_call(self, tool_name, tool_arguments):
67
+ """
68
+ Execute a tool call and return the result.
69
+
70
+ Args:
71
+ tool_name: Name of the tool to execute
72
+ tool_arguments: Dictionary of arguments for the tool
73
+
74
+ Returns:
75
+ str: JSON string with the tool execution result in format { success: bool, result/error: data }
76
+ """
77
+ logger.info(f"Executing tool: {tool_name} with arguments: {tool_arguments}")
78
+
79
+ try:
80
+ if tool_name == "get_real_time_commissions":
81
+ result = self.data_service.get_data("GetRealTimeCommissions.json")
82
+ if result is None:
83
+ return json.dumps({"success": False, "error": f"Failed to load data from GetRealTimeCommissions.json"})
84
+
85
+ return json.dumps({"success": True, "result": result})
86
+ elif tool_name == "get_volumes":
87
+ result = self.data_service.get_data("GetVolumes.json")
88
+ if result is None:
89
+ return json.dumps({"success": False, "error": f"Failed to load data from GetVolumes.json"})
90
+
91
+ return json.dumps({"success": True, "result": result})
92
+ elif tool_name == "get_customers":
93
+ result = self.data_service.get_data("GetCustomers.json")
94
+ if result is None:
95
+ return json.dumps({"success": False, "error": f"Failed to load data from GetCustomers.json"})
96
+
97
+ return json.dumps({"success": True, "result": result})
98
+ else:
99
+ logger.warning(f"Unknown tool: {tool_name}")
100
+ return json.dumps({"success": False, "error": f"Unknown tool: {tool_name}"})
101
+
102
+ except Exception as e:
103
+ logger.error(f"Error executing tool {tool_name}: {str(e)}", exc_info=True)
104
+ return json.dumps({"success": False, "error": str(e)})
105
+
106
+ def generate_stream(self, message):
107
+ """
108
+ Generate response from assistant with streaming and tool handling.
109
+
110
+ Args:
111
+ message: User's message string
112
+
113
+ Yields:
114
+ str: Chunks of the response as they arrive
115
+ """
116
+ try:
117
+ logger.info(f"Processing message: {message[:50]}...")
118
+ thread_id = self.get_or_create_thread()
119
+
120
+ # Add user message to thread
121
+ logger.info(f"Adding message to thread {thread_id}")
122
+ self.client.beta.threads.messages.create(
123
+ thread_id=thread_id,
124
+ role="user",
125
+ content=message
126
+ )
127
+ logger.info("Message added successfully")
128
+
129
+ # Stream the assistant's response
130
+ logger.info("Starting assistant response stream...")
131
+ chunk_count = 0
132
+ skipped_annotations = 0
133
+
134
+ with self.client.beta.threads.runs.stream(
135
+ thread_id=thread_id,
136
+ assistant_id=self.assistant_id
137
+ ) as stream:
138
+ for event in stream:
139
+ # Handle text streaming
140
+ if event.event == "thread.message.delta":
141
+ for content in event.data.delta.content:
142
+ if hasattr(content, 'text') and hasattr(content.text, 'value'):
143
+ if hasattr(content.text, 'annotations') and content.text.annotations:
144
+ skipped_annotations += 1
145
+ continue
146
+
147
+ chunk_count += 1
148
+ yield content.text.value
149
+
150
+ # Handle tool calls
151
+ elif event.event == "thread.run.requires_action":
152
+ logger.info("Assistant requires action (tool calls)")
153
+ run_id = event.data.id
154
+ tool_calls = event.data.required_action.submit_tool_outputs.tool_calls
155
+
156
+ tool_outputs = []
157
+ for tool_call in tool_calls:
158
+ logger.info(f"Processing tool call: {tool_call.function.name}")
159
+
160
+ tool_arguments = json.loads(tool_call.function.arguments)
161
+ tool_output = self.execute_tool_call(
162
+ tool_call.function.name,
163
+ tool_arguments
164
+ )
165
+
166
+ tool_outputs.append({
167
+ "tool_call_id": tool_call.id,
168
+ "output": tool_output
169
+ })
170
+
171
+ # Submit tool outputs and continue streaming
172
+ logger.info(f"Submitting {len(tool_outputs)} tool outputs")
173
+ with self.client.beta.threads.runs.submit_tool_outputs_stream(
174
+ thread_id=thread_id,
175
+ run_id=run_id,
176
+ tool_outputs=tool_outputs
177
+ ) as tool_stream:
178
+ for tool_event in tool_stream:
179
+ if tool_event.event == "thread.message.delta":
180
+ for content in tool_event.data.delta.content:
181
+ if hasattr(content, 'text') and hasattr(content.text, 'value'):
182
+ if hasattr(content.text, 'annotations') and content.text.annotations:
183
+ skipped_annotations += 1
184
+ continue
185
+
186
+ chunk_count += 1
187
+ yield content.text.value
188
+
189
+ logger.info(f"Stream completed. Chunks received: {chunk_count}, Annotations skipped: {skipped_annotations}")
190
+
191
+ except Exception as e:
192
+ logger.error(f"Error in generate_stream: {str(e)}", exc_info=True)
193
+ yield f"Error: {str(e)}"
194
+
195
+ def clear_thread(self):
196
+ """Clear the current thread by creating a new one."""
197
+ if self.thread:
198
+ logger.info(f"Clearing thread: {self.thread.id}")
199
+ else:
200
+ logger.info("No active thread to clear")
201
+ self.thread = None
202
+ logger.info("Thread cleared. New thread will be created on next message")
203
+
interface.py ADDED
@@ -0,0 +1,121 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from generate_service import OpenAIService
3
+
4
+
5
+ # Initialize OpenAI service
6
+ openai_service = OpenAIService()
7
+
8
+
9
+ def respond(message, history):
10
+ """
11
+ Process user message and stream the response from OpenAI Assistant.
12
+
13
+ Args:
14
+ message: User's input message
15
+ history: Chat history as list of tuples
16
+
17
+ Yields:
18
+ Updated history with streaming response
19
+ """
20
+ if not message.strip():
21
+ return
22
+
23
+ # Add user message to history with loading indicator
24
+ history.append((message, '<span class="loading-dots">🤔 Thinking<span style="animation: blink 1.4s infinite; animation-delay: 0s;">.</span><span style="animation: blink 1.4s infinite; animation-delay: 0.2s;">.</span><span style="animation: blink 1.4s infinite; animation-delay: 0.4s;">.</span></span>'))
25
+ yield history, ""
26
+
27
+ # Stream the assistant's response
28
+ response_text = ""
29
+ first_chunk = True
30
+ for chunk in openai_service.generate_stream(message):
31
+ response_text += chunk
32
+ # Replace loading message with actual response
33
+ history[-1] = (message, response_text)
34
+ yield history, ""
35
+ first_chunk = False
36
+
37
+
38
+ def clear_chat():
39
+ """Clear the chat history and create a new thread."""
40
+ openai_service.clear_thread()
41
+ return []
42
+
43
+
44
+ with gr.Blocks(css="""
45
+ #chatbot-container {
46
+ height: calc(100vh - 200px) !important;
47
+ min-height: 600px;
48
+ }
49
+ #input-row {
50
+ position: sticky;
51
+ bottom: 0;
52
+ background: white;
53
+ padding: 10px 0;
54
+ }
55
+ .contain {
56
+ max-width: 100% !important;
57
+ padding: 0 !important;
58
+ }
59
+ #clear-btn {
60
+ position: absolute;
61
+ right: 10px;
62
+ top: 10px;
63
+ z-index: 100;
64
+ }
65
+ @keyframes blink {
66
+ 0%, 100% { opacity: 1; }
67
+ 50% { opacity: 0.3; }
68
+ }
69
+ .message.bot:has(.loading-dots) {
70
+ animation: pulse 1.5s ease-in-out infinite;
71
+ }
72
+ @keyframes pulse {
73
+ 0%, 100% { opacity: 1; }
74
+ 50% { opacity: 0.6; }
75
+ }
76
+ """) as demo:
77
+
78
+ with gr.Row():
79
+ gr.Markdown("# Chat Interface")
80
+ clear_button = gr.Button("Clear", elem_id="clear-btn", size="sm")
81
+
82
+ chatbot = gr.Chatbot(
83
+ value=[],
84
+ elem_id="chatbot-container",
85
+ height="calc(100vh - 200px)",
86
+ show_label=False,
87
+ sanitize_html=False,
88
+ )
89
+
90
+ with gr.Row(elem_id="input-row"):
91
+ with gr.Column(scale=9):
92
+ msg_input = gr.Textbox(
93
+ placeholder="Type your message here...",
94
+ show_label=False,
95
+ container=False
96
+ )
97
+ with gr.Column(scale=1, min_width=100):
98
+ send_button = gr.Button("Send", variant="primary")
99
+
100
+ # Event handlers
101
+ msg_input.submit(
102
+ respond,
103
+ inputs=[msg_input, chatbot],
104
+ outputs=[chatbot, msg_input]
105
+ )
106
+
107
+ send_button.click(
108
+ respond,
109
+ inputs=[msg_input, chatbot],
110
+ outputs=[chatbot, msg_input]
111
+ )
112
+
113
+ clear_button.click(
114
+ clear_chat,
115
+ inputs=None,
116
+ outputs=chatbot
117
+ )
118
+
119
+
120
+ if __name__ == "__main__":
121
+ demo.launch(inbrowser=True)
requirements.txt ADDED
Binary file (2.47 kB). View file