# coding=utf-8 # Copyright 2023 The AIWaves Inc. team. # # 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. import json from PIL import Image import requests from typing import List, Tuple class GradioConfig: # How many avatars are currently registered POINTER = 0 # Avatar image. You can add or replace. AGENT_HEAD_URL = [ "https://img.touxiangwu.com/zb_users/upload/2023/06/202306241687579617434043.jpg", "https://img.touxiangwu.com/zb_users/upload/2023/06/202306241687592097408547.jpg", "https://img.touxiangwu.com/zb_users/upload/2023/06/202306141686726561699613.jpg", "https://img.touxiangwu.com/zb_users/upload/2023/06/202306141686726561275758.jpg", "https://img.touxiangwu.com/uploads/allimg/2021090300/ry5k31wt33c.jpg", "https://img.touxiangwu.com/uploads/allimg/2021090300/0ls2gmwhrf5.jpg", "https://img.touxiangwu.com/zb_users/upload/2023/02/202302281677545695326193.jpg", "https://img.touxiangwu.com/zb_users/upload/2023/03/202303271679886128550253.jpg", "https://img.touxiangwu.com/zb_users/upload/2023/06/202306141686711344407060.jpg", "https://img.touxiangwu.com/zb_users/upload/2023/06/202306141686711345834296.jpg", "https://img.touxiangwu.com/zb_users/upload/2023/05/202305171684311194291520.jpg", "https://img.touxiangwu.com/zb_users/upload/2023/05/202305171684311196958993.jpg", "https://img.touxiangwu.com/uploads/allimg/2021082612/vr0bkov0dwl.jpg", "https://img.touxiangwu.com/uploads/allimg/2021082612/auqx5zfsv5g.jpg", "https://img.touxiangwu.com/uploads/allimg/2021082612/llofpivtwls.jpg", "https://img.touxiangwu.com/uploads/allimg/2021082612/3j2sdot3ye0.jpg", "https://img.touxiangwu.com/2020/3/nQfYf2.jpg", "https://img.touxiangwu.com/zb_users/upload/2023/08/202308131691918068774532.jpg", "https://img.touxiangwu.com/zb_users/upload/2023/08/202308131691918068289945.jpg", "https://img.touxiangwu.com/zb_users/upload/2023/08/202308131691918069785183.jpg", "https://img.touxiangwu.com/zb_users/upload/2023/06/202306141686726561292003.jpg", "https://img.touxiangwu.com/zb_users/upload/2023/06/202306141686726561578616.jpg", "https://img.touxiangwu.com/zb_users/upload/2023/06/202306141686726564597524.jpg" ] USER_HEAD_URL = "https://img.touxiangwu.com/zb_users/upload/2023/05/202305301685407468585486.jpg" # The css style of gradio.Chatbot CSS = """ #chatbot1 .user { background-color:transparent; border-color:transparent; } #chatbot1 .bot { background-color:transparent; border-color:transparent; } #btn {color: red; border-color: red;} """ ID = ["USER", "AGENT", "SYSTEM"] # Bubble template BUBBLE_CSS = { # Background-color Name-color Name-content Font-color Font-size Content Avatar-URL "USER": """

{}

{}

USER
""", # Avatar-URL Background-color Name-color Name-Content Font-color Font-size Content "AGENT": """
AGENT

{}

{}

""", # Background-color Font-size Font-color Name Content "SYSTEM": """

{}:{}

