diff --git a/backend/auth/__init__.py b/backend/auth/__init__.py new file mode 100644 index 0000000..6b64027 --- /dev/null +++ b/backend/auth/__init__.py @@ -0,0 +1,16 @@ + +# Copyright 2022 Cisco Systems, Inc. and its affiliates +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 diff --git a/backend/auth/escherauth.py b/backend/auth/escherauth.py new file mode 100644 index 0000000..a22c871 --- /dev/null +++ b/backend/auth/escherauth.py @@ -0,0 +1,313 @@ +#code taken from https://github.com/emartech/escher-python and updated to python3 + +import datetime +import hmac +import requests +import urllib.request, urllib.parse, urllib.error +import re + +from hashlib import sha256, sha512 + +try: + from urllib.parse import urlparse, parse_qsl, urljoin + from urllib.parse import quote +except: + from urllib.parse import urlparse, parse_qsl, urljoin, quote + + +class EscherException(Exception): + pass + + +class EscherRequestsAuth(requests.auth.AuthBase): + def __init__(self, credential_scope, options, client): + self.escher = Escher(credential_scope, options) + self.client = client + + def __call__(self, request): + return self.escher.sign(request, self.client) + + +class EscherRequest(): + _uri_regex = re.compile('([^?#]*)(\?(.*))?') + + def __init__(self, request): + self.type = type(request) + self.request = request + self.prepare_request_uri() + + def request(self): + return self.request + + def prepare_request_uri(self): + if self.type is requests.models.PreparedRequest: + self.request_uri = self.request.path_url + if self.type is dict: + self.request_uri = self.request['uri'] + match = re.match(self._uri_regex, self.request_uri) + self.uri_path = match.group(1) + self.uri_query = match.group(3) + + def method(self): + if self.type is requests.models.PreparedRequest: + return self.request.method + if self.type is dict: + return self.request['method'] + + def host(self): + if self.type is requests.models.PreparedRequest: + return self.request.host + if self.type is dict: + return self.request['host'] + + def path(self): + return self.uri_path + + def query_parts(self): + return parse_qsl((self.uri_query or '').replace(';', '%3b'), True) + + def headers(self): + if self.type is requests.models.PreparedRequest: + headers = [] + for key, value in self.request.headers.items(): + headers.append([key, value]) + return headers + if self.type is dict: + return self.request['headers'] + + def body(self): + if self.type is requests.models.PreparedRequest: + return self.request.body or '' + if self.type is dict: + return self.request.get('body', '') + + def add_header(self, header, value): + if self.type is requests.models.PreparedRequest: + self.request.headers[header] = value + if self.type is dict: + self.request['headers'].append((header, value)) + + +class AuthParams: + def __init__(self, data, vendor_key): + self._init_data(data, 'X-' + vendor_key + '-') + + def _init_data(self, data, prefix): + self._data = {} + for (k, v) in data: + if k.startswith(prefix): + self._data[k.replace(prefix, '').lower()] = v + + def get(self, name): + if name not in self._data: + raise EscherException('Missing authorization parameter: ' + name) + return self._data[name] + + def get_signed_headers(self): + return self.get('signedheaders').lower().split(';') + + def get_algo_data(self): + data = self.get('algorithm').split('-') + if len(data) != 3: + raise EscherException('Malformed Algorithm parameter') + return data + + def get_algo_prefix(self): + return self.get_algo_data()[0] + + def get_hash_algo(self): + return self.get_algo_data()[2].upper() + + def get_credential_data(self): + data = self.get('credentials').split('/', 2) + if len(data) != 3: + raise EscherException('Malformed Credentials parameter') + return data + + def get_credential_key(self): + return self.get_credential_data()[0] + + def get_credential_date(self): + return datetime.datetime.strptime(self.get_credential_data()[1], '%Y%m%d') + + def get_credential_scope(self): + return self.get_credential_data()[2] + + def get_expires(self): + return int(self.get('expires')) + + def get_request_date(self): + return datetime.datetime.strptime(self.get('date'), '%Y%m%dT%H%M%SZ') + + +class AuthenticationValidator: + def validate_mandatory_signed_headers(self, headers_to_sign): + if 'host' not in headers_to_sign: + raise EscherException('Host header is not signed') + + def validate_hash_algo(self, hash_algo): + if hash_algo not in ('SHA256', 'SHA512'): + raise EscherException('Only SHA256 and SHA512 hash algorithms are allowed') + + def validate_dates(self, current_date, request_date, credential_date, expires, clock_skew): + if request_date.strftime('%Y%m%d') != credential_date.strftime('%Y%m%d'): + raise EscherException('The request date and credential date do not match') + + min_date = current_date - datetime.timedelta(seconds=(clock_skew + expires)) + max_date = current_date + datetime.timedelta(seconds=clock_skew) + if request_date < min_date or request_date > max_date: + raise EscherException('Request date is not within the accepted time interval') + + def validate_credential_scope(self, expected, actual): + if actual != expected: + raise EscherException('Invalid credential scope (provided: ' + actual + ', required: ' + expected + ')') + + def validate_signature(self, expected, actual): + if expected != actual: + raise EscherException('The signatures do not match (provided: ' + actual + ', calculated: ' + expected + ')') + + +class Escher: + _normalize_path = re.compile('([^/]+/\.\./?|/\./|//|/\.$|/\.\.$)') + + def __init__(self, credential_scope, options={}): + self.credential_scope = credential_scope + self.algo_prefix = options.get('algo_prefix', 'ESR') + self.vendor_key = options.get('vendor_key', 'Escher') + self.hash_algo = options.get('hash_algo', 'SHA256') + self.current_time = options.get('current_time', datetime.datetime.utcnow()) + self.auth_header_name = options.get('auth_header_name', 'X-Escher-Auth') + self.date_header_name = options.get('date_header_name', 'X-Escher-Date') + self.clock_skew = options.get('clock_skew', 300) + self.algo = self.create_algo() + self.algo_id = self.algo_prefix + '-HMAC-' + self.hash_algo + + def sign(self, r, client, headers_to_sign=[]): + request = EscherRequest(r) + + for header in [self.date_header_name.lower(), 'host']: + if header not in headers_to_sign: + headers_to_sign.append(header) + + signature = self.generate_signature(client['api_secret'], request, headers_to_sign, self.current_time) + request.add_header(self.auth_header_name, ", ".join([ + self.algo_id + ' Credential=' + client['api_key'] + '/' + self.short_date( + self.current_time) + '/' + self.credential_scope, + 'SignedHeaders=' + self.prepare_headers_to_sign(headers_to_sign), + 'Signature=' + signature + ])) + return request.request + + def authenticate(self, r, key_db): + request = EscherRequest(r) + + auth_params = AuthParams(request.query_parts(), self.vendor_key) + validator = AuthenticationValidator() + + validator.validate_mandatory_signed_headers(auth_params.get_signed_headers()) + validator.validate_hash_algo(auth_params.get_hash_algo()) + validator.validate_dates( + self.current_time, + auth_params.get_request_date(), + auth_params.get_credential_date(), + auth_params.get_expires(), + self.clock_skew + ) + validator.validate_credential_scope(self.credential_scope, auth_params.get_credential_scope()) + + if auth_params.get_credential_key() not in key_db: + raise EscherException('Invalid Escher key') + + calculated_signature = self.generate_signature( + key_db[auth_params.get_credential_key()], request, + auth_params.get_signed_headers(), + auth_params.get_request_date() + ) + validator.validate_signature(calculated_signature, auth_params.get('signature')) + + return auth_params.get_credential_key() + + def hmac_digest(self, key, message, is_hex=False): + if not isinstance(key, bytes): + key = key.encode('utf-8') + digest = hmac.new(key, message.encode('utf-8'), self.algo) + if is_hex: + return digest.hexdigest() + return digest.digest() + + def generate_signature(self, api_secret, req, headers_to_sign, current_time): + canonicalized_request = self.canonicalize(req, headers_to_sign) + string_to_sign = self.get_string_to_sign(canonicalized_request, current_time) + + signing_key = self.hmac_digest(self.algo_prefix + api_secret, self.short_date(current_time)) + for data in self.credential_scope.split('/'): + signing_key = self.hmac_digest(signing_key, data) + + return self.hmac_digest(signing_key, string_to_sign, True) + + def canonicalize(self, req, headers_to_sign): + return "\n".join([ + req.method(), + self.canonicalize_path(req.path()), + self.canonicalize_query(req.query_parts()), + self.canonicalize_headers(req.headers(), headers_to_sign), + '', + self.prepare_headers_to_sign(headers_to_sign), + self.algo(req.body().encode('utf-8')).hexdigest() + ]) + + def canonicalize_path(self, path): + changes = 1 + while changes > 0: + path, changes = self._normalize_path.subn('/', path, 1) + return path + + def canonicalize_headers(self, headers, headers_to_sign): + headers_list = [] + for key, value in iter(sorted(headers)): + if key.lower() in headers_to_sign: + headers_list.append(key.lower() + ':' + self.normalize_white_spaces(value)) + return "\n".join(sorted(headers_list)) + + def normalize_white_spaces(self, value): + index = 0 + value_normalized = [] + pattern = re.compile(r'\s+') + for part in value.split('"'): + if index % 2 == 0: + part = pattern.sub(' ', part) + value_normalized.append(part) + index += 1 + return '"'.join(value_normalized).strip() + + def canonicalize_query(self, query_parts): + safe = "~+!'()*" + query_list = [] + for key, value in query_parts: + if key == 'X-' + self.vendor_key + '-Signature': + continue + query_list.append(quote(key, safe=safe) + '=' + quote(value, safe=safe)) + return "&".join(sorted(query_list)) + + def get_string_to_sign(self, canonicalized_request, current_time): + return "\n".join([ + self.algo_id, + self.long_date(current_time), + self.short_date(current_time) + '/' + self.credential_scope, + self.algo(canonicalized_request.encode('utf-8')).hexdigest() + ]) + + def create_algo(self): + if self.hash_algo == 'SHA256': + return sha256 + if self.hash_algo == 'SHA512': + return sha512 + + def long_date(self, time): + return time.strftime('%Y%m%dT%H%M%SZ') + + def short_date(self, time): + return time.strftime('%Y%m%d') + + def prepare_headers_to_sign(self, headers_to_sign): + return ";".join(sorted(headers_to_sign)) \ No newline at end of file diff --git a/backend/config.py b/backend/config.py index 2689e32..1262773 100644 --- a/backend/config.py +++ b/backend/config.py @@ -1,5 +1,6 @@ import os import os.path as path +from tinydb import TinyDB, Query basedir = os.path.normpath(os.path.join(os.path.dirname(__file__), "..")) @@ -14,7 +15,7 @@ class ProductionConfig(Config): class DevelopmentConfig(Config): # ASKI/user FILES_DIR = os.path.join(basedir, "user") - + DB_CONFIG_FILE = os.path.join(basedir, "config.json") # /ASKI/aski/models MODELS_DIR = os.path.join(basedir, "backend/models/") @@ -32,8 +33,13 @@ class TestingConfig(Config): # ASKI/user FILES_DIR = os.path.join(basedir, "user") + ############DB Config############### + DB_CONFIG_FILE = os.path.join(basedir, "config.json") + db = TinyDB(DB_CONFIG_FILE) + DBConfig = Query() # /ASKI/aski/models MODELS_DIR = os.path.join(basedir, "backend/models/") + CONFIG_DIR = os.path.join(basedir) # /ASKI/aski/datasets DATASETS_DIR = os.path.join(basedir, "backend/datasets/") @@ -46,6 +52,10 @@ class TestingConfig(Config): OPENAPI_KEY = os.environ.get('OPENAPI_KEY', "") all_modules = {"openai":"backend.server.utils.openai_utils"} + BOT_EMAIL = 'blazetranscriptionbot@webex.bot' + + SLACK_BOT_TOKEN = os.environ.get('SLACK_BOT_TOKEN', "") + SLACK_APP_TOKEN = os.environ.get('SLACK_APP_TOKEN', "") @classmethod @@ -53,8 +63,11 @@ def public_config(self): return { "WEBEX_BOT_TOKEN": self.WEBEX_BOT_TOKEN, "WEBEX_ACCESS_TOKEN":self.WEBEX_ACCESS_TOKEN, - "OPENAPI_KEY":self.OPENAPI_KEY + "OPENAPI_KEY":self.OPENAPI_KEY, + "SLACK_APP_TOKEN": self.SLACK_APP_TOKEN, + "SLACK_BOT_TOKEN": self.SLACK_BOT_TOKEN } + @classmethod def yaml_allowed_moduls(cls,yaml_defined_modules): allowed_modules = {} diff --git a/backend/datasets/common/Swagger.py b/backend/datasets/common/Swagger.py new file mode 100644 index 0000000..0e46336 --- /dev/null +++ b/backend/datasets/common/Swagger.py @@ -0,0 +1,21 @@ +import requests +import json +import os.path as path +import datetime +from backend.config import TestingConfig +from backend.auth.escherauth import EscherRequestsAuth +from backend.datasets.interfaces.SwaggerBase import SwaggerBase +from backend.utils import make_request + + + +class Swagger(SwaggerBase): + def fetch_api_docs(self): + + yaml_config = TestingConfig.db.get(TestingConfig.DBConfig.type == 'yaml_config') + + ######## Change according to swagger link ########## + response = make_request(yaml_config["config"]["Swagger"]["auth_type"], 'get', yaml_config["config"]["Swagger"]["url"], yaml_config["config"]["Swagger"]) + ############## + + return response \ No newline at end of file diff --git a/backend/datasets/interfaces/SwaggerBase.py b/backend/datasets/interfaces/SwaggerBase.py new file mode 100644 index 0000000..7c7c005 --- /dev/null +++ b/backend/datasets/interfaces/SwaggerBase.py @@ -0,0 +1,117 @@ +import requests +import json +import os.path as path +import datetime +from backend.config import TestingConfig + + +class SwaggerBase: + functions_supported = ["search","summarization","functions"] + + def __init__(self): + self.date_format = '%Y%m%dT%H%M%SZ' + self.date_string = datetime.datetime.utcnow().strftime(self.date_format) + self.date = datetime.datetime.strptime(self.date_string, self.date_format) + self.swagger_url= None + self.headers = None + self.auth = None + self._class_name = 'Swagger' + self._dataset_name = 'Swagger' + + self.swagger_json = self.fetch_api_docs().json() + + self.file_name = "functions.json" + self.api_info = self.extract_api_information(self.swagger_json) + + def fetch_api_docs(self): + pass + + + def _get_class_name(self): + return self._class_name + + def _get_dataset_name(self): + return self._dataset_name + + + def dereference_schema(self,ref, definitions): + """ + Dereference a $ref pointer to fetch the actual schema from definitions. + + :param ref: The $ref string. + :param definitions: The Swagger definitions section. + :return: The dereferenced schema. + """ + if not ref.startswith("#/definitions/"): + return {} + ref_name = ref.split("/")[2] + return definitions.get(ref_name, {}) + + def extract_param_type(self,param, definitions): + """ + Extract the type of a parameter from Swagger data. + + :param param: The Swagger parameter data. + :param definitions: The Swagger definitions section. + :return: The parameter type and description and item type + to take care of the case where parameter type is array + """ + # Direct type from parameter + param_items = '' + param_type = param.get('type') + param_description = param.get('description', '') + if param_type == 'array': + param_items = param.get('items', '') + # If not found, look inside the schema + if 'schema' in param: + schema = param['schema'] + if '$ref' in schema: + schema = self.dereference_schema(schema['$ref'], definitions) + param_type = schema.get('type', param_type) + param_description = schema.get('description', param_description) + + return param_type or 'unknown', param_description, param_items + + def extract_api_information(self,swagger_data): + api_info = [] + + definitions = swagger_data.get('definitions', {}) + # api_info_description = swagger_data.get('info', {}).get('description', '') + + paths = swagger_data.get('paths', {}) + for path, methods in paths.items(): + for method, details in methods.items(): + function_name = details.get('operationId', method + path.replace("/", "_")) + + # Extract parameters, their types, and descriptions + params = details.get('parameters', []) + properties = {} + required = [] + for param in params: + param_type, param_description, param_items = self.extract_param_type(param, definitions) + if param_type == 'array': + properties[param['name']] = {'type': param_type, 'description': param_description, 'items': param_items} + else: + properties[param['name']] = {'type': param_type, 'description': param_description} + if param.get('required'): + required.append(param['name']) + summary = details.get('summary') + # Prioritize the description from the 'info' section + description = details.get('description', '') + tags = details.get('tags', []) + + api_info.append({ + 'name': function_name, + 'description': description if description else summary, + 'parameters': { + 'type': 'object', + 'properties': properties, + 'required': required + }, + 'summary': summary, + 'path': path, + 'tags': tags + }) + + return api_info + diff --git a/backend/models/common/LLM.py b/backend/models/common/LLM.py new file mode 100644 index 0000000..4ec179d --- /dev/null +++ b/backend/models/common/LLM.py @@ -0,0 +1,21 @@ + + +# Copyright 2022 Cisco Systems, Inc. and its affiliates +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 + + +class LLMs(): + pass diff --git a/backend/models/common/OpenAI.py b/backend/models/common/OpenAI.py index f382f7a..b528a85 100644 --- a/backend/models/common/OpenAI.py +++ b/backend/models/common/OpenAI.py @@ -1,5 +1,16 @@ import openai from flask import current_app +import openai +import os +import inspect +import re +import json +from dotenv import load_dotenv +import requests +from typing import Callable, Dict +import datetime +from collections import defaultdict + def get_openAI_info(): """ @@ -21,9 +32,11 @@ def get_openAI_info(): return model_info class OpenAI(): - tasks_supported = ["actionables","summarization"] + tasks_supported = ["actionables","summarization","chat","functions"] + model = "gpt-3.5-turbo-0613" def __init__(self): + self._info = get_openAI_info() def _get_model_info(self): @@ -77,4 +90,128 @@ def _summarize_text(self, text_to_summarize): def get_actionables(self,text): response = self.gpt_analysis("actionables",text) - return response['choices'][0]['text'] \ No newline at end of file + return response['choices'][0]['text'] + + def parse_docstring(self,function: Callable) -> Dict: + doc = inspect.getdoc(function) + + function_description = re.search(r'(.*?)Parameters', doc, re.DOTALL).group(1).strip() + parameters_description = re.findall(r'(\w+)\s*:\s*([\w\[\], ]+)\n(.*?)(?=\n\w+\s*:\s*|\nReturns|\nExample$)', doc, re.DOTALL) + + returns_description_match = re.search(r'Returns\n(.*?)(?=\n\w+\s*:\s*|$)', doc, re.DOTALL) + returns_description = returns_description_match.group(1).strip() if returns_description_match else None + + example = re.search(r'Example\n(.*?)(?=\n\w+\s*:\s*|$)', doc, re.DOTALL) + example_description = example.group(1).strip() if example else None + + signature_params = list(inspect.signature(function).parameters.keys()) + properties = {} + required = [] + for name, type, description in parameters_description: + name = name.strip() + type = type.strip() + description = description.strip() + + required.append(name) + properties[name] = { + "type": type, + "description": description, + } + if len(signature_params) != len(required): + print(f'Signature params : {signature_params}, Required params : {required}') + raise ValueError(f"Number of parameters in function signature ({signature_params}) does not match the number of parameters in docstring ({required})") + for param in signature_params: + if param not in required: + raise ValueError(f"Parameter '{param}' in function signature is missing in the docstring") + + parameters = { + "type": "object", + "properties": properties, + "required": required, + } + function_dict = { + "name": function.__name__, + "description": function_description, + "parameters": parameters, + "returns": returns_description, + # "example": example_description, + } + + return function_dict + + + def run_with_functions(self,messages, function_dicts): + response = '' + print(f"within run_with_functions : {messages} and {function_dicts}") + messages[0]["role"] = "system" + openai.api_key = current_app.config.get('OPENAPI_KEY') + response = openai.ChatCompletion.create( + model=self.model, + messages=messages, + functions=function_dicts, + temperature=0, + ) + + return response + + def get_role_message_dict(role, content=None, fn_name=None, arguments=None, result=None): + message_dict = {"role":role} + if role == "user": + message_dict["content"] = content + elif role == "assistant": + message_dict["content"] = content + message_dict["function_call"] = {} + message_dict["function_call"]["name"] = fn_name + message_dict["function_call"]["arguments"] = arguments + elif role == "function": + message_dict["name"] = fn_name + message_dict["content"] = f'{{"result": {str(result)} }}' + return message_dict + + + def translate_to_openai_functions(self,api_info): + openai_functions = [] + tag_dict = defaultdict(list) + count = 0 + for api in api_info: + if not api['description']: + print(api['name']+' does not have a description! and is using summary') + count += 1 + function_info = { + 'name': api['name'], + 'description': api['description'] if api['description'] else api['summary'], + 'parameters': api['parameters'], + 'path': api['path'], + } + openai_functions.append(function_info) + + for tag in api['tags']: + tag_dict[tag].append(function_info) + + print(f'Total number of api endpoints without description is {count}') + return openai_functions, tag_dict + + def translate_swagger_data(self,swagger_dataset,description_text): + api_info = swagger_dataset.api_info + openai_functions, tag_dict = self.translate_to_openai_functions(api_info) + + + description_text = description_text + for index, tmp_dict in enumerate(swagger_dataset.swagger_json['tags']): + description_text += f'{tmp_dict["name"]} is returned when the following description is satisfied {tmp_dict["description"]},' + + description_text = description_text[:-1] + '.' + + classifier_tag = { + 'name': "classifies_the_tag", + 'description': description_text, + 'method': 'get', + 'path': '/', + 'tags': 'classifier' + } + + openai_functions.append(classifier_tag) + + return openai_functions, swagger_dataset.swagger_json, tag_dict, classifier_tag + + \ No newline at end of file diff --git a/backend/server/__init__.py b/backend/server/__init__.py index efd08e6..9055f70 100644 --- a/backend/server/__init__.py +++ b/backend/server/__init__.py @@ -68,6 +68,7 @@ def create_server_config(data): def create_app(server_config,config_class=TestingConfig): + print(server_config) app = Flask(__name__) CORS(app) # This will enable CORS for all routes socketio = SocketIO(app,cors_allowed_origins="*") diff --git a/backend/server/core/model_views.py b/backend/server/core/model_views.py index b094fe5..66632e6 100644 --- a/backend/server/core/model_views.py +++ b/backend/server/core/model_views.py @@ -6,7 +6,10 @@ from flask_restful import Resource, request from flask import current_app from backend.params.specifications import Specifications -from backend.server.utils.helpers import get_model_object_from_name +from backend.server.utils.helpers import get_model_object_from_name,get_object_from_name +from backend.utils import make_request +from backend.config import TestingConfig + class ModelsList(Resource): def get(self): @@ -297,4 +300,69 @@ def post(self): res, latency = model.file_search(query) - return {'result': res, 'latency': latency}, 200 \ No newline at end of file + return {'result': res, 'latency': latency}, 200 + + +class GetFunctionsFromSwaggerData(Resource): + + def post(self): + print("gettiong called") + request_json = request.json + if any(param not in request_json for param in ['model', 'dataset', "description_text"]): + return "Malformed request", 400 + + model_name = request_json['model'] + model = get_model_object_from_name(model_name, 'functions', current_app.config.get("server_config")) + dataset_name = request_json['dataset'] + dataset_obj = get_object_from_name(dataset_name, current_app.config.get("server_config"), 'dataset') + description_text = request_json['description_text'] + """Get by data""" + functions, swagger_data, tag_dict, classifier_tag = model.translate_swagger_data(dataset_obj,description_text) + + response = { + "functions": functions, + "swagger_data": swagger_data, + "tag_dict": tag_dict, + "classifier_tag":classifier_tag + } + + filepath = path.join(current_app.config.get("FILES_DIR"), "functions.json") + print(filepath) + with open(filepath, "w") as outfile: + json.dump(response, outfile) + + return response + +class RunWithFunctions(Resource): + + def post(self): + request_json = request.json + if any(param not in request_json for param in ['model']): + return "Malformed request", 400 + filepath = open(path.join(current_app.config.get("FILES_DIR"), "functions.json")) + data = json.load(filepath) + model_name = request_json['model'] + model = get_model_object_from_name(model_name, 'functions', current_app.config.get("server_config")) + messages = request_json['messages'] + response = model.run_with_functions(messages,data["tag_dict"]['dashboard-controller']) + return response + +class CallFunction(Resource): + + def post(self): + request_json = request.json + yaml_config = TestingConfig.db.get(TestingConfig.DBConfig.type == 'yaml_config') + auth_type = yaml_config["config"]["Swagger"]["auth_type"] + request_type = request_json["request_type"] + url = request_json["url"] + config = yaml_config["config"]["Swagger"] + data = request_json.get("data",None) + params = request_json.get('params',None) + + response = make_request(auth_type, request_type, url,config,params,data) + print(response.text) + return response.json() + + + + \ No newline at end of file diff --git a/backend/server/core/panoptica_views.py b/backend/server/core/panoptica_views.py new file mode 100644 index 0000000..e94a681 --- /dev/null +++ b/backend/server/core/panoptica_views.py @@ -0,0 +1,9 @@ +from glob import glob +from flask_restful import Resource, request +from flask import current_app + + +class Panoptica_prompt_with_functions(Resource): + + def post(self): + pass \ No newline at end of file diff --git a/backend/server/core/routes.py b/backend/server/core/routes.py index 5103b2a..ce3477a 100644 --- a/backend/server/core/routes.py +++ b/backend/server/core/routes.py @@ -1,5 +1,5 @@ -from .views import Default,ResetServer,Models,Config,TestDynamicApis -from .model_views import ModelsList,ModelDetail,ModelInitilize,ModelSearch,ModelSummary, ModelActionables +from .views import Default,ResetServer,Models,Config,TestDynamicApis,SwaggerConfig +from .model_views import ModelsList,ModelDetail,ModelInitilize,ModelSearch,ModelSummary, ModelActionables, GetFunctionsFromSwaggerData, RunWithFunctions, CallFunction from .dataset_views import DatasetsList,DatasetFilesList,DatasetFilesDetails, ListMeetingTranscripts @@ -13,6 +13,10 @@ "endpoint": ['/config'], "resource":Config }, + { + "endpoint": ['/swagger_config'], + "resource":SwaggerConfig + }, { "endpoint": ['/get_model_checklist'], "resource":Models @@ -55,7 +59,18 @@ "endpoint": ['/actionables'], "resource":ModelActionables }, - + { + "endpoint": ['/functions'], + "resource":GetFunctionsFromSwaggerData + }, + { + "endpoint": ['/run_function'], + "resource":RunWithFunctions + }, + { + "endpoint": ['/call_function'], + "resource":CallFunction + }, { "endpoint": ['/list_webex_meeting_transcripts'], "resource":ListMeetingTranscripts @@ -65,5 +80,4 @@ "resource":TestDynamicApis }, - ] \ No newline at end of file diff --git a/backend/server/core/views.py b/backend/server/core/views.py index bce5479..ca482e4 100644 --- a/backend/server/core/views.py +++ b/backend/server/core/views.py @@ -34,6 +34,13 @@ def get(self): print(current_app.config) return {'response': current_app.config.get('frontend_config')}, 200 + +class SwaggerConfig(Resource): + + def get(self): + + print(current_app) + return {"config": current_app.config.get('frontend_config') }, 200 class ResetServer(Resource): def __init__(self,**kwargs): diff --git a/backend/server/utils/helpers.py b/backend/server/utils/helpers.py index 2947d54..48ddb3c 100644 --- a/backend/server/utils/helpers.py +++ b/backend/server/utils/helpers.py @@ -22,7 +22,6 @@ import json import time - def profile(func): def wrap(*args, **kwargs): if "ASKI_PROFILING" in os.environ: diff --git a/backend/utils.py b/backend/utils.py new file mode 100644 index 0000000..d5ee4c8 --- /dev/null +++ b/backend/utils.py @@ -0,0 +1,29 @@ +import os +import os.path as path +import datetime +import requests +from backend.auth.escherauth import EscherRequestsAuth + +def EscherRequest(request_type,url,config, params=None, data=None): + date_format = '%Y%m%dT%H%M%SZ' + date_string = datetime.datetime.utcnow().strftime(date_format) + date = datetime.datetime.strptime(date_string, date_format) + url = url + headers = {'X-Escher-Date': date_string, + 'host': config["host"], + 'content-type': 'application/json'} + auth=EscherRequestsAuth(config["credential_scope"], + {'current_time': date}, + {'api_key': config["api_key"], 'api_secret': config["api_secret"]}) + + response = requests.request(request_type,url,headers=headers, auth=auth, params=params, data=data) + return response + + +request_function_dict = { + "Escher": EscherRequest, +} + +def make_request(authentication_type, request_type, url, config,params=None, data=None): + response = request_function_dict.get(authentication_type)(request_type, url, config,params=None, data=None) + return response \ No newline at end of file diff --git a/config.json b/config.json new file mode 100755 index 0000000..506199a --- /dev/null +++ b/config.json @@ -0,0 +1 @@ +{"_default": {"1": {"type": "yaml_config", "config": {"Title": "Panoptica Function call with Slack Bot", "function": {"task": ["functions"], "custom": true}, "metrics": {}, "datasets": ["Swagger"], "models_functions": ["OpenAI"], "module": ["openai"], "UI": ["webex_bot"], "Swagger": {"auth_type": "Escher", "url": "https://appsecurity.cisco.com/api/v2/api-docs", "host": "appsecurity.cisco.com", "api_key": "2a797523-3934-4698-9975-af13de9e15ca", "api_secret": "kSN59Kje1AiOfFaTe+itdHiPUnFUIxC1bOs4gJ1kCnk=", "credential_scope": "global/services/portshift_request"}}}}} \ No newline at end of file diff --git a/external_apps/__init__.py b/external_apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/webex_UI/help.py b/external_apps/help.py similarity index 100% rename from webex_UI/help.py rename to external_apps/help.py diff --git a/external_apps/panoptica_utils/panoptica_utils.py b/external_apps/panoptica_utils/panoptica_utils.py new file mode 100644 index 0000000..cc46e02 --- /dev/null +++ b/external_apps/panoptica_utils/panoptica_utils.py @@ -0,0 +1,204 @@ +""" + +This file implements the commands of the webex bot, via the underlying class `PrevMeetings`. + +""" + +from constants import CONSTANTS +import requests +import json +import datetime +import os +import inspect +import re +from dotenv import load_dotenv +from typing import Callable, Dict +from collections import defaultdict + + +base_url = "https://appsecurity.cisco.com/api" + + +def LoadTranscripts(): + transcriptFileName = "webex_transcripts.json" + return transcriptFileName + +""" + +HELPER FUNCTIONS LISTED BELOW + +""" + +def InitilizeSwaggerFunctions(): + url = CONSTANTS.get("webex_api_endpoint")+"/functions" + + payload = json.dumps({ + "model": "OpenAI", + "dataset":"Swagger", + "description_text":"Used when given a question about panoptica to identify the tag of the api that needs to be referenced." + }) + headers = { + 'Content-Type': 'application/json' + } + + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + return json.loads(response.text) + +def GetAPIKeys(): + url = CONSTANTS.get("webex_api_endpoint")+"/swagger_config" + + headers = { + 'Content-Type': 'application/json' + } + + response = requests.request("GET", url, headers=headers) + print(response.text) + return json.loads(response.text) + +def get_role_message_dict(role, content=None, fn_name=None, arguments=None, result=None): + message_dict = {"role":role} + if role == "user": + message_dict["content"] = content + elif role == "assistant": + message_dict["content"] = content + message_dict["function_call"] = {} + message_dict["function_call"]["name"] = fn_name + message_dict["function_call"]["arguments"] = arguments + elif role == "function": + message_dict["name"] = fn_name + message_dict["content"] = f'{{"result": {str(result)} }}' + return message_dict + +def panoptica_call_functions(full_url): + url = CONSTANTS.get("webex_api_endpoint")+"/call_function" + payload = json.dumps({ + "url": full_url, + "request_type":"GET", + "params": None, + "data":None + }) + headers = { + 'Content-Type': 'application/json' + } + + response = requests.request("POST", url, headers=headers, data=payload) + print(response.text) + return response + +def parse_docstring(function: Callable) -> Dict: + doc = inspect.getdoc(function) + + function_description = re.search(r'(.*?)Parameters', doc, re.DOTALL).group(1).strip() + parameters_description = re.findall(r'(\w+)\s*:\s*([\w\[\], ]+)\n(.*?)(?=\n\w+\s*:\s*|\nReturns|\nExample$)', doc, re.DOTALL) + + returns_description_match = re.search(r'Returns\n(.*?)(?=\n\w+\s*:\s*|$)', doc, re.DOTALL) + returns_description = returns_description_match.group(1).strip() if returns_description_match else None + + example = re.search(r'Example\n(.*?)(?=\n\w+\s*:\s*|$)', doc, re.DOTALL) + example_description = example.group(1).strip() if example else None + + signature_params = list(inspect.signature(function).parameters.keys()) + properties = {} + required = [] + for name, type, description in parameters_description: + name = name.strip() + type = type.strip() + description = description.strip() + + required.append(name) + properties[name] = { + "type": type, + "description": description, + } + if len(signature_params) != len(required): + print(f'Signature params : {signature_params}, Required params : {required}') + raise ValueError(f"Number of parameters in function signature ({signature_params}) does not match the number of parameters in docstring ({required})") + for param in signature_params: + if param not in required: + raise ValueError(f"Parameter '{param}' in function signature is missing in the docstring") + + parameters = { + "type": "object", + "properties": properties, + "required": required, + } + function_dict = { + "name": function.__name__, + "description": function_description, + "parameters": parameters, + "returns": returns_description, + # "example": example_description, + } + + return function_dict + +def run_with_functions(messages): + url = CONSTANTS.get("webex_api_endpoint")+"/run_function" + + payload = json.dumps({ + "model": "OpenAI", + "messages": messages + }) + headers = { + 'Content-Type': 'application/json' + } + + response = requests.request("POST", url, headers=headers, data=payload) + + print(response.text) + + return json.loads(response.text) + +def prompt_with_functions(prompt, functions, function_dict): + # setup_database() + #prompt += prompt_append() + prompt = "You are an expert in Panoptica, which is a tool to give security insights in Kubernetes Clusters, API security. You are not aware of anything else. All queries should either use one of the APIs provided or should ask the user to rephrase the qurey: " + prompt + output = [] + fn_names_dict = {} + + if function_dict: + function_dicts = functions + for fn in functions: + fn_names_dict[fn['name']] = fn + else: + for fn in functions: + fn_names_dict[fn.__name__] = fn + function_dicts = [parse_docstring(fun) for fun in functions] + # print(function_dicts) + messages = [get_role_message_dict("user", content=(prompt))] + + response = run_with_functions(messages) + + if response["choices"][0]["finish_reason"] == "stop": + print("Received STOP signal from GPT.") + print() + print() + + elif response["choices"][0]["finish_reason"] == "function_call": + print("Received FUNCTION_CALL signal from GPT.") + fn_name = response["choices"][0]["message"]["function_call"]["name"] + arguments = response["choices"][0]["message"]["function_call"]["arguments"] + #json_arguments = json.loads(arguments) + #function = fn_names_dict[fn_name] + paths = fn_names_dict[fn_name]['path'] + full_url = base_url + paths + print(f"Running the {fn_name} function locally with args {arguments}") + response = panoptica_call_functions(full_url) + #result = function(**json_arguments) + print(f"Finished running {fn_name}. Output is {response._content}") + print() + print() + output.append(response._content.decode('utf-8')) + # output.append(f'You should call the {response.choices[0].message["function_call"].name} function using the following arguments : \n {response.choices[0].message["function_call"].arguments} \n Function raw output : {str(result)}') + messages.append(get_role_message_dict("assistant", fn_name=fn_name, arguments=arguments)) + messages.append(get_role_message_dict("function", fn_name=fn_name)) + response = run_with_functions(messages) + + + return output + +def RunFunction(message,functions): + # print(message,functions) + output = prompt_with_functions(message, functions["tag_dict"]['dashboard-controller'],function_dict=True) + return output \ No newline at end of file diff --git a/webex_UI/webex_bot/constants.py b/external_apps/slackbot/constants.py similarity index 100% rename from webex_UI/webex_bot/constants.py rename to external_apps/slackbot/constants.py diff --git a/external_apps/slackbot/run.py b/external_apps/slackbot/run.py new file mode 100644 index 0000000..d0a8ed9 --- /dev/null +++ b/external_apps/slackbot/run.py @@ -0,0 +1,31 @@ +from urllib import response +from slack_bolt import App +from slack_bolt.adapter.socket_mode import SocketModeHandler +import os +import json +from dotenv import load_dotenv +from external_apps.panoptica_utils.panoptica_utils import RunFunction, InitilizeSwaggerFunctions, GetAPIKeys + + +load_dotenv() +functions = InitilizeSwaggerFunctions() +api_keys = GetAPIKeys() + +app = App(token=api_keys["config"]["SLACK_BOT_TOKEN"]) + + + +@app.event("app_mention") +def mention_handler(body, say): + words = body["event"]["text"].split() + message = ' '.join(words[1:]) + res = RunFunction(message, functions) + print(res) + say(str(res)) + + +if __name__ == "__main__": + handler = SocketModeHandler(app, api_keys["config"]["SLACK_APP_TOKEN"]) + handler.start() + + \ No newline at end of file diff --git a/webex_UI/webex_bot/cmds.py b/external_apps/webex_bot/cmds.py similarity index 82% rename from webex_UI/webex_bot/cmds.py rename to external_apps/webex_bot/cmds.py index 968e001..b59055d 100644 --- a/webex_UI/webex_bot/cmds.py +++ b/external_apps/webex_bot/cmds.py @@ -12,6 +12,7 @@ from webexteamssdk.models.cards.actions import OpenUrl from help import SummarizeTranscripts,SearchTranscripts,ListMeetingTranscripts,ActionablesTranscripts +from external_apps.panoptica_utils.panoptica_utils import RunFunction, InitilizeSwaggerFunctions, GetAPIKeys class EmptySpace(Command): def __init__(self): @@ -100,4 +101,20 @@ def __init__(self, transcriptFileName): def execute(self, message, attachment_actions, query_info): res = ActionablesTranscripts(self.transcriptFileName,message) - return f"{res}" \ No newline at end of file + return f"{res}" + + +class Panoptica(Command): + def __init__(self, functions): + super().__init__( + command_keyword="function", + help_message="function: write query with argument to run function", + card = None + ) + self.functions = functions + + def execute(self, message, attachment_actions, query_info): + res = RunFunction(message, self.functions) + return f"{res}" + + \ No newline at end of file diff --git a/external_apps/webex_bot/constants.py b/external_apps/webex_bot/constants.py new file mode 100644 index 0000000..a954717 --- /dev/null +++ b/external_apps/webex_bot/constants.py @@ -0,0 +1,3 @@ +CONSTANTS = { + "webex_api_endpoint":"http://localhost:3000/" +} \ No newline at end of file diff --git a/webex_UI/webex_bot/help.py b/external_apps/webex_bot/help.py similarity index 93% rename from webex_UI/webex_bot/help.py rename to external_apps/webex_bot/help.py index 4368138..699de34 100644 --- a/webex_UI/webex_bot/help.py +++ b/external_apps/webex_bot/help.py @@ -7,8 +7,17 @@ from constants import CONSTANTS import requests import json +import datetime +import os +import inspect +import re +from dotenv import load_dotenv +from typing import Callable, Dict +from collections import defaultdict +base_url = "https://appsecurity.cisco.com/api" + def LoadTranscripts(): transcriptFileName = "webex_transcripts.json" return transcriptFileName @@ -140,4 +149,6 @@ def ActionablesTranscripts(transcriptFileName,message): response = requests.request("POST", CONSTANTS.get("webex_api_endpoint")+"/actionables", headers=headers, data=payload).json() print(response) res = " \n ".join(response["result"].split("|")) - return res \ No newline at end of file + return res + + diff --git a/webex_UI/webex_bot/main.py b/external_apps/webex_bot/main.py similarity index 88% rename from webex_UI/webex_bot/main.py rename to external_apps/webex_bot/main.py index 1c6822f..84e7307 100644 --- a/webex_UI/webex_bot/main.py +++ b/external_apps/webex_bot/main.py @@ -38,8 +38,10 @@ """ from webex_bot.webex_bot import WebexBot -from cmds import SummarAcross, EmptySpace, SearchAcross, ListTranscripts, Actionables +from cmds import SummarAcross, EmptySpace, SearchAcross, ListTranscripts, Actionables, Panoptica from help import LoadTranscripts, InitilizeTranscripts +from external_apps.panoptica_utils.panoptica_utils import RunFunction, InitilizeSwaggerFunctions, GetAPIKeys + from constants import CONSTANTS import requests @@ -51,17 +53,15 @@ transcriptsFileName = "webex_transcripts.json" InitilizeTranscripts(transcriptsFileName) +functions = InitilizeSwaggerFunctions() bot = WebexBot(bot_token) -print(dir(bot)) -print(bot.device_info) -print(bot.device_url) -print(bot.on_message) -print(bot.websocket) bot.add_command(EmptySpace()) bot.add_command(ListTranscripts()) bot.add_command(SummarAcross(transcriptsFileName)) bot.add_command(SearchAcross(transcriptsFileName)) bot.add_command(Actionables(transcriptsFileName)) +bot.add_command(Panoptica(functions)) + bot.run() \ No newline at end of file diff --git a/webex_UI/webex_bot/requirements.txt b/external_apps/webex_bot/requirements.txt similarity index 100% rename from webex_UI/webex_bot/requirements.txt rename to external_apps/webex_bot/requirements.txt diff --git a/webex_UI/webex_meetings/index.html b/external_apps/webex_meetings/index.html similarity index 70% rename from webex_UI/webex_meetings/index.html rename to external_apps/webex_meetings/index.html index c6f1fd4..3d99e0e 100644 --- a/webex_UI/webex_meetings/index.html +++ b/external_apps/webex_meetings/index.html @@ -18,8 +18,19 @@

