feat: add Mingdao HAP tool, implemented read and maintain HAP application worksheet data. (#6257)
Co-authored-by: takatost <takatost@gmail.com>pull/6562/head
parent
f30a51e673
commit
5fcc2caeed
|
After Width: | Height: | Size: 1.6 KiB |
@ -0,0 +1,8 @@
|
|||||||
|
from typing import Any
|
||||||
|
|
||||||
|
from core.tools.provider.builtin_tool_provider import BuiltinToolProviderController
|
||||||
|
|
||||||
|
|
||||||
|
class HapProvider(BuiltinToolProviderController):
|
||||||
|
def _validate_credentials(self, credentials: dict[str, Any]) -> None:
|
||||||
|
pass
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
identity:
|
||||||
|
author: Mingdao
|
||||||
|
name: hap
|
||||||
|
label:
|
||||||
|
en_US: HAP
|
||||||
|
zh_Hans: HAP
|
||||||
|
pt_BR: HAP
|
||||||
|
description:
|
||||||
|
en_US: "Hyper application platform that is particularly friendly to AI"
|
||||||
|
zh_Hans: "对 AI 特别友好的超级应用平台"
|
||||||
|
pt_BR: "Plataforma de aplicação hiper que é particularmente amigável à IA"
|
||||||
|
icon: icon.svg
|
||||||
|
tags:
|
||||||
|
- productivity
|
||||||
|
credentials_for_provider:
|
||||||
@ -0,0 +1,53 @@
|
|||||||
|
import json
|
||||||
|
from typing import Any, Union
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||||
|
from core.tools.tool.builtin_tool import BuiltinTool
|
||||||
|
|
||||||
|
|
||||||
|
class AddWorksheetRecordTool(BuiltinTool):
|
||||||
|
|
||||||
|
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
|
||||||
|
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||||
|
|
||||||
|
appkey = tool_parameters.get('appkey', '')
|
||||||
|
if not appkey:
|
||||||
|
return self.create_text_message('Invalid parameter App Key')
|
||||||
|
sign = tool_parameters.get('sign', '')
|
||||||
|
if not sign:
|
||||||
|
return self.create_text_message('Invalid parameter Sign')
|
||||||
|
worksheet_id = tool_parameters.get('worksheet_id', '')
|
||||||
|
if not worksheet_id:
|
||||||
|
return self.create_text_message('Invalid parameter Worksheet ID')
|
||||||
|
record_data = tool_parameters.get('record_data', '')
|
||||||
|
if not record_data:
|
||||||
|
return self.create_text_message('Invalid parameter Record Row Data')
|
||||||
|
|
||||||
|
host = tool_parameters.get('host', '')
|
||||||
|
if not host:
|
||||||
|
host = 'https://api.mingdao.com'
|
||||||
|
elif not host.startswith(("http://", "https://")):
|
||||||
|
return self.create_text_message('Invalid parameter Host Address')
|
||||||
|
else:
|
||||||
|
host = f"{host[:-1] if host.endswith('/') else host}/api"
|
||||||
|
|
||||||
|
url = f"{host}/v2/open/worksheet/addRow"
|
||||||
|
headers = {'Content-Type': 'application/json'}
|
||||||
|
payload = {"appKey": appkey, "sign": sign, "worksheetId": worksheet_id}
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload['controls'] = json.loads(record_data)
|
||||||
|
res = httpx.post(url, headers=headers, json=payload, timeout=60)
|
||||||
|
res.raise_for_status()
|
||||||
|
res_json = res.json()
|
||||||
|
if res_json.get('error_code') != 1:
|
||||||
|
return self.create_text_message(f"Failed to add the new record. {res_json['error_msg']}")
|
||||||
|
return self.create_text_message(f"New record added successfully. The record ID is {res_json['data']}.")
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
return self.create_text_message(f"Failed to add the new record, request error: {e}")
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
return self.create_text_message(f"Failed to parse JSON response: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
return self.create_text_message(f"Failed to add the new record, unexpected error: {e}")
|
||||||
@ -0,0 +1,49 @@
|
|||||||
|
from typing import Any, Union
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||||
|
from core.tools.tool.builtin_tool import BuiltinTool
|
||||||
|
|
||||||
|
|
||||||
|
class DeleteWorksheetRecordTool(BuiltinTool):
|
||||||
|
|
||||||
|
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
|
||||||
|
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||||
|
|
||||||
|
appkey = tool_parameters.get('appkey', '')
|
||||||
|
if not appkey:
|
||||||
|
return self.create_text_message('Invalid parameter App Key')
|
||||||
|
sign = tool_parameters.get('sign', '')
|
||||||
|
if not sign:
|
||||||
|
return self.create_text_message('Invalid parameter Sign')
|
||||||
|
worksheet_id = tool_parameters.get('worksheet_id', '')
|
||||||
|
if not worksheet_id:
|
||||||
|
return self.create_text_message('Invalid parameter Worksheet ID')
|
||||||
|
row_id = tool_parameters.get('row_id', '')
|
||||||
|
if not row_id:
|
||||||
|
return self.create_text_message('Invalid parameter Record Row ID')
|
||||||
|
|
||||||
|
host = tool_parameters.get('host', '')
|
||||||
|
if not host:
|
||||||
|
host = 'https://api.mingdao.com'
|
||||||
|
elif not host.startswith(("http://", "https://")):
|
||||||
|
return self.create_text_message('Invalid parameter Host Address')
|
||||||
|
else:
|
||||||
|
host = f"{host[:-1] if host.endswith('/') else host}/api"
|
||||||
|
|
||||||
|
url = f"{host}/v2/open/worksheet/deleteRow"
|
||||||
|
headers = {'Content-Type': 'application/json'}
|
||||||
|
payload = {"appKey": appkey, "sign": sign, "worksheetId": worksheet_id, "rowId": row_id}
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = httpx.post(url, headers=headers, json=payload, timeout=30)
|
||||||
|
res.raise_for_status()
|
||||||
|
res_json = res.json()
|
||||||
|
if res_json.get('error_code') != 1:
|
||||||
|
return self.create_text_message(f"Failed to delete the record. {res_json['error_msg']}")
|
||||||
|
return self.create_text_message("Successfully deleted the record.")
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
return self.create_text_message(f"Failed to delete the record, request error: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
return self.create_text_message(f"Failed to delete the record, unexpected error: {e}")
|
||||||
@ -0,0 +1,71 @@
|
|||||||
|
identity:
|
||||||
|
name: delete_worksheet_record
|
||||||
|
author: Ryan Tian
|
||||||
|
label:
|
||||||
|
en_US: Delete Worksheet Record
|
||||||
|
zh_Hans: 删除指定的一条工作表记录
|
||||||
|
description:
|
||||||
|
human:
|
||||||
|
en_US: Deletes a single record from a worksheet based on the specified record row ID
|
||||||
|
zh_Hans: 根据指定的记录ID删除一条工作表记录数据
|
||||||
|
llm: A tool to remove a particular record from a worksheet by specifying its unique record identifier.
|
||||||
|
parameters:
|
||||||
|
- name: appkey
|
||||||
|
type: secret-input
|
||||||
|
required: true
|
||||||
|
label:
|
||||||
|
en_US: App Key
|
||||||
|
zh_Hans: App Key
|
||||||
|
human_description:
|
||||||
|
en_US: The AppKey parameter for the HAP application, typically found in the application's API documentation.
|
||||||
|
zh_Hans: HAP 应用的 AppKey 参数,可以从应用 API 文档中查找到
|
||||||
|
llm_description: the AppKey parameter for the HAP application
|
||||||
|
form: form
|
||||||
|
|
||||||
|
- name: sign
|
||||||
|
type: secret-input
|
||||||
|
required: true
|
||||||
|
label:
|
||||||
|
en_US: Sign
|
||||||
|
zh_Hans: Sign
|
||||||
|
human_description:
|
||||||
|
en_US: The Sign parameter for the HAP application
|
||||||
|
zh_Hans: HAP 应用的 Sign 参数
|
||||||
|
llm_description: the Sign parameter for the HAP application
|
||||||
|
form: form
|
||||||
|
|
||||||
|
- name: worksheet_id
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
label:
|
||||||
|
en_US: Worksheet ID
|
||||||
|
zh_Hans: 工作表 ID
|
||||||
|
human_description:
|
||||||
|
en_US: The ID of the specified worksheet
|
||||||
|
zh_Hans: 要获取字段信息的工作表 ID
|
||||||
|
llm_description: The ID of the specified worksheet which to get the fields information.
|
||||||
|
form: llm
|
||||||
|
|
||||||
|
- name: row_id
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
label:
|
||||||
|
en_US: Record Row ID
|
||||||
|
zh_Hans: 记录 ID
|
||||||
|
human_description:
|
||||||
|
en_US: The row ID of the specified record
|
||||||
|
zh_Hans: 要删除的记录 ID
|
||||||
|
llm_description: The row ID of the specified record which to be deleted.
|
||||||
|
form: llm
|
||||||
|
|
||||||
|
- name: host
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
label:
|
||||||
|
en_US: Host Address
|
||||||
|
zh_Hans: 服务器地址
|
||||||
|
human_description:
|
||||||
|
en_US: The address for the privately deployed HAP server.
|
||||||
|
zh_Hans: 私有部署 HAP 服务器地址,公有云无需填写
|
||||||
|
llm_description: the address for the privately deployed HAP server.
|
||||||
|
form: form
|
||||||
@ -0,0 +1,148 @@
|
|||||||
|
import json
|
||||||
|
from typing import Any, Union
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||||
|
from core.tools.tool.builtin_tool import BuiltinTool
|
||||||
|
|
||||||
|
|
||||||
|
class GetWorksheetFieldsTool(BuiltinTool):
|
||||||
|
|
||||||
|
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
|
||||||
|
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||||
|
|
||||||
|
appkey = tool_parameters.get('appkey', '')
|
||||||
|
if not appkey:
|
||||||
|
return self.create_text_message('Invalid parameter App Key')
|
||||||
|
sign = tool_parameters.get('sign', '')
|
||||||
|
if not sign:
|
||||||
|
return self.create_text_message('Invalid parameter Sign')
|
||||||
|
worksheet_id = tool_parameters.get('worksheet_id', '')
|
||||||
|
if not worksheet_id:
|
||||||
|
return self.create_text_message('Invalid parameter Worksheet ID')
|
||||||
|
|
||||||
|
host = tool_parameters.get('host', '')
|
||||||
|
if not host:
|
||||||
|
host = 'https://api.mingdao.com'
|
||||||
|
elif not host.startswith(("http://", "https://")):
|
||||||
|
return self.create_text_message('Invalid parameter Host Address')
|
||||||
|
else:
|
||||||
|
host = f"{host[:-1] if host.endswith('/') else host}/api"
|
||||||
|
|
||||||
|
url = f"{host}/v2/open/worksheet/getWorksheetInfo"
|
||||||
|
headers = {'Content-Type': 'application/json'}
|
||||||
|
payload = {"appKey": appkey, "sign": sign, "worksheetId": worksheet_id}
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = httpx.post(url, headers=headers, json=payload, timeout=60)
|
||||||
|
res.raise_for_status()
|
||||||
|
res_json = res.json()
|
||||||
|
if res_json.get('error_code') != 1:
|
||||||
|
return self.create_text_message(f"Failed to get the worksheet information. {res_json['error_msg']}")
|
||||||
|
|
||||||
|
fields_json, fields_table = self.get_controls(res_json['data']['controls'])
|
||||||
|
result_type = tool_parameters.get('result_type', 'table')
|
||||||
|
return self.create_text_message(
|
||||||
|
text=json.dumps(fields_json, ensure_ascii=False) if result_type == 'json' else fields_table
|
||||||
|
)
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
return self.create_text_message(f"Failed to get the worksheet information, request error: {e}")
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
return self.create_text_message(f"Failed to parse JSON response: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
return self.create_text_message(f"Failed to get the worksheet information, unexpected error: {e}")
|
||||||
|
|
||||||
|
def get_field_type_by_id(self, field_type_id: int) -> str:
|
||||||
|
field_type_map = {
|
||||||
|
2: "Text",
|
||||||
|
3: "Text-Phone",
|
||||||
|
4: "Text-Phone",
|
||||||
|
5: "Text-Email",
|
||||||
|
6: "Number",
|
||||||
|
7: "Text",
|
||||||
|
8: "Number",
|
||||||
|
9: "Option-Single Choice",
|
||||||
|
10: "Option-Multiple Choices",
|
||||||
|
11: "Option-Single Choice",
|
||||||
|
15: "Date",
|
||||||
|
16: "Date",
|
||||||
|
24: "Option-Region",
|
||||||
|
25: "Text",
|
||||||
|
26: "Option-Member",
|
||||||
|
27: "Option-Department",
|
||||||
|
28: "Number",
|
||||||
|
29: "Option-Linked Record",
|
||||||
|
30: "Unknown Type",
|
||||||
|
31: "Number",
|
||||||
|
32: "Text",
|
||||||
|
33: "Text",
|
||||||
|
35: "Option-Linked Record",
|
||||||
|
36: "Number-Yes1/No0",
|
||||||
|
37: "Number",
|
||||||
|
38: "Date",
|
||||||
|
40: "Location",
|
||||||
|
41: "Text",
|
||||||
|
46: "Time",
|
||||||
|
48: "Option-Organizational Role",
|
||||||
|
50: "Text",
|
||||||
|
51: "Query Record",
|
||||||
|
}
|
||||||
|
return field_type_map.get(field_type_id, '')
|
||||||
|
|
||||||
|
def get_controls(self, controls: list) -> dict:
|
||||||
|
fields = []
|
||||||
|
fields_list = ['|fieldId|fieldName|fieldType|fieldTypeId|description|options|','|'+'---|'*6]
|
||||||
|
for control in controls:
|
||||||
|
if control['type'] in self._get_ignore_types():
|
||||||
|
continue
|
||||||
|
field_type_id = control['type']
|
||||||
|
field_type = self.get_field_type_by_id(control['type'])
|
||||||
|
if field_type_id == 30:
|
||||||
|
source_type = control['sourceControl']['type']
|
||||||
|
if source_type in self._get_ignore_types():
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
field_type_id = source_type
|
||||||
|
field_type = self.get_field_type_by_id(source_type)
|
||||||
|
field = {
|
||||||
|
'id': control['controlId'],
|
||||||
|
'name': control['controlName'],
|
||||||
|
'type': field_type,
|
||||||
|
'typeId': field_type_id,
|
||||||
|
'description': control['remark'].replace('\n', ' ').replace('\t', ' '),
|
||||||
|
'options': self._extract_options(control),
|
||||||
|
}
|
||||||
|
fields.append(field)
|
||||||
|
fields_list.append(f"|{field['id']}|{field['name']}|{field['type']}|{field['typeId']}|{field['description']}|{field['options'] if field['options'] else ''}|")
|
||||||
|
|
||||||
|
fields.append({
|
||||||
|
'id': 'ctime',
|
||||||
|
'name': 'Created Time',
|
||||||
|
'type': self.get_field_type_by_id(16),
|
||||||
|
'typeId': 16,
|
||||||
|
'description': '',
|
||||||
|
'options': []
|
||||||
|
})
|
||||||
|
fields_list.append("|ctime|Created Time|Date|16|||")
|
||||||
|
return fields, '\n'.join(fields_list)
|
||||||
|
|
||||||
|
def _extract_options(self, control: dict) -> list:
|
||||||
|
options = []
|
||||||
|
if control['type'] in [9, 10, 11]:
|
||||||
|
options.extend([{"key": opt['key'], "value": opt['value']} for opt in control.get('options', [])])
|
||||||
|
elif control['type'] in [28, 36]:
|
||||||
|
itemnames = control['advancedSetting'].get('itemnames')
|
||||||
|
if itemnames and itemnames.startswith('[{'):
|
||||||
|
try:
|
||||||
|
options = json.loads(itemnames)
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
elif control['type'] == 30:
|
||||||
|
source_type = control['sourceControl']['type']
|
||||||
|
if source_type not in self._get_ignore_types():
|
||||||
|
options.extend([{"key": opt['key'], "value": opt['value']} for opt in control.get('options', [])])
|
||||||
|
return options
|
||||||
|
|
||||||
|
def _get_ignore_types(self):
|
||||||
|
return {14, 21, 22, 34, 42, 43, 45, 47, 49, 10010}
|
||||||
@ -0,0 +1,130 @@
|
|||||||
|
import json
|
||||||
|
from typing import Any, Union
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||||
|
from core.tools.tool.builtin_tool import BuiltinTool
|
||||||
|
|
||||||
|
|
||||||
|
class GetWorksheetPivotDataTool(BuiltinTool):
|
||||||
|
|
||||||
|
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
|
||||||
|
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||||
|
|
||||||
|
appkey = tool_parameters.get('appkey', '')
|
||||||
|
if not appkey:
|
||||||
|
return self.create_text_message('Invalid parameter App Key')
|
||||||
|
sign = tool_parameters.get('sign', '')
|
||||||
|
if not sign:
|
||||||
|
return self.create_text_message('Invalid parameter Sign')
|
||||||
|
worksheet_id = tool_parameters.get('worksheet_id', '')
|
||||||
|
if not worksheet_id:
|
||||||
|
return self.create_text_message('Invalid parameter Worksheet ID')
|
||||||
|
x_column_fields = tool_parameters.get('x_column_fields', '')
|
||||||
|
if not x_column_fields or not x_column_fields.startswith('['):
|
||||||
|
return self.create_text_message('Invalid parameter Column Fields')
|
||||||
|
y_row_fields = tool_parameters.get('y_row_fields', '')
|
||||||
|
if y_row_fields and not y_row_fields.strip().startswith('['):
|
||||||
|
return self.create_text_message('Invalid parameter Row Fields')
|
||||||
|
elif not y_row_fields:
|
||||||
|
y_row_fields = '[]'
|
||||||
|
value_fields = tool_parameters.get('value_fields', '')
|
||||||
|
if not value_fields or not value_fields.strip().startswith('['):
|
||||||
|
return self.create_text_message('Invalid parameter Value Fields')
|
||||||
|
|
||||||
|
host = tool_parameters.get('host', '')
|
||||||
|
if not host:
|
||||||
|
host = 'https://api.mingdao.com'
|
||||||
|
elif not host.startswith(("http://", "https://")):
|
||||||
|
return self.create_text_message('Invalid parameter Host Address')
|
||||||
|
else:
|
||||||
|
host = f"{host[:-1] if host.endswith('/') else host}/api"
|
||||||
|
|
||||||
|
url = f"{host}/report/getPivotData"
|
||||||
|
headers = {'Content-Type': 'application/json'}
|
||||||
|
payload = {"appKey": appkey, "sign": sign, "worksheetId": worksheet_id, "options": {"showTotal": True}}
|
||||||
|
|
||||||
|
try:
|
||||||
|
x_column_fields = json.loads(x_column_fields)
|
||||||
|
payload['columns'] = x_column_fields
|
||||||
|
y_row_fields = json.loads(y_row_fields)
|
||||||
|
if y_row_fields: payload['rows'] = y_row_fields
|
||||||
|
value_fields = json.loads(value_fields)
|
||||||
|
payload['values'] = value_fields
|
||||||
|
sort_fields = tool_parameters.get('sort_fields', '')
|
||||||
|
if not sort_fields: sort_fields = '[]'
|
||||||
|
sort_fields = json.loads(sort_fields)
|
||||||
|
if sort_fields: payload['options']['sort'] = sort_fields
|
||||||
|
res = httpx.post(url, headers=headers, json=payload, timeout=60)
|
||||||
|
res.raise_for_status()
|
||||||
|
res_json = res.json()
|
||||||
|
if res_json.get('status') != 1:
|
||||||
|
return self.create_text_message(f"Failed to get the worksheet pivot data. {res_json['msg']}")
|
||||||
|
|
||||||
|
pivot_json = self.generate_pivot_json(res_json['data'])
|
||||||
|
pivot_table = self.generate_pivot_table(res_json['data'])
|
||||||
|
result_type = tool_parameters.get('result_type', '')
|
||||||
|
text = pivot_table if result_type == 'table' else json.dumps(pivot_json, ensure_ascii=False)
|
||||||
|
return self.create_text_message(text)
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
return self.create_text_message(f"Failed to get the worksheet pivot data, request error: {e}")
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
return self.create_text_message(f"Failed to parse JSON response: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
return self.create_text_message(f"Failed to get the worksheet pivot data, unexpected error: {e}")
|
||||||
|
|
||||||
|
def generate_pivot_table(self, data: dict[str, Any]) -> str:
|
||||||
|
columns = data['metadata']['columns']
|
||||||
|
rows = data['metadata']['rows']
|
||||||
|
values = data['metadata']['values']
|
||||||
|
|
||||||
|
rows_data = data['data']
|
||||||
|
|
||||||
|
header = ([row['displayName'] for row in rows] if rows else []) + [column['displayName'] for column in columns] + [value['displayName'] for value in values]
|
||||||
|
line = (['---'] * len(rows) if rows else []) + ['---'] * len(columns) + ['--:'] * len(values)
|
||||||
|
|
||||||
|
table = [header, line]
|
||||||
|
for row in rows_data:
|
||||||
|
row_data = [self.replace_pipe(row['rows'][r['controlId']]) for r in rows] if rows else []
|
||||||
|
row_data.extend([self.replace_pipe(row['columns'][column['controlId']]) for column in columns])
|
||||||
|
row_data.extend([self.replace_pipe(str(row['values'][value['controlId']])) for value in values])
|
||||||
|
table.append(row_data)
|
||||||
|
|
||||||
|
return '\n'.join([('|'+'|'.join(row) +'|') for row in table])
|
||||||
|
|
||||||
|
def replace_pipe(self, text: str) -> str:
|
||||||
|
return text.replace('|', '▏').replace('\n', ' ')
|
||||||
|
|
||||||
|
def generate_pivot_json(self, data: dict[str, Any]) -> dict:
|
||||||
|
fields = {
|
||||||
|
"x-axis": [
|
||||||
|
{"fieldId": column["controlId"], "fieldName": column["displayName"]}
|
||||||
|
for column in data["metadata"]["columns"]
|
||||||
|
],
|
||||||
|
"y-axis": [
|
||||||
|
{"fieldId": row["controlId"], "fieldName": row["displayName"]}
|
||||||
|
for row in data["metadata"]["rows"]
|
||||||
|
] if data["metadata"]["rows"] else [],
|
||||||
|
"values": [
|
||||||
|
{"fieldId": value["controlId"], "fieldName": value["displayName"]}
|
||||||
|
for value in data["metadata"]["values"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
# fields = ([
|
||||||
|
# {"fieldId": row["controlId"], "fieldName": row["displayName"]}
|
||||||
|
# for row in data["metadata"]["rows"]
|
||||||
|
# ] if data["metadata"]["rows"] else []) + [
|
||||||
|
# {"fieldId": column["controlId"], "fieldName": column["displayName"]}
|
||||||
|
# for column in data["metadata"]["columns"]
|
||||||
|
# ] + [
|
||||||
|
# {"fieldId": value["controlId"], "fieldName": value["displayName"]}
|
||||||
|
# for value in data["metadata"]["values"]
|
||||||
|
# ]
|
||||||
|
rows = []
|
||||||
|
for row in data["data"]:
|
||||||
|
row_data = row["rows"] if row["rows"] else {}
|
||||||
|
row_data.update(row["columns"])
|
||||||
|
row_data.update(row["values"])
|
||||||
|
rows.append(row_data)
|
||||||
|
return {"fields": fields, "rows": rows, "summary": data["metadata"]["totalRow"]}
|
||||||
@ -0,0 +1,209 @@
|
|||||||
|
import json
|
||||||
|
import re
|
||||||
|
from typing import Any, Union
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||||
|
from core.tools.tool.builtin_tool import BuiltinTool
|
||||||
|
|
||||||
|
|
||||||
|
class ListWorksheetRecordsTool(BuiltinTool):
|
||||||
|
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
|
||||||
|
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||||
|
|
||||||
|
appkey = tool_parameters.get('appkey', '')
|
||||||
|
if not appkey:
|
||||||
|
return self.create_text_message('Invalid parameter App Key')
|
||||||
|
|
||||||
|
sign = tool_parameters.get('sign', '')
|
||||||
|
if not sign:
|
||||||
|
return self.create_text_message('Invalid parameter Sign')
|
||||||
|
|
||||||
|
worksheet_id = tool_parameters.get('worksheet_id', '')
|
||||||
|
if not worksheet_id:
|
||||||
|
return self.create_text_message('Invalid parameter Worksheet ID')
|
||||||
|
|
||||||
|
host = tool_parameters.get('host', '')
|
||||||
|
if not host:
|
||||||
|
host = 'https://api.mingdao.com'
|
||||||
|
elif not (host.startswith("http://") or host.startswith("https://")):
|
||||||
|
return self.create_text_message('Invalid parameter Host Address')
|
||||||
|
else:
|
||||||
|
host = f"{host[:-1] if host.endswith('/') else host}/api"
|
||||||
|
|
||||||
|
url_fields = f"{host}/v2/open/worksheet/getWorksheetInfo"
|
||||||
|
headers = {'Content-Type': 'application/json'}
|
||||||
|
payload = {"appKey": appkey, "sign": sign, "worksheetId": worksheet_id}
|
||||||
|
|
||||||
|
field_ids = tool_parameters.get('field_ids', '')
|
||||||
|
|
||||||
|
try:
|
||||||
|
res = httpx.post(url_fields, headers=headers, json=payload, timeout=30)
|
||||||
|
res_json = res.json()
|
||||||
|
if res.is_success:
|
||||||
|
if res_json['error_code'] != 1:
|
||||||
|
return self.create_text_message("Failed to get the worksheet information. {}".format(res_json['error_msg']))
|
||||||
|
else:
|
||||||
|
worksheet_name = res_json['data']['name']
|
||||||
|
fields, schema, table_header = self.get_schema(res_json['data']['controls'], field_ids)
|
||||||
|
else:
|
||||||
|
return self.create_text_message(
|
||||||
|
f"Failed to get the worksheet information, status code: {res.status_code}, response: {res.text}")
|
||||||
|
except Exception as e:
|
||||||
|
return self.create_text_message("Failed to get the worksheet information, something went wrong: {}".format(e))
|
||||||
|
|
||||||
|
if field_ids:
|
||||||
|
payload['controls'] = [v.strip() for v in field_ids.split(',')] if field_ids else []
|
||||||
|
filters = tool_parameters.get('filters', '')
|
||||||
|
if filters:
|
||||||
|
payload['filters'] = json.loads(filters)
|
||||||
|
sort_id = tool_parameters.get('sort_id', '')
|
||||||
|
sort_is_asc = tool_parameters.get('sort_is_asc', False)
|
||||||
|
if sort_id:
|
||||||
|
payload['sortId'] = sort_id
|
||||||
|
payload['isAsc'] = sort_is_asc
|
||||||
|
limit = tool_parameters.get('limit', 50)
|
||||||
|
payload['pageSize'] = limit
|
||||||
|
page_index = tool_parameters.get('page_index', 1)
|
||||||
|
payload['pageIndex'] = page_index
|
||||||
|
payload['useControlId'] = True
|
||||||
|
payload['listType'] = 1
|
||||||
|
|
||||||
|
url = f"{host}/v2/open/worksheet/getFilterRows"
|
||||||
|
try:
|
||||||
|
res = httpx.post(url, headers=headers, json=payload, timeout=90)
|
||||||
|
res_json = res.json()
|
||||||
|
if res.is_success:
|
||||||
|
if res_json['error_code'] != 1:
|
||||||
|
return self.create_text_message("Failed to get the records. {}".format(res_json['error_msg']))
|
||||||
|
else:
|
||||||
|
result = {
|
||||||
|
"fields": fields,
|
||||||
|
"rows": [],
|
||||||
|
"total": res_json.get("data", {}).get("total"),
|
||||||
|
"payload": {key: payload[key] for key in ['worksheetId', 'controls', 'filters', 'sortId', 'isAsc', 'pageSize', 'pageIndex'] if key in payload}
|
||||||
|
}
|
||||||
|
rows = res_json.get("data", {}).get("rows", [])
|
||||||
|
result_type = tool_parameters.get('result_type', '')
|
||||||
|
if not result_type: result_type = 'table'
|
||||||
|
if result_type == 'json':
|
||||||
|
for row in rows:
|
||||||
|
result['rows'].append(self.get_row_field_value(row, schema))
|
||||||
|
return self.create_text_message(json.dumps(result, ensure_ascii=False))
|
||||||
|
else:
|
||||||
|
result_text = f"Found {result['total']} rows in worksheet \"{worksheet_name}\"."
|
||||||
|
if result['total'] > 0:
|
||||||
|
result_text += f" The following are {result['total'] if result['total'] < limit else limit} pieces of data presented in a table format:\n\n{table_header}"
|
||||||
|
for row in rows:
|
||||||
|
result_values = []
|
||||||
|
for f in fields:
|
||||||
|
result_values.append(self.handle_value_type(row[f['fieldId']], schema[f['fieldId']]))
|
||||||
|
result_text += '\n|'+'|'.join(result_values)+'|'
|
||||||
|
return self.create_text_message(result_text)
|
||||||
|
else:
|
||||||
|
return self.create_text_message(
|
||||||
|
f"Failed to get the records, status code: {res.status_code}, response: {res.text}")
|
||||||
|
except Exception as e:
|
||||||
|
return self.create_text_message("Failed to get the records, something went wrong: {}".format(e))
|
||||||
|
|
||||||
|
|
||||||
|
def get_row_field_value(self, row: dict, schema: dict):
|
||||||
|
row_value = {"rowid": row["rowid"]}
|
||||||
|
for field in schema:
|
||||||
|
row_value[field] = self.handle_value_type(row[field], schema[field])
|
||||||
|
return row_value
|
||||||
|
|
||||||
|
|
||||||
|
def get_schema(self, controls: list, fieldids: str):
|
||||||
|
allow_fields = {v.strip() for v in fieldids.split(',')} if fieldids else set()
|
||||||
|
fields = []
|
||||||
|
schema = {}
|
||||||
|
field_names = []
|
||||||
|
for control in controls:
|
||||||
|
control_type_id = self.get_real_type_id(control)
|
||||||
|
if (control_type_id in self._get_ignore_types()) or (allow_fields and not control['controlId'] in allow_fields):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
fields.append({'fieldId': control['controlId'], 'fieldName': control['controlName']})
|
||||||
|
schema[control['controlId']] = {'typeId': control_type_id, 'options': self.set_option(control)}
|
||||||
|
field_names.append(control['controlName'])
|
||||||
|
if (not allow_fields or ('ctime' in allow_fields)):
|
||||||
|
fields.append({'fieldId': 'ctime', 'fieldName': 'Created Time'})
|
||||||
|
schema['ctime'] = {'typeId': 16, 'options': {}}
|
||||||
|
field_names.append("Created Time")
|
||||||
|
fields.append({'fieldId':'rowid', 'fieldName': 'Record Row ID'})
|
||||||
|
schema['rowid'] = {'typeId': 2, 'options': {}}
|
||||||
|
field_names.append("Record Row ID")
|
||||||
|
return fields, schema, '|'+'|'.join(field_names)+'|\n|'+'---|'*len(field_names)
|
||||||
|
|
||||||
|
def get_real_type_id(self, control: dict) -> int:
|
||||||
|
return control['sourceControlType'] if control['type'] == 30 else control['type']
|
||||||
|
|
||||||
|
def set_option(self, control: dict) -> dict:
|
||||||
|
options = {}
|
||||||
|
if control.get('options'):
|
||||||
|
options = {option['key']: option['value'] for option in control['options']}
|
||||||
|
elif control.get('advancedSetting', {}).get('itemnames'):
|
||||||
|
try:
|
||||||
|
itemnames = json.loads(control['advancedSetting']['itemnames'])
|
||||||
|
options = {item['key']: item['value'] for item in itemnames}
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
return options
|
||||||
|
|
||||||
|
def _get_ignore_types(self):
|
||||||
|
return {14, 21, 22, 34, 42, 43, 45, 47, 49, 10010}
|
||||||
|
|
||||||
|
def handle_value_type(self, value, field):
|
||||||
|
type_id = field.get("typeId")
|
||||||
|
if type_id == 10:
|
||||||
|
value = value if isinstance(value, str) else "、".join(value)
|
||||||
|
elif type_id in [28, 36]:
|
||||||
|
value = field.get("options", {}).get(value, value)
|
||||||
|
elif type_id in [26, 27, 48, 14]:
|
||||||
|
value = self.process_value(value)
|
||||||
|
elif type_id in [35, 29]:
|
||||||
|
value = self.parse_cascade_or_associated(field, value)
|
||||||
|
elif type_id == 40:
|
||||||
|
value = self.parse_location(value)
|
||||||
|
return self.rich_text_to_plain_text(value) if value else ''
|
||||||
|
|
||||||
|
def process_value(self, value):
|
||||||
|
if isinstance(value, str):
|
||||||
|
if value.startswith("[{\"accountId\""):
|
||||||
|
value = json.loads(value)
|
||||||
|
value = ', '.join([item['fullname'] for item in value])
|
||||||
|
elif value.startswith("[{\"departmentId\""):
|
||||||
|
value = json.loads(value)
|
||||||
|
value = '、'.join([item['departmentName'] for item in value])
|
||||||
|
elif value.startswith("[{\"organizeId\""):
|
||||||
|
value = json.loads(value)
|
||||||
|
value = '、'.join([item['organizeName'] for item in value])
|
||||||
|
elif value.startswith("[{\"file_id\""):
|
||||||
|
value = ''
|
||||||
|
elif value == '[]':
|
||||||
|
value = ''
|
||||||
|
elif hasattr(value, 'accountId'):
|
||||||
|
value = value['fullname']
|
||||||
|
return value
|
||||||
|
|
||||||
|
def parse_cascade_or_associated(self, field, value):
|
||||||
|
if (field['typeId'] == 35 and value.startswith('[')) or (field['typeId'] == 29 and value.startswith('[{')):
|
||||||
|
value = json.loads(value)
|
||||||
|
value = value[0]['name'] if len(value) > 0 else ''
|
||||||
|
else:
|
||||||
|
value = ''
|
||||||
|
return value
|
||||||
|
|
||||||
|
def parse_location(self, value):
|
||||||
|
if len(value) > 10:
|
||||||
|
parsed_value = json.loads(value)
|
||||||
|
value = parsed_value.get("address", "")
|
||||||
|
else:
|
||||||
|
value = ""
|
||||||
|
return value
|
||||||
|
|
||||||
|
def rich_text_to_plain_text(self, rich_text):
|
||||||
|
text = re.sub(r'<[^>]+>', '', rich_text) if '<' in rich_text else rich_text
|
||||||
|
return text.replace("|", "▏").replace("\n", " ")
|
||||||
@ -0,0 +1,82 @@
|
|||||||
|
import json
|
||||||
|
from typing import Any, Union
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||||
|
from core.tools.tool.builtin_tool import BuiltinTool
|
||||||
|
|
||||||
|
|
||||||
|
class ListWorksheetsTool(BuiltinTool):
|
||||||
|
|
||||||
|
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
|
||||||
|
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||||
|
|
||||||
|
appkey = tool_parameters.get('appkey', '')
|
||||||
|
if not appkey:
|
||||||
|
return self.create_text_message('Invalid parameter App Key')
|
||||||
|
sign = tool_parameters.get('sign', '')
|
||||||
|
if not sign:
|
||||||
|
return self.create_text_message('Invalid parameter Sign')
|
||||||
|
|
||||||
|
host = tool_parameters.get('host', '')
|
||||||
|
if not host:
|
||||||
|
host = 'https://api.mingdao.com'
|
||||||
|
elif not (host.startswith("http://") or host.startswith("https://")):
|
||||||
|
return self.create_text_message('Invalid parameter Host Address')
|
||||||
|
else:
|
||||||
|
host = f"{host[:-1] if host.endswith('/') else host}/api"
|
||||||
|
url = f"{host}/v1/open/app/get"
|
||||||
|
|
||||||
|
result_type = tool_parameters.get('result_type', '')
|
||||||
|
if not result_type:
|
||||||
|
result_type = 'table'
|
||||||
|
|
||||||
|
headers = { 'Content-Type': 'application/json' }
|
||||||
|
params = { "appKey": appkey, "sign": sign, }
|
||||||
|
try:
|
||||||
|
res = httpx.get(url, headers=headers, params=params, timeout=30)
|
||||||
|
res_json = res.json()
|
||||||
|
if res.is_success:
|
||||||
|
if res_json['error_code'] != 1:
|
||||||
|
return self.create_text_message("Failed to access the application. {}".format(res_json['error_msg']))
|
||||||
|
else:
|
||||||
|
if result_type == 'json':
|
||||||
|
worksheets = []
|
||||||
|
for section in res_json['data']['sections']:
|
||||||
|
worksheets.extend(self._extract_worksheets(section, result_type))
|
||||||
|
return self.create_text_message(text=json.dumps(worksheets, ensure_ascii=False))
|
||||||
|
else:
|
||||||
|
worksheets = '|worksheetId|worksheetName|description|\n|---|---|---|'
|
||||||
|
for section in res_json['data']['sections']:
|
||||||
|
worksheets += self._extract_worksheets(section, result_type)
|
||||||
|
return self.create_text_message(worksheets)
|
||||||
|
|
||||||
|
else:
|
||||||
|
return self.create_text_message(
|
||||||
|
f"Failed to list worksheets, status code: {res.status_code}, response: {res.text}")
|
||||||
|
except Exception as e:
|
||||||
|
return self.create_text_message("Failed to list worksheets, something went wrong: {}".format(e))
|
||||||
|
|
||||||
|
def _extract_worksheets(self, section, type):
|
||||||
|
items = []
|
||||||
|
tables = ''
|
||||||
|
for item in section.get('items', []):
|
||||||
|
if item.get('type') == 0 and (not 'notes' in item or item.get('notes') != 'NO'):
|
||||||
|
if type == 'json':
|
||||||
|
filtered_item = {
|
||||||
|
'id': item['id'],
|
||||||
|
'name': item['name'],
|
||||||
|
'notes': item.get('notes', '')
|
||||||
|
}
|
||||||
|
items.append(filtered_item)
|
||||||
|
else:
|
||||||
|
tables += f"\n|{item['id']}|{item['name']}|{item.get('notes', '')}|"
|
||||||
|
|
||||||
|
for child_section in section.get('childSections', []):
|
||||||
|
if type == 'json':
|
||||||
|
items.extend(self._extract_worksheets(child_section, 'json'))
|
||||||
|
else:
|
||||||
|
tables += self._extract_worksheets(child_section, 'table')
|
||||||
|
|
||||||
|
return items if type == 'json' else tables
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
import json
|
||||||
|
from typing import Any, Union
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
from core.tools.entities.tool_entities import ToolInvokeMessage
|
||||||
|
from core.tools.tool.builtin_tool import BuiltinTool
|
||||||
|
|
||||||
|
|
||||||
|
class UpdateWorksheetRecordTool(BuiltinTool):
|
||||||
|
|
||||||
|
def _invoke(self, user_id: str, tool_parameters: dict[str, Any]
|
||||||
|
) -> Union[ToolInvokeMessage, list[ToolInvokeMessage]]:
|
||||||
|
|
||||||
|
appkey = tool_parameters.get('appkey', '')
|
||||||
|
if not appkey:
|
||||||
|
return self.create_text_message('Invalid parameter App Key')
|
||||||
|
sign = tool_parameters.get('sign', '')
|
||||||
|
if not sign:
|
||||||
|
return self.create_text_message('Invalid parameter Sign')
|
||||||
|
worksheet_id = tool_parameters.get('worksheet_id', '')
|
||||||
|
if not worksheet_id:
|
||||||
|
return self.create_text_message('Invalid parameter Worksheet ID')
|
||||||
|
row_id = tool_parameters.get('row_id', '')
|
||||||
|
if not row_id:
|
||||||
|
return self.create_text_message('Invalid parameter Record Row ID')
|
||||||
|
record_data = tool_parameters.get('record_data', '')
|
||||||
|
if not record_data:
|
||||||
|
return self.create_text_message('Invalid parameter Record Row Data')
|
||||||
|
|
||||||
|
host = tool_parameters.get('host', '')
|
||||||
|
if not host:
|
||||||
|
host = 'https://api.mingdao.com'
|
||||||
|
elif not host.startswith(("http://", "https://")):
|
||||||
|
return self.create_text_message('Invalid parameter Host Address')
|
||||||
|
else:
|
||||||
|
host = f"{host[:-1] if host.endswith('/') else host}/api"
|
||||||
|
|
||||||
|
url = f"{host}/v2/open/worksheet/editRow"
|
||||||
|
headers = {'Content-Type': 'application/json'}
|
||||||
|
payload = {"appKey": appkey, "sign": sign, "worksheetId": worksheet_id, "rowId": row_id}
|
||||||
|
|
||||||
|
try:
|
||||||
|
payload['controls'] = json.loads(record_data)
|
||||||
|
res = httpx.post(url, headers=headers, json=payload, timeout=60)
|
||||||
|
res.raise_for_status()
|
||||||
|
res_json = res.json()
|
||||||
|
if res_json.get('error_code') != 1:
|
||||||
|
return self.create_text_message(f"Failed to update the record. {res_json['error_msg']}")
|
||||||
|
return self.create_text_message("Record updated successfully.")
|
||||||
|
except httpx.RequestError as e:
|
||||||
|
return self.create_text_message(f"Failed to update the record, request error: {e}")
|
||||||
|
except json.JSONDecodeError as e:
|
||||||
|
return self.create_text_message(f"Failed to parse JSON response: {e}")
|
||||||
|
except Exception as e:
|
||||||
|
return self.create_text_message(f"Failed to update the record, unexpected error: {e}")
|
||||||
Loading…
Reference in New Issue