# 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": """
""",
# Avatar-URL Background-color Name-color Name-Content Font-color Font-size Content
"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