BLAZE - WebEx Plugin

-

Research edition of WebEx Plugin, powered by BLAZE.

- +

Research edition of WebEx Plugin, powered by BLAZE.

+
+ + + +
+ +
+ +
+
+

Make sure to turn on WebEx Assistant prior to clicking Submit!

+
diff --git a/external_apps/webex_meetings/index.js b/external_apps/webex_meetings/index.js new file mode 100644 index 0000000..82cb342 --- /dev/null +++ b/external_apps/webex_meetings/index.js @@ -0,0 +1,202 @@ +let webex; +let receiveTranscriptionOption = true; +let transcript_final_result = {"transcript":""}; +let meetings; +let current_meeting; +let actionables=""; +var ACCESS_TOKEN = ""; +let is_bot = false; +let botEmailID = ""; +let time_interval = 60000; +let interval = 1 +let botIntervalID; + +function summary() { + // WARNING: For POST requests, body is set to null by browsers. + console.log(transcript_final_result["transcript"]) + var data = JSON.stringify({ + "module_name": "openai", + "method_type": "module_function", + "method_name": "process_transcript", + "args": [ + transcript_final_result["transcript"] + ] + }); + + var xhr = new XMLHttpRequest(); + xhr.withCredentials = false; + + xhr.addEventListener("readystatechange", function() { + if(this.readyState === 4) { + response = JSON.parse(this.responseText) + console.log(response); + let summary = response["result"]["summary"] + let summaryContainer = document.getElementById('summaryContainer') + summaryContainer.innerHTML = `
${summary}
` + + actionables = response["result"]["actionables"] + let actionablesContainer = document.getElementById('actionablesContainer') + actionablesContainer.innerHTML = `
${actionables}
` + + let time = response["result"]["agenda"] + let timeContainer = document.getElementById('timeContainer') + timeContainer.innerHTML = `
${time}
` + + // index.html + } + }); + + xhr.open("POST", "http://127.0.0.1:3000/dynamic_query"); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.setRequestHeader('Access-Control-Allow-Origin','*'); + xhr.send(data); +} + + +function bot_response() { + // WARNING: For POST requests, body is set to null by browsers. "blazetranscriptionbot@webex.bot" + + console.log("sending actionables to bot") + let data = JSON.stringify({ + "toPersonEmail": botEmailID , + "text": actionables, + + }); + + var xhr = new XMLHttpRequest(); + xhr.withCredentials = false; + + + + xhr.open("POST", "https://webexapis.com/v1/messages"); + xhr.setRequestHeader("Content-Type", "application/json"); + xhr.setRequestHeader('Authorization',`Bearer ${ACCESS_TOKEN}`); + xhr.send(data); + + +} + +// Send function to send keys/ids to the REST API +function submitForm() { + var webexId = document.getElementById("access-token").value; + botEmailID = document.getElementById("bot-email-id").value; + interval = document.getElementById("time-interval").value; + + if (botEmailID !== "") { + is_bot = true + if (interval !== ""){ + time_interval = 60000 * interval + } + } + if(is_bot===true){ + if(!botIntervalID){ + botIntervalID = setInterval(bot_response, time_interval); + } + + } + + // Call big scrip tto use WebexID key to register the mtg + + ACCESS_TOKEN = webexId; + document.getElementById("iniform").style.display = "none"; + registerMeeting(); + + +} + +function registerMeeting() { + + console.log("Entered script, got access token"); + console.log(ACCESS_TOKEN); + + initWebex(); + console.log("Initialized Webex"); + + setTimeout(function() { + register(); + console.log("Register meeting"); + }, 2000); + + +} + +function initWebex(){ + webex = window.webex = Webex.init({ + config: { + logger: { + level: "debug", + }, + meetings: { + reconnection: { + enabled: true, + }, + enableRtx: true, + experimental: { + enableUnifiedMeetings: true, + }, + }, + // Any other sdk config we need + }, + credentials: { + access_token: + ACCESS_TOKEN, + }, + }); + + webex.once("ready", () => { + console.log("Authentication#initWebex() :: Webex Ready"); + }); +} + + + +function register(){ + webex.meetings.register().then(() => { + console.log("successful registered"); + webex.meetings + .syncMeetings() + .then( + () => + new Promise((resolve) => { + setTimeout(() => resolve(), 3000); + }) + ) + .then(() => { + console.log( + "MeetingsManagement#collectMeetings() :: successfully collected meetings" + ); + meetings = webex.meetings.getAllMeetings(); + + if (webex.meetings.registered) { + console.log(meetings); + current_meeting = meetings[Object.keys(meetings)[0]]; + console.log(current_meeting); + current_meeting.on( + "meeting:receiveTranscription:started", + (payload) => { + if (payload["type"]=="transcript_final_result"){ + transcript_final_result["transcript"] = transcript_final_result["transcript"] + ", " + payload["transcription"]; + + } + + console.log(transcript_final_result) + + } + ); + } + const joinOptions = { + moveToResource: false, + resourceId: webex.devicemanager._pairedDevice + ? webex.devicemanager._pairedDevice.identity.id + : undefined, + receiveTranscription: receiveTranscriptionOption, + }; + + current_meeting.join(joinOptions); + }); + }); +} + + +const intervalID = setInterval(summary, 10000); + diff --git a/requirements.txt b/requirements.txt index df6c472..c50900f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -33,3 +33,9 @@ gevent==22.10.2 webex-bot==0.4.0 webexteamssdk==1.6.1 openai==0.27.8 +slack-bolt==1.18.0 +slack-sdk==3.23.0 +slackbot==1.0.5 +slackclient==1.3.2 +slacker==0.14.0 +tinydb diff --git a/run.sh b/run.sh index 5df68cd..a3e277f 100755 --- a/run.sh +++ b/run.sh @@ -24,7 +24,9 @@ frontend(){ server(){ echo "Running $yaml" - is_elastic=$(echo $(python parse_yaml.py $yaml models_search)| grep -c "ElasticBERT") + is_elastic=$(echo $(python3.9 parse_yaml.py $yaml models_search)| grep -c "ElasticBERT") + is_webexBot=$(echo $(python3.9 parse_yaml.py $yaml UI)| grep -c "webex_bot") + is_slackBot=$(echo $(python3.9 parse_yaml.py $yaml UI)| grep -c "slack_bot") if (($is_elastic >= 1 )) then is_elastic_service_running=$(echo $(curl http://localhost:9200/_cluster/health) | grep -c "elasticsearch") @@ -39,14 +41,24 @@ server(){ then echo -e "\n\n\n\nPlease Run Elastic Search to run the backend\n\n\n\n" else - python run_backend.py $yaml + python run_backend.py $yaml & disown fi else - python run_backend.py $yaml + python run_backend.py $yaml & disown fi else - python run_backend.py $yaml + python run_backend.py $yaml & disown fi + if (($is_webexBot >= 1 )) + then + sleep 10 + python external_apps/webex_bot/main.py + fi + if (($is_slackBot >= 1 )) + then + sleep 10 + python external_apps/slack_bot/run.py + fi } echo $1 @@ -75,7 +87,7 @@ case $1 in ;; "bot") - python webex_UI/webex_bot/main.py + python external_apps/webex_bot/main.py ;; *) diff --git a/run_backend.py b/run_backend.py index 88fb8f6..9798461 100644 --- a/run_backend.py +++ b/run_backend.py @@ -19,7 +19,7 @@ import argparse from multiprocessing import Process import yaml - +from tinydb import TinyDB, Query from backend.server import create_app, run_app_server, create_server_config from frontend.app import run_client @@ -29,6 +29,9 @@ # run_elastic_search_service = subprocess.Popen( # ["./elasticsearch"], cwd="../elasticsearch/bin") + db = TinyDB('config.json') + db.drop_tables() + Config = Query() parser = argparse.ArgumentParser() parser.add_argument('yaml_file', help='YAML file that describes the NLP pipeline', @@ -41,6 +44,10 @@ with open(args.yaml_file, mode="rt", encoding="utf-8") as file: data = yaml.safe_load(file) + db.insert({ + "type":"yaml_config", + "config":data + }) config = create_server_config(data) app,sockio = create_app(data) port = args.p diff --git a/webex_UI/webex_meetings/index.js b/webex_UI/webex_meetings/index.js deleted file mode 100644 index 9895bb0..0000000 --- a/webex_UI/webex_meetings/index.js +++ /dev/null @@ -1,121 +0,0 @@ -let webex; -let receiveTranscriptionOption = true; -let transcript_final_result = {"transcript":""}; -let meetings; -let current_meeting; - - -function summary() { - // WARNING: For POST requests, body is set to null by browsers. - console.log(transcript_final_result["transcript"]) - var data = JSON.stringify({ - "module_name": "openai", - "method_type": "module_function", - "method_name": "process_transcript", - "args": [ - transcript_final_result["transcript"] - ] - }); - - var xhr = new XMLHttpRequest(); - xhr.withCredentials = false; - - xhr.addEventListener("readystatechange", function() { - if(this.readyState === 4) { - response = JSON.parse(this.responseText) - console.log(response); - let summary = response["result"]["summary"] - let summaryContainer = document.getElementById('summaryContainer') - summaryContainer.innerHTML = `
${summary}
` - - let actionables = response["result"]["actionables"] - let actionablesContainer = document.getElementById('actionablesContainer') - actionablesContainer.innerHTML = `
${actionables}
` - - let time = response["result"]["agenda"] - let timeContainer = document.getElementById('timeContainer') - timeContainer.innerHTML = `
${time}
` - } - }); - - xhr.open("POST", "http://127.0.0.1:3000/dynamic_query"); - xhr.setRequestHeader("Content-Type", "application/json"); - xhr.setRequestHeader('Access-Control-Allow-Origin','*'); - xhr.send(data); -} - - - -webex = window.webex = Webex.init({ - config: { - logger: { - level: "debug", - }, - meetings: { - reconnection: { - enabled: true, - }, - enableRtx: true, - experimental: { - enableUnifiedMeetings: true, - }, - }, - // Any other sdk config we need - }, - credentials: { - access_token: - "", - }, -}); - -webex.once("ready", () => { - console.log("Authentication#initWebex() :: Webex Ready"); -}); - -webex.meetings.register().then(() => { - console.log("successful registered"); - webex.meetings - .syncMeetings() - .then( - () => - new Promise((resolve) => { - setTimeout(() => resolve(), 3000); - }) - ) - .then(() => { - console.log( - "MeetingsManagement#collectMeetings() :: successfully collected meetings" - ); - meetings = webex.meetings.getAllMeetings(); - - if (webex.meetings.registered) { - console.log(meetings); - current_meeting = meetings[Object.keys(meetings)[0]]; - console.log(current_meeting); - current_meeting.on( - "meeting:receiveTranscription:started", - (payload) => { - if (payload["type"]=="transcript_final_result"){ - transcript_final_result["transcript"] = transcript_final_result["transcript"] + ", " + payload["transcription"]; - - } - - console.log(transcript_final_result) - - } - ); - } - const joinOptions = { - moveToResource: false, - resourceId: webex.devicemanager._pairedDevice - ? webex.devicemanager._pairedDevice.identity.id - : undefined, - receiveTranscription: receiveTranscriptionOption, - }; - - current_meeting.join(joinOptions); - }); -}); - -const intervalID = setInterval(summary, 100000); - diff --git a/yaml/07_panoptica_SlackBot.yaml b/yaml/07_panoptica_SlackBot.yaml new file mode 100644 index 0000000..906b979 --- /dev/null +++ b/yaml/07_panoptica_SlackBot.yaml @@ -0,0 +1,18 @@ +Title: Panoptica Function call with Slack Bot +function: + task: ['functions'] + custom: true +metrics: {} +datasets: +- Swagger +models_functions: +- OpenAI +module: ['openai'] +UI: ["webex_bot"] +Swagger: + auth_type: 'Escher' + url: '' + host: '' + api_key: '' + api_secret: '' + credential_scope: '' diff --git a/yaml/07_panoptica_bot.yaml b/yaml/07_panoptica_bot.yaml new file mode 100644 index 0000000..a8033ea --- /dev/null +++ b/yaml/07_panoptica_bot.yaml @@ -0,0 +1,16 @@ +Title: Panoptica Function call with WebEx Bot +function: + task: ['functions'] + custom: true +metrics: {} +datasets: +- Swagger +models_functions: +- OpenAI +module: ['openai'] +UI: ["webex_bot"] +Swagger: + url: '' + host: '' + access_key: '' + access_secret: ''