|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import json
|
|
from PIL import Image
|
|
import requests
|
|
from typing import List, Tuple
|
|
|
|
class GradioConfig:
|
|
|
|
POINTER = 0
|
|
|
|
|
|
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"
|
|
|
|
|
|
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_CSS = {
|
|
|
|
"USER": """
|
|
<div style="display: flex; align-items: flex-start; justify-content: flex-end;">
|
|
<div style="background-color: {}; border-radius: 20px 0px 20px 20px; padding: 15px; min-width: 100px; max-width: 300px;">
|
|
<p style="margin: 0; padding: 0; color: {}; font-weight: bold; font-size: 18px;">{}</p>
|
|
<p style="margin: 0; padding: 0; color: {}; font-size: {}px;">{}</p>
|
|
</div>
|
|
<img src="{}" alt="USER" style="width: 50px; height: 50px; border-radius: 50%; margin-left: 10px;">
|
|
</div>
|
|
""",
|
|
|
|
|
|
"AGENT": """
|
|
<div style="display: flex; align-items: flex-start;">
|
|
<img src="{}" alt="AGENT" style="width: 50px; height: 50px; border-radius: 50%; margin-right: 10px;">
|
|
<div style="background-color: {}; border-radius: 0px 20px 20px 20px; padding: 15px; min-width: 100px; max-width: 600px;">
|
|
<p style="margin: 0; padding: 0; color: {}; font-weight: bold; font-size: 18px;">{}</p>
|
|
<p style="margin: 0; padding: 0; color: {}; font-size: {}px;">{}</p>
|
|
</div>
|
|
</div>
|
|
""",
|
|
|
|
|
|
"SYSTEM": """
|
|
<div style="display: flex; align-items: center; justify-content: center;">
|
|
<div style="background-color: {}; border-radius: 20px; padding: 1px; min-width: 200px; max-width: 1000px;">
|
|
<p style="margin: 0; padding: 0; text-align: center; font-size: {}px; font-weight: bold; font-family: '微软雅黑', sans-serif; color: {};">{}:{}</p>
|
|
</div>
|
|
</div>
|
|
"""
|
|
}
|
|
|
|
ROLE_2_NAME = {}
|
|
|
|
OBJECT_INFO = {
|
|
|
|
"User": {
|
|
|
|
"head_url": USER_HEAD_URL,
|
|
"bubble_color": "#95EC69",
|
|
"text_color": "#000000",
|
|
"font_size": 0,
|
|
"id": "USER"
|
|
},
|
|
|
|
"System": {
|
|
|
|
"head_url": "https://img.touxiangwu.com/zb_users/upload/2023/03/202303141678768524747045.jpg",
|
|
"bubble_color": "#7F7F7F",
|
|
"text_color": "#FFFFFF",
|
|
"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):
|
|
|
|
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):
|
|
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 = """
|
|
<html>
|
|
<head>
|
|
<style>
|
|
{}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<br>
|
|
<center>
|
|
<div class="container progress-bar-container">
|
|
<ul class="progressbar">
|
|
{}
|
|
</ul>
|
|
</div>
|
|
</center>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
STATES_NAME:List[str] = None
|
|
|
|
@classmethod
|
|
def _generate_template(cls, types:str)->str:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
assert types.lower() in ["normal","active-show-up", "active-show-down", "active-show-both", "active", "active-show-none"]
|
|
both_templates = """<li class="active" style="--gradient-start: {}%; --gradient-end: {}%;">
|
|
<div class="data-content">
|
|
<center>
|
|
<p style="line-height: 1px;"></p>
|
|
{}
|
|
<p>
|
|
{}
|
|
</p>
|
|
</center>
|
|
</div>
|
|
<span>{}</span>
|
|
</li>"""
|
|
|
|
if types.lower() == "normal":
|
|
templates = "<li><span>{}</span></li>"
|
|
elif types.lower() == "active":
|
|
templates = """<li class="active"><span>{}</span></li>"""
|
|
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 = """<li class="active" style="--gradient-start: {}%; --gradient-end: {}%;">
|
|
<span>{}</span>
|
|
</li>"""
|
|
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)
|
|
|
|
|
|
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(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):
|
|
|
|
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
|
|
|