yugapurush commited on
Commit
0fbe5c9
Β·
1 Parent(s): 65cf01b

Day before judgement day, judas will strike in an hour

Browse files
Files changed (2) hide show
  1. app_old.py +215 -0
  2. doli4.py +657 -0
app_old.py ADDED
@@ -0,0 +1,215 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import json
4
+ import asyncio
5
+ import logging
6
+ from typing import Dict, List, Any, Optional
7
+
8
+ # Set up logging to help debug issues
9
+ logging.basicConfig(level=logging.INFO)
10
+ logger = logging.getLogger(__name__)
11
+
12
+ class DolibarrAPI:
13
+ base_url = "http://localhost/dolibarr/api/index.php"
14
+
15
+ def __init__(self, api_key: str):
16
+ """
17
+ Just a constructor, sets up the api key and some other stuff
18
+
19
+ Args:
20
+ api_key (str): Dolibarr user api key
21
+ """
22
+ self.api_key = "OKgV53jdbT1p1tKuZrB05eK9z0p9I2YX"
23
+ self.headers = {
24
+ 'DOLAPIKEY': api_key,
25
+ 'Content-Type': 'application/json',
26
+ 'Accept': 'application/json'
27
+ }
28
+
29
+ def _request(self, method: str, endpoint: str, data: Optional[dict] = None, params: Optional[dict] = None) -> Any:
30
+ """
31
+ basic method used for making http request to Dolibarr api
32
+
33
+ Args:
34
+ method (str): The type of HTTP - GET, POST, PUT, DELETE,etc
35
+ endpoint (str): api endpoint based on the request type - "/invoice" , "/thirdparties",etc
36
+ data (Optional[dict]) : data to be sent in request body, dictionary
37
+ params (Optional[dict]) : dictionary of params to be sent with this request (for GET)
38
+ """
39
+
40
+ base_url = "http://localhost/dolibarr/api/index.php"
41
+ url = f"{base_url}{endpoint}"
42
+
43
+ try:
44
+ response = requests.request(method, url, headers=self.headers, json=data, params=params)
45
+ response.raise_for_status()
46
+ return response.json()
47
+ except requests.exceptions.RequestException as e:
48
+ logger.error(f"API request failed: {e}")
49
+ return {"error": f"API request failed: {str(e)}"}
50
+ except json.JSONDecodeError as e:
51
+ logger.error(f"JSON decode error: {e}")
52
+ return {"error": f"Invalid JSON response: {str(e)}"}
53
+
54
+ # --- GET REQUESTS ------
55
+ def get_req(self, endpoint: str, params: Optional[dict] = None):
56
+ return self._request('GET', endpoint, params=params)
57
+
58
+ # --- POST requests -----
59
+ def post_req(self, endpoint: str, params: dict):
60
+ return self._request("POST", endpoint, data=params)
61
+
62
+ # --- PUT requests ----
63
+ def put_req(self, endpoint: str, params: dict):
64
+ return self._request("PUT", endpoint, data=params)
65
+
66
+ # --- DELETE requests ----
67
+ def del_req(self, endpoint: str, params: Optional[dict] = None):
68
+ return self._request("DELETE", endpoint, params=params)
69
+
70
+ def dolibarr_interface(method: str, endpoint: str, api_key="OKgV53jdbT1p1tKuZrB05eK9z0p9I2YX", payload_str: str = "") -> str:
71
+ """
72
+ To orchestrate the API call from start to finish based on simple string and dictionary inputs
73
+
74
+ Args:
75
+ method (str): http method type ( GET, POST, PUT, DELETE,etc)
76
+ endpoint (str): api endpoint, basically which api to call (/invoices, /thirdparties,/products,etc)
77
+ api_key : dolibarr api key
78
+ payload_str (str): payload as JSON string to send as request body
79
+ """
80
+
81
+ try:
82
+ api = DolibarrAPI(api_key)
83
+ method = method.upper()
84
+
85
+ # Parse payload if provided
86
+ payload = None
87
+ if payload_str and payload_str.strip():
88
+ try:
89
+ payload = json.loads(payload_str)
90
+ except json.JSONDecodeError as e:
91
+ return json.dumps({"error": f"Invalid JSON payload: {str(e)}"}, indent=2)
92
+
93
+ if method == 'GET':
94
+ result = api.get_req(endpoint, payload)
95
+ elif method == 'POST':
96
+ if not payload:
97
+ return json.dumps({"error": "POST requests require a payload"}, indent=2)
98
+ result = api.post_req(endpoint, payload)
99
+ elif method == 'PUT':
100
+ if not payload:
101
+ return json.dumps({"error": "PUT requests require a payload"}, indent=2)
102
+ result = api.put_req(endpoint, payload)
103
+ elif method == 'DELETE':
104
+ result = api.del_req(endpoint, payload)
105
+ else:
106
+ return json.dumps({"error": f"Invalid HTTP method '{method}' selected."}, indent=2)
107
+
108
+ cleaned = clean_json_response(result)
109
+ return json.dumps(cleaned, indent=2)
110
+
111
+ except Exception as e:
112
+ logger.error(f"Unexpected error in dolibarr_interface: {e}")
113
+ return json.dumps({"error": f"Unexpected error: {str(e)}"}, indent=2)
114
+
115
+ def clean_json_response(data: Any) -> Any:
116
+ """
117
+ Recursively clean JSON response by removing null values, empty strings, and empty collections.
118
+
119
+ Args:
120
+ data: The data to clean (can be dict, list, or primitive type)
121
+
122
+ Returns:
123
+ Cleaned data with null values and empty collections removed
124
+ """
125
+ if isinstance(data, dict):
126
+ return {
127
+ k: clean_json_response(v)
128
+ for k, v in data.items()
129
+ if v is not None and v != "" and clean_json_response(v) is not None
130
+ }
131
+ elif isinstance(data, list):
132
+ cleaned = [clean_json_response(item) for item in data]
133
+ return [item for item in cleaned if item is not None and item != ""]
134
+ else:
135
+ return data
136
+
137
+ # Create the Gradio interface with better error handling
138
+ def create_interface():
139
+ """Create and return the Gradio interface"""
140
+
141
+ demo = gr.Interface(
142
+ fn=dolibarr_interface,
143
+ inputs=[
144
+ gr.Dropdown(
145
+ choices=["GET", "POST", "PUT", "DELETE"],
146
+ label="HTTP Method",
147
+ value="GET"
148
+ ),
149
+ gr.Dropdown(
150
+ choices=["/thirdparties", "/invoices", "/products", "/contacts", "/users"],
151
+ label="API Endpoint",
152
+ value="/thirdparties",
153
+ allow_custom_value=True
154
+ ),
155
+ gr.Textbox(
156
+ label="API Key",
157
+ value="OKgV53jdbT1p1tKuZrB05eK9z0p9I2YX"
158
+ ),
159
+ gr.Textbox(
160
+ label="Payload (JSON format)",
161
+ placeholder='{"ref": "PROD-001", "label": "Product Name", "price": "99.99"}'
162
+ )
163
+ ],
164
+ outputs=gr.Textbox(
165
+ label="API Response",
166
+ #lines=10
167
+ ),
168
+ title="Dolibarr AI Agent/Personal ERP Assistant",
169
+ description="Interact with your Dolibarr ERP system through API calls. Select method, endpoint, and provide JSON payload for POST/PUT requests.",
170
+ examples=[
171
+ ["GET", "/thirdparties", "OKgV53jdbT1p1tKuZrB05eK9z0p9I2YX", ""],
172
+ ["POST", "/products", "OKgV53jdbT1p1tKuZrB05eK9z0p9I2YX", '{"ref": "PROD-007", "label": "New AI-Powered Gadget", "price": "199.99", "tva_tx": "20.0"}']
173
+ ]
174
+ )
175
+
176
+ return demo
177
+
178
+ # Main execution with better MCP server handling
179
+ if __name__ == '__main__':
180
+ try:
181
+ demo = create_interface()
182
+
183
+ logger.info("starting gradio app...")
184
+ demo.launch()
185
+
186
+ except Exception as e:
187
+ logger.error(f"Failed to start application: {e}")
188
+ print(f"Error starting application: {e}")
189
+
190
+
191
+
192
+
193
+
194
+
195
+ #----- Example usage ----
196
+ # Uncomment these lines to test the API functionality directly
197
+
198
+ # # Test basic API calling
199
+ # print("Testing API connection...")
200
+ # list_result = dolibarr_interface(method='GET', endpoint='/thirdparties')
201
+ # print("Thirdparties list:", list_result)
202
+
203
+ # # Test creating a product
204
+ # new_product_data = {
205
+ # "ref": "PROD-007",
206
+ # "label": "New AI-Powered Gadget",
207
+ # "price": "199.99",
208
+ # "tva_tx": "20.0"
209
+ # }
210
+ # create_result = dolibarr_interface(
211
+ # method='POST',
212
+ # endpoint='/products',
213
+ # payload_str=json.dumps(new_product_data)
214
+ # )
215
+ # print("Product creation result:", create_result)
doli4.py ADDED
@@ -0,0 +1,657 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import json
4
+ import asyncio
5
+ import logging
6
+ from typing import Dict, List, Any, Optional
7
+ import anthropic
8
+ import openai
9
+ from datetime import datetime
10
+ import os
11
+ # from dotenv import load_dotenv
12
+
13
+ # load_dotenv()
14
+ def get_api_keys():
15
+ # Try to get from Hugging Face secrets first
16
+ openai_key = os.getenv("OPENAI_API_KEY")
17
+ dolibarr_key = os.getenv("DOLIBARR_API_KEY")
18
+
19
+ # If not found, try to load from .env file (for local development)
20
+ if not openai_key or not dolibarr_key:
21
+ from dotenv import load_dotenv
22
+ load_dotenv()
23
+ openai_key = os.getenv("OPENAI_API_KEY")
24
+ dolibarr_key = os.getenv("DOLIBARR_API_KEY")
25
+
26
+ return openai_key, dolibarr_key
27
+
28
+
29
+ # Set up logging
30
+ logging.basicConfig(level=logging.INFO)
31
+ logger = logging.getLogger(__name__)
32
+
33
+ class DolibarrAPI:
34
+ """Your existing Dolibarr API class - keeping it unchanged"""
35
+ base_url = "https://valiant-trust-production.up.railway.app/api/index.php"
36
+
37
+ def __init__(self, api_key: str):
38
+ self.api_key = api_key
39
+ self.headers = {
40
+ 'DOLAPIKEY': api_key,
41
+ 'Content-Type': 'application/json',
42
+ 'Accept': 'application/json'
43
+ }
44
+
45
+ def _request(self, method: str, endpoint: str, data: Optional[dict] = None, params: Optional[dict] = None) -> Any:
46
+ base_url = "https://valiant-trust-production.up.railway.app/api/index.php"
47
+ url = f"{base_url}{endpoint}"
48
+
49
+ try:
50
+ response = requests.request(method, url, headers=self.headers, json=data, params=params)
51
+ response.raise_for_status()
52
+ return response.json()
53
+ except requests.exceptions.RequestException as e:
54
+ logger.error(f"API request failed: {e}")
55
+ return {"error": f"API request failed: {str(e)}"}
56
+ except json.JSONDecodeError as e:
57
+ logger.error(f"JSON decode error: {e}")
58
+ return {"error": f"Invalid JSON response: {str(e)}"}
59
+
60
+ def get_req(self, endpoint: str, params: Optional[dict] = None):
61
+ return self._request('GET', endpoint, params=params)
62
+
63
+ def post_req(self, endpoint: str, params: dict):
64
+ return self._request("POST", endpoint, data=params)
65
+
66
+ def put_req(self, endpoint: str, params: dict):
67
+ return self._request("PUT", endpoint, data=params)
68
+
69
+ def del_req(self, endpoint: str, params: Optional[dict] = None):
70
+ return self._request("DELETE", endpoint, params=params)
71
+
72
+ def dolibarr_interface(method: str, endpoint: str, api_key=os.getenv("DOLIBARR_API_KEY"), payload_str: str = "") -> str:
73
+ """Your existing interface function - keeping it unchanged"""
74
+ try:
75
+ api = DolibarrAPI(api_key)
76
+ method = method.upper()
77
+
78
+ payload = None
79
+ if payload_str and payload_str.strip():
80
+ try:
81
+ payload = json.loads(payload_str)
82
+ except json.JSONDecodeError as e:
83
+ return json.dumps({"error": f"Invalid JSON payload: {str(e)}"}, indent=2)
84
+
85
+ if method == 'GET':
86
+ result = api.get_req(endpoint, payload)
87
+ elif method == 'POST':
88
+ if not payload:
89
+ return json.dumps({"error": "POST requests require a payload"}, indent=2)
90
+ result = api.post_req(endpoint, payload)
91
+ elif method == 'PUT':
92
+ if not payload:
93
+ return json.dumps({"error": "PUT requests require a payload"}, indent=2)
94
+ result = api.put_req(endpoint, payload)
95
+ elif method == 'DELETE':
96
+ result = api.del_req(endpoint, payload)
97
+ else:
98
+ return json.dumps({"error": f"Invalid HTTP method '{method}' selected."}, indent=2)
99
+
100
+ return json.dumps(result, indent=2)
101
+
102
+ except Exception as e:
103
+ logger.error(f"Unexpected error in dolibarr_interface: {e}")
104
+ return json.dumps({"error": f"Unexpected error: {str(e)}"}, indent=2)
105
+
106
+ class OpenAIDolibarrAgent:
107
+ def __init__(self, openai_api_key: str, dolibarr_api_key: str, base_url: str = None):
108
+ self.client = openai.OpenAI(api_key=openai_api_key, base_url=base_url)
109
+ self.dolibarr_api_key = dolibarr_api_key
110
+
111
+ # System prompt with Dolibarr context
112
+ self.system_prompt = """You are a helpful ERP assistant that can interact with a Dolibarr system via API calls.
113
+ CRITICAL RULES:
114
+ 1. ALWAYS show ALL data returned by API calls - never truncate, limit, or summarize unless explicitly asked
115
+ 2. When listing items (customers, invoices, products), display EVERY record returned
116
+ 3. Present data in clean tables or structured format showing key fields
117
+ 4. If API returns 100+ records, show all unless user asks for specific filtering
118
+ 5. NEVER make assumptions about what the user wants to see - show everything
119
+ 5. DO NOT ASSUME DATA AS IF IT WAS RETURNED BY THE API, NEVER ASSUME
120
+
121
+ DOLIBARR API ENDPOINTS:
122
+ - /thirdparties - GET: list all, GET /{id}: specific customer, POST: create
123
+ - /invoices - GET: list all invoices, GET /{id}: specific invoice
124
+ - /products - GET: list all products, POST: create product
125
+ - /contacts - Contact management
126
+ - /users - System users
127
+ - /proposals - Commercial proposals/quotes
128
+ - /orders - Sales orders
129
+ - /bills - Supplier bills
130
+ - /projects - Project management
131
+ - /stocks - Inventory management
132
+
133
+
134
+ RESPONSE FORMAT RULES:
135
+ - For lists: Show ID, Name, Status, and other key fields in table format
136
+ - For single items: Show all relevant details clearly organized
137
+ - Always extract and display the most important information from API responses
138
+ - If API returns error, explain clearly what went wrong
139
+ - Include record counts: "Found X customers:" or "Total invoices: Y"
140
+
141
+ BEHAVIOR RULES:
142
+ - Be proactive - if user asks for "customers", get ALL customers
143
+ - Don't ask "would you like to see more?" - just show everything
144
+ - For specific IDs, show complete details
145
+ - When creating records, confirm success with details
146
+ - Always make the API call needed - don't hesitate or ask for clarification
147
+
148
+ Common operations:
149
+ - GET /thirdparties - List all customers/suppliers
150
+ - GET /thirdparties/{id} - Get specific customer details
151
+ - POST /thirdparties - Create new customer
152
+ - GET /invoices - List all invoices
153
+ - GET /products - List all products
154
+ - POST /products - Create new product
155
+
156
+ You will use the following **HTTP methods**:
157
+
158
+ * `GET` to retrieve data.
159
+ * `POST` to create new records.
160
+ * `PUT` to update existing records.
161
+ * `DELETE` to delete records.
162
+
163
+ Here is what you need to do for each of the provided **endpoints**:
164
+
165
+ ---
166
+
167
+ ### **1. `/thirdparties` (Customers, Suppliers, etc.)**
168
+
169
+ #### **GET** (Retrieve a list or details of a third party):
170
+
171
+ * **Endpoint**: `/thirdparties` or `/thirdparties/{id}`
172
+ * **Required parameters for GET**: No parameters for listing all, but you can use `id` in the endpoint for details of a specific third party.
173
+ * **Response**: A list of third parties or details of the specific one.
174
+
175
+ #### **POST** (Create a new third party):
176
+
177
+ * **Endpoint**: `/thirdparties`
178
+ * **Required Parameters** (in `payload` JSON):
179
+
180
+ ```json
181
+ {
182
+ "name": "John Doe", // The name of the third party
183
+ "address": "123 Main St", // Street address
184
+ "zip": "12345", // Postal code
185
+ "town": "Sample City", // City or town
186
+ "country_id": 1, // Country ID (e.g., 1 for USA)
187
+ "email": "johndoe@example.com", // Email address
188
+ "phone": "+1234567890", // Phone number
189
+ "type": 1, // Type (1 for customer, 2 for supplier, etc.)
190
+ "status": 1 // Status (1 for active, 0 for inactive)
191
+ }
192
+ ```
193
+
194
+ #### **PUT** (Update an existing third party):
195
+
196
+ * **Endpoint**: `/thirdparties/{id}`
197
+ * **Required Parameters** (in `payload` JSON):
198
+
199
+ ```json
200
+ {
201
+ "name": "Updated Name", // Update the name or other attributes as needed
202
+ "email": "newemail@example.com", // Update email
203
+ "phone": "+9876543210" // Update phone number
204
+ }
205
+ ```
206
+
207
+ #### **DELETE** (Delete a third party):
208
+
209
+ * **Endpoint**: `/thirdparties/{id}`
210
+ * **No payload is needed**, just the `id` of the third party to be deleted.
211
+
212
+ ---
213
+
214
+ ### **2. `/invoices`**
215
+
216
+ #### **GET** (Retrieve a list or details of an invoice):
217
+
218
+ * **Endpoint**: `/invoices` or `/invoices/{id}`
219
+ * **Required parameters for GET**: None for listing all invoices, `id` for a specific invoice.
220
+ * **Response**: A list of invoices or details of the specific invoice.
221
+
222
+ #### **POST** (Create a new invoice):
223
+
224
+ * **Endpoint**: `/invoices`
225
+ * **Required Parameters** (in `payload` JSON):
226
+
227
+ ```json
228
+ {
229
+ "socid": 10, // Third-party ID (Customer ID)
230
+ "lines": [ // List of invoice lines
231
+ {
232
+ "desc": "Web Development Service", // Description of the service/product
233
+ "subprice": 500, // Unit price
234
+ "qty": 1, // Quantity
235
+ "total_ht": 500, // Total excluding tax
236
+ "vat": 18, // VAT percentage
237
+ "total_ttc": 590 // Total including tax
238
+ }
239
+ ],
240
+ "date": "2025-06-01", // Invoice creation date (YYYY-MM-DD)
241
+ "duedate": "2025-06-15" // Due date (YYYY-MM-DD)
242
+ }
243
+ ```
244
+
245
+ #### **PUT** (Update an existing invoice):
246
+
247
+ * **Endpoint**: `/invoices/{id}`
248
+ * **Required Parameters** (in `payload` JSON):
249
+
250
+ ```json
251
+ {
252
+ "lines": [ // Updated lines
253
+ {
254
+ "desc": "Updated Service", // New or updated description
255
+ "subprice": 550, // New price
256
+ "qty": 2, // Updated quantity
257
+ "total_ht": 1100, // Updated total excluding tax
258
+ "vat": 18, // VAT
259
+ "total_ttc": 1294 // Updated total including tax
260
+ }
261
+ ]
262
+ }
263
+ ```
264
+
265
+ #### **DELETE** (Delete an invoice):
266
+
267
+ * **Endpoint**: `/invoices/{id}`
268
+ * **No payload needed**, just the `id` of the invoice to delete.
269
+
270
+ ---
271
+
272
+ ### **3. `/contacts` (Contacts for Third Parties)**
273
+
274
+ #### **GET** (Retrieve a list or details of a contact):
275
+
276
+ * **Endpoint**: `/contacts` or `/contacts/{id}`
277
+ * **Required parameters for GET**: None for listing all contacts, `id` for a specific contact.
278
+ * **Response**: A list of contacts or details of a specific contact.
279
+
280
+ #### **POST** (Create a new contact):
281
+
282
+ * **Endpoint**: `/contacts`
283
+ * **Required Parameters** (in `payload` JSON):
284
+
285
+ ```json
286
+ {
287
+ "thirdparty_id": 1, // Third-party ID (Customer or Supplier)
288
+ "firstname": "Jane", // Contact first name
289
+ "lastname": "Doe", // Contact last name
290
+ "email": "janedoe@example.com", // Email address
291
+ "phone": "+1234567890", // Phone number
292
+ "position": "Sales Manager", // Position of the contact
293
+ "address": "1234 Office St" // Address
294
+ }
295
+ ```
296
+
297
+ #### **PUT** (Update an existing contact):
298
+
299
+ * **Endpoint**: `/contacts/{id}`
300
+ * **Required Parameters** (in `payload` JSON):
301
+
302
+ ```json
303
+ {
304
+ "email": "newemail@example.com", // Update email
305
+ "phone": "+9876543210" // Update phone
306
+ }
307
+ ```
308
+
309
+ #### **DELETE** (Delete a contact):
310
+
311
+ * **Endpoint**: `/contacts/{id}`
312
+ * **No payload needed**, just the `id` of the contact to delete.
313
+
314
+ ---
315
+
316
+ ### **4. `/orders` (Customer/Supplier Orders)**
317
+
318
+ #### **GET** (Retrieve a list or details of an order):
319
+
320
+ * **Endpoint**: `/orders` or `/orders/{id}`
321
+ * **Required parameters for GET**: None for listing all orders, `id` for a specific order.
322
+ * **Response**: A list of orders or details of a specific order.
323
+
324
+ #### **POST** (Create a new order):
325
+
326
+ * **Endpoint**: `/orders`
327
+ * **Required Parameters** (in `payload` JSON):
328
+
329
+ ```json
330
+ {
331
+ "socid": 10, // Third-party ID (Customer ID)
332
+ "lines": [ // List of order lines
333
+ {
334
+ "desc": "Laptop", // Description of product
335
+ "subprice": 1000, // Unit price
336
+ "qty": 1, // Quantity
337
+ "total_ht": 1000, // Total excluding tax
338
+ "vat": 18, // VAT
339
+ "total_ttc": 1180 // Total including tax
340
+ }
341
+ ],
342
+ "date": "2025-06-01", // Order date
343
+ "duedate": "2025-06-15" // Due date
344
+ }
345
+ ```
346
+
347
+ #### **PUT** (Update an existing order):
348
+
349
+ * **Endpoint**: `/orders/{id}`
350
+ * **Required Parameters** (in `payload` JSON):
351
+
352
+ ```json
353
+ {
354
+ "lines": [ // Updated lines
355
+ {
356
+ "desc": "Updated Laptop", // Updated description
357
+ "subprice": 1100, // Updated price
358
+ "qty": 2, // Updated quantity
359
+ "total_ht": 2200, // Updated total excluding tax
360
+ "vat": 18, // VAT
361
+ "total_ttc": 2600 // Updated total including tax
362
+ }
363
+ ]
364
+ }
365
+ ```
366
+
367
+ #### **DELETE** (Delete an order):
368
+
369
+ * **Endpoint**: `/orders/{id}`
370
+ * **No payload needed**, just the `id` of the order to delete.
371
+
372
+ ---
373
+
374
+ ### **5. `/products`**
375
+
376
+ #### **GET** (Retrieve a list or details of a product):
377
+
378
+ * **Endpoint**: `/products` or `/products/{id}`
379
+ * **Required parameters for GET**: None for listing all products, `id` for a specific product.
380
+ * **Response**: A list of products or details of a specific product.
381
+
382
+ #### **POST** (Create a new product):
383
+
384
+ * **Endpoint**: `/products`
385
+ * **Required Parameters** (in `payload` JSON):
386
+
387
+ ```json
388
+ {
389
+ "label": "Smartphone", // Product
390
+ ```
391
+
392
+
393
+ name
394
+ "price": 499.99, // Unit price
395
+ "stock": 100, // Quantity in stock
396
+ "description": "Latest model", // Product description
397
+ "socid": 10 // Supplier ID
398
+ }
399
+
400
+
401
+ #### **PUT** (Update an existing product):
402
+
403
+ * **Endpoint**: `/products/{id}`
404
+ * **Required Parameters** (in `payload` JSON):
405
+
406
+ ```json
407
+ {
408
+ "label": "Updated Smartphone", // Updated product name
409
+ "price": 549.99, // Updated price
410
+ "stock": 120, // Updated stock
411
+ "description": "Updated model" // Updated description
412
+ }
413
+ ```
414
+
415
+ #### **DELETE** (Delete a product):
416
+
417
+ * **Endpoint**: `/products/{id}`
418
+ * **No payload needed**, just the `id` of the product to delete.
419
+
420
+ # When a product/invoice/thirdparty is mentioned, call api with a get request to get their id and name and then proceed to make the next required api call.
421
+
422
+ When users ask for information, determine the appropriate API call needed and use the dolibarr_api function.
423
+ Always format responses in a user-friendly way, extracting key information from the API responses.
424
+ If an API call fails, explain the error clearly and suggest alternatives.
425
+
426
+ Current date: """ + datetime.now().strftime("%Y-%m-%d")
427
+
428
+ # Function definition for OpenAI format
429
+ self.functions = [
430
+ {
431
+ "name": "dolibarr_api",
432
+ "description": "Execute API calls to the Dolibarr ERP system",
433
+ "parameters": {
434
+ "type": "object",
435
+ "properties": {
436
+ "method": {
437
+ "type": "string",
438
+ "enum": ["GET", "POST", "PUT", "DELETE"],
439
+ "description": "HTTP method for the API call"
440
+ },
441
+ "endpoint": {
442
+ "type": "string",
443
+ "description": "API endpoint (e.g., /thirdparties, /invoices)"
444
+ },
445
+ "payload": {
446
+ "type": "string",
447
+ "description": "JSON payload for POST/PUT requests (leave empty for GET)"
448
+ }
449
+ },
450
+ "required": ["method", "endpoint"]
451
+ }
452
+ }
453
+ ]
454
+
455
+ def execute_dolibarr_call(self, method: str, endpoint: str, payload: str = "") -> str:
456
+ """Execute the actual Dolibarr API call"""
457
+ return dolibarr_interface(method, endpoint, self.dolibarr_api_key, payload)
458
+
459
+ def chat(self, message: str, history: List[List[str]]) -> str:
460
+ """Main chat function that processes user messages"""
461
+ try:
462
+ # Convert Gradio history to OpenAI format
463
+ messages = [{"role": "system", "content": self.system_prompt}]
464
+
465
+ for human_msg, assistant_msg in history:
466
+ if human_msg:
467
+ messages.append({"role": "user", "content": human_msg})
468
+ if assistant_msg:
469
+ messages.append({"role": "assistant", "content": assistant_msg})
470
+
471
+ # Add current message
472
+ messages.append({"role": "user", "content": message})
473
+
474
+ # Call OpenAI API with functions
475
+ logger.info("Sending request to Nebius API...")
476
+ response = self.client.chat.completions.create(
477
+ model="gpt-3.5-turbo", # or gpt-4 "Qwen/Qwen3-235B-A22B",
478
+ messages=messages,
479
+ functions=self.functions,
480
+ function_call="auto",
481
+ max_tokens=1500
482
+ )
483
+
484
+ # Process the response
485
+ message = response.choices[0].message
486
+ logger.info(f"Received response from Nebius: {message}")
487
+
488
+ if message.function_call:
489
+ # Execute the Dolibarr API call
490
+ function_name = message.function_call.name
491
+ function_args = json.loads(message.function_call.arguments)
492
+ logger.info(f"Function call: {function_name} with args: {function_args}")
493
+
494
+ if function_name == "dolibarr_api":
495
+ api_result = self.execute_dolibarr_call(
496
+ method=function_args.get("method", "GET"),
497
+ endpoint=function_args.get("endpoint", ""),
498
+ payload=function_args.get("payload", "")
499
+ )
500
+ logger.info(f"Dolibarr API result: {api_result}")
501
+ # Send function result back to OpenAI
502
+ messages.append({
503
+ "role": "assistant",
504
+ "content": None,
505
+ "function_call": message.function_call
506
+ })
507
+ messages.append({
508
+ "role": "function",
509
+ "name": function_name,
510
+ "content": api_result
511
+ })
512
+
513
+ # Get final response
514
+ logger.info("Getting final response from Nebius...")
515
+ final_response = self.client.chat.completions.create(
516
+ model="gpt-3.5-turbo",#"Qwen/Qwen3-235B-A22B",
517
+ messages=messages,
518
+ max_tokens=1500
519
+ )
520
+ logger.info(f"Final response: {final_response.choices[0].message}")
521
+
522
+ # Clean up the response content
523
+ content = final_response.choices[0].message.content
524
+ # Remove the <think> sections
525
+ content = content.split('</think>')[-1].strip() if '</think>' in content else content
526
+ return content
527
+
528
+ # Clean up the response content for non-function calls too
529
+ content = message.content
530
+ content = content.split('</think>')[-1].strip() if '</think>' in content else content
531
+ return content if content else "I couldn't process that request."
532
+
533
+ except Exception as e:
534
+ logger.error(f"Error in chat: {e}")
535
+ return f"Sorry, I encountered an error: {str(e)}"
536
+
537
+ def create_openai_agent_interface():
538
+ """Create the Gradio interface for the OpenAI-powered Dolibarr agent"""
539
+
540
+ # OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") # Replace with your key
541
+ # NEBIUS_BASE_URL = "https://api.studio.nebius.ai/v1" # For Nebius (optional)
542
+ # DOLIBARR_API_KEY = os.getenv("DOLIBARR_API_KEY")
543
+
544
+ OPENAI_API_KEY, DOLIBARR_API_KEY = get_api_keys()
545
+
546
+ if not OPENAI_API_KEY or not DOLIBARR_API_KEY:
547
+ raise ValueError("API keys not found. Please set them in Hugging Face Secrets or .env file")
548
+
549
+ # Initialize the agent
550
+ agent = OpenAIDolibarrAgent(OPENAI_API_KEY, DOLIBARR_API_KEY)
551
+ agent = OpenAIDolibarrAgent(OPENAI_API_KEY, DOLIBARR_API_KEY)
552
+ #agent = OpenAIDolibarrAgent(os.getenv("NEBIUS_API_KEY"), DOLIBARR_API_KEY, NEBIUS_BASE_URL) # For Nebius
553
+
554
+ # Create Gradio ChatInterface
555
+ demo = gr.ChatInterface(
556
+ fn=agent.chat,
557
+ title="πŸ€– ERP Assistant",
558
+ description="""
559
+ # πŸ€– AI-Powered Dolibarr ERP Assistant
560
+
561
+ Welcome to your intelligent business management assistant! I can help you manage your entire business operations through natural conversation.
562
+ ---
563
+ ## 🎯 What I Can Do:
564
+
565
+ ### πŸ“‹ Business Information
566
+ - List all customers and suppliers
567
+ - View detailed customer profiles
568
+ - Track invoice status
569
+ - Monitor product inventory
570
+ - Check order status
571
+
572
+ ### πŸ‘₯ Customer Management
573
+ - Create new customers
574
+ - Update customer information
575
+ - Manage customer contacts
576
+ - Track customer orders
577
+
578
+ ### πŸ’° Financial Operations
579
+ - Create and manage invoices
580
+ - Track payment status
581
+ - Generate financial reports
582
+ - Monitor outstanding payments
583
+
584
+ ### πŸ“¦ Product Management
585
+ - View product catalog
586
+ - Update product information
587
+ - Track inventory levels
588
+ - Manage product categories
589
+
590
+ ### πŸ“ Order Processing
591
+ - Create new orders
592
+ - Track order status
593
+ - Manage order details
594
+ - Process order updates
595
+ ---
596
+ ## πŸš€ Getting Started
597
+ 1. Simply type your request in natural language
598
+ 2. I'll understand and execute the appropriate actions
599
+ 3. Get instant results in a clear, organized format
600
+ ---
601
+ ## πŸ’‘ Example Queries:
602
+ - "Show me all customers"
603
+ - "Create a new invoice for customer ID 1"
604
+ - "What's the status of order #123?"
605
+ - "List all products in stock"
606
+ - "Show me recent proposals"
607
+ ---
608
+ ## πŸ”§ Demo Instance
609
+ Try it out with our demo Dolibarr instance:
610
+ - URL: https://valiant-trust-production.up.railway.app/
611
+ - Username: admin
612
+ - Password: admin123
613
+
614
+ Just ask me anything about your business operations, and I'll help you manage them efficiently!
615
+ """,
616
+ examples=[
617
+ "Show me all customers",
618
+ "List all invoices",
619
+ "What products do we have?",
620
+ "Get details for customer ID 1",
621
+ "Show me recent proposals"
622
+ ],
623
+ cache_examples=False,
624
+ theme=gr.themes.Soft()
625
+ )
626
+
627
+ return demo
628
+
629
+ # Main execution
630
+ if __name__ == '__main__':
631
+ try:
632
+ print("πŸš€ Starting OpenAI-Powered Dolibarr Agent...")
633
+
634
+ # Create and launch the interface
635
+ demo = create_openai_agent_interface()
636
+ demo.launch(
637
+ server_name="127.0.0.1",
638
+ server_port=7862,
639
+ share=False,
640
+ debug=True,
641
+ show_error=True
642
+ )
643
+
644
+ except Exception as e:
645
+ logger.error(f"Failed to start application: {e}")
646
+ print(f"❌ Error starting application: {e}")
647
+
648
+ # Example queries you can try:
649
+ """
650
+ - "Show me all customers"
651
+ - "List all invoices"
652
+ - "Get me customer details for ID 1"
653
+ - "What products do we have?"
654
+ - "Show me recent proposals"
655
+ - "Create a new customer named Test Corp"
656
+ - "Find all unpaid invoices"
657
+ """