""" } ROLE_2_NAME = {} OBJECT_INFO = { "User": { # https://img-blog.csdnimg.cn/img_convert/7c20bc39ac69b6972a22e18762d02db3.jpeg "head_url": USER_HEAD_URL, "bubble_color": "#95EC69", "text_color": "#000000", "font_size": 0, "id": "USER" }, "System": { # https://img-blog.csdnimg.cn/img_convert/e7e5887cfff67df8c2205c2ef0e5e7fa.png "head_url": "https://img.touxiangwu.com/zb_users/upload/2023/03/202303141678768524747045.jpg", "bubble_color": "#7F7F7F", ##FFFFFF "text_color": "#FFFFFF", ##000000 "font_size": 0, "id": "SYSTEM" }, "wait": { "head_url": "https://img.touxiangwu.com/zb_users/upload/2022/12/202212011669881536145501.jpg", "bubble_color": "#E7CBA6", "text_color": "#000000", "font_size": 0, "id": "AGENT" }, "Recorder": { "head_url": "https://img.touxiangwu.com/zb_users/upload/2023/02/202302281677545695326193.jpg", "bubble_color": "#F7F7F7", "text_color": "#000000", "font_size": 0, "id": "AGENT" } } @classmethod def color_for_img(cls, url): """ Extract the main colors from the picture and set them as the background color, then determine the corresponding text color. """ def get_main_color(image): image = image.convert("RGB") width, height = image.size pixels = image.getcolors(width * height) most_common_pixel = max(pixels, key=lambda item: item[0]) return most_common_pixel[1] def is_dark_color(rgb_color): r, g, b = rgb_color luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255 return luminance < 0.5 def download_image(url): print(f"binding: {url}") response = requests.get(url) if response.status_code == 200: with open('image.jpg', 'wb') as f: f.write(response.content) def rgb_to_hex(color): return "#{:02X}{:02X}{:02X}".format(color[0], color[1], color[2]) def get_color(image_url): download_image(image_url) image = Image.open("image.jpg") main_color = get_main_color(image) is_dark = is_dark_color(main_color) if is_dark: font_color = "#FFFFFF" else: font_color = "#000000" return rgb_to_hex(main_color), font_color return get_color(url) @classmethod def init(cls, JSON): # Deprecated with open(JSON) as f: sop = json.load(f) cnt = 0 FISRT_NODE = True fisrt_node_roles = [] for node_name in sop['nodes']: node_info = sop['nodes'][node_name] agent_states = node_info['agent_states'] for agent_role in agent_states: name = agent_states[agent_role]['style']['name'] cls.ROLE_2_NAME[agent_role] = name if FISRT_NODE: fisrt_node_roles.append(agent_role) bubble_color, text_color = cls.color_for_img(cls.AGENT_HEAD_URL[cnt]) cls.OBJECT_INFO[name] = { "head_url": f"{cls.AGENT_HEAD_URL[cnt]}", "bubble_color": bubble_color, "text_color": text_color, "font_size": 0, "id": "AGENT" } cnt += 1 if FISRT_NODE: FISRT_NODE = False print(cls.OBJECT_INFO) for usr_name in cls.OBJECT_INFO: if cls.OBJECT_INFO[usr_name]["id"] == "SYSTEM": cls.OBJECT_INFO[usr_name]["font_size"] = 12 elif cls.OBJECT_INFO[usr_name]["id"] in ["USER", "AGENT"]: cls.OBJECT_INFO[usr_name]["font_size"] = 16 else: assert False return fisrt_node_roles @classmethod def add_agent(cls, agents_name:List,p:int=None): if p != None: cls.POINTER = p for name in agents_name: bubble_color, text_color = cls.color_for_img(cls.AGENT_HEAD_URL[cls.POINTER]) cls.OBJECT_INFO[name] = { "head_url": f"{cls.AGENT_HEAD_URL[cls.POINTER]}", "bubble_color": bubble_color, "text_color": text_color, "font_size": 0, "id": "AGENT" } cls.POINTER += 1 for usr_name in cls.OBJECT_INFO: if cls.OBJECT_INFO[usr_name]["id"] == "SYSTEM": cls.OBJECT_INFO[usr_name]["font_size"] = 12 elif cls.OBJECT_INFO[usr_name]["id"] in ["USER", "AGENT"]: cls.OBJECT_INFO[usr_name]["font_size"] = 16 else: assert False class StateConfig: """UI configuration for the step progress bar (indicating the current node)""" CSS = """ :root { --gradient-start: 100%; --gradient-end: 0%; } .container.progress-bar-container { position: relative; display: flex; align-items: flex-end; width: 100%; overflow-x: auto; padding-bottom: 30px; padding-top: 20px } .container.progress-bar-container::-webkit-scrollbar { width: 8px; background-color: transparent; } .container.progress-bar-container::-webkit-scrollbar-thumb { background-color: transparent; } .progress-bar-container .progressbar { counter-reset: step; white-space: nowrap; } .progress-bar-container .progressbar li { list-style: none; display: inline-block; width: 200px; position: relative; text-align: center; cursor: pointer; white-space: normal; } .progress-bar-container .progressbar li:before { content: counter(step); counter-increment: step; width: 30px; height: 30px; line-height: 30px; border: 1px solid #ddd; border-radius: 100%; display: block; text-align: center; margin: 0 auto 10px auto; background-color: #ffffff; } .progress-bar-container .progressbar li:after { content: attr(data-content); position: absolute; width: 87%; height: 2px; background-color: #dddddd; top: 15px; left: -45%; } .progress-bar-container .progressbar li:first-child:after { content: none; } .progress-bar-container .progressbar li.active { color: green; } .progress-bar-container .progressbar li.active:before { border-color: green; background-color: green; color: white; } .progress-bar-container .progressbar li.active + li:after { background: linear-gradient(to right, green var(--gradient-start), lightgray var(--gradient-end)); } .progress-bar-container .small-element { transform: scale(0.8); } .progress-bar-container .progressbar li span { position: absolute; top: 40px; left: 0; width: 100%; text-align: center; } .progress-bar-container .progressbar li .data-content { position: absolute; width: 100%; top: -10px; left: -100px; text-align: center; } """ FORMAT = """
""" STATES_NAME:List[str] = None @classmethod def _generate_template(cls, types:str)->str: # normal: A state with no execution. # active-show-up: Active state, and content displayed above the horizontal line. # active-show-down: Active state, and content displayed below the horizontal line. # active-show-both: Active state, and content displayed both above and below the horizontal line. # active-show-none: Active state, with no content displayed above the horizontal line. assert types.lower() in ["normal","active-show-up", "active-show-down", "active-show-both", "active", "active-show-none"] both_templates = """
  • {}

    {}

    {}
  • """ if types.lower() == "normal": templates = "
  • {}
  • " elif types.lower() == "active": templates = """
  • {}
  • """ elif types.lower() == "active-show-up": templates = both_templates.format("{}","{}", "{}", "", "{}") elif types.lower() == "active-show-down": templates = both_templates.format("{}","{}", "", "{}", "{}") elif types.lower() == "active-show-both": templates = both_templates elif types.lower() == "active-show-none": templates = """
  • {}
  • """ else: assert False return templates @classmethod def update_states(cls, current_states:List[int], current_templates:List[str], show_content:List[Tuple[str]])->str: assert len(current_states) == len(current_templates) # You can dynamically change the number of states. # assert len(current_states) == len(cls.STATES_NAME) css_code = [] for idx in range(len(current_states)): if idx == 0: if current_states[idx] != 0: css_code = [f"{cls._generate_template('active').format(cls.STATES_NAME[idx])}"] else: css_code = [f"{cls._generate_template('normal').format(cls.STATES_NAME[idx])}"] continue if current_states[idx-1] == 0: # new_code = f"{cls._generate_template('normal').format(*(show_content[idx]))}" new_code = f"{cls._generate_template('normal').format(cls.STATES_NAME[idx])}" else: new_code = f"{cls._generate_template(current_templates[idx]).format(current_states[idx-1], 100-current_states[idx-1],*(show_content[idx-1]), cls.STATES_NAME[idx])}" if current_states[idx-1] != 100 or (current_states[idx]==0 and current_states[idx-1]==100): new_code = new_code.replace("""li class="active" ""","""li """) css_code.append(new_code) return "\n".join(css_code) @classmethod def create_states(cls, states_name:List[str], manual_create_end_nodes:bool=False): # Create states if manual_create_end_nodes: states_name.append("Done") css_code = "" cls.STATES_NAME: List[str] = states_name for name in states_name: css_code = f"{css_code}\n{cls._generate_template('normal').format(name)}" return css_code if __name__ == '__main__': pass