File size: 22,890 Bytes
2bec240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c40284
2bec240
3c40284
 
 
 
 
 
 
 
 
 
 
 
 
 
2bec240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c40284
2bec240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c40284
2bec240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c40284
2bec240
 
 
 
3c40284
2bec240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c40284
2bec240
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3c40284
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
# 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.

# Emoji comes from this website:
# https://emojipedia.org/
import subprocess
from gradio_config import GradioConfig as gc
import gradio as gr
from typing import List, Tuple, Any
import time
import socket
import psutil
import os
from abc import abstractmethod
import openai

def test_apikey_connection(api_key=None, model="gpt-3.5-turbo"):
    openai.api_key = api_key if api_key is not None else os.environ["API_KEY"]
    if "PROXY" in os.environ:
        openai.proxy = os.environ["PROXY"]
    messages = [{"role": "user", "content": "what's your name?"}]
    try:
        response = openai.ChatCompletion.create(
            model=model,
            messages=messages,
        )
        return True
    except:
        return False
    
def convert2list4agentname(sop):
    """

    Extract the agent names of all states

    return:

        only name: [name1, name2, ...]

        agent_name: [name1(role1), name2(role2), ...]

    """
    only_name = []  
    agent_name = [] 
    roles_to_names = sop.roles_to_names
    for state_name,roles_names in roles_to_names.items():
        for role,name in roles_names.items():
            agent_name.append(f"{name}({role})")
            only_name.append(name)
    agent_name = list(set(agent_name))
    agent_name.sort()
    return agent_name, only_name

def is_port_in_use(port):
    """Check if the port is available"""
    for conn in psutil.net_connections():
        if conn.laddr.port == port:
            return True
    return False

def check_port(port):
    """Determine available ports"""
    if os.path.isfile("PORT.txt"):
        port = int(open("PORT.txt","r",encoding='utf-8').readlines()[0])
    else:
        for i in range(10):
            if is_port_in_use(port+i) == False:
                port += i
                break
        with open("PORT.txt", "w") as f:
            f.writelines(str(port))
    return port

# Determine some heads
SPECIAL_SIGN = {
    "START": "<START>",
    "SPLIT": "<SELFDEFINESEP>",
    "END": "<ENDSEP>"
}
HOST = "127.0.0.1"
# The starting port number for the search.
PORT = 15000                
PORT = check_port(PORT)
    
def print_log(message:str):
    print(f"[{time.ctime()}]{message}")

global_dialog = {
    "user": [],
    "agent": {},
    "system": []
}

class UIHelper:
    """Static Class"""

    @classmethod
    def wrap_css(cls, content, name) -> str:
        """

        Description:

            Wrap CSS around each output, and return it in HTML format for rendering with Markdown.

        Input:

            content: Output content 

            name: Whose output is it

        Output:

            HTML

        """
        assert name in gc.OBJECT_INFO, \
            f"The current name `{name}` is not registered with an image. The names of the currently registered agents are in `{gc.OBJECT_INFO.keys()}`. Please use `GradioConfig.add_agent()` from `Gradio_Config/gradio_config.py` to bind the name of the new agent."
        output = ""
        info = gc.OBJECT_INFO[name]
        if info["id"] == "USER":
            output = gc.BUBBLE_CSS["USER"].format(
                info["bubble_color"],                   # Background-color
                info["text_color"],                     # Color of the agent's name 
                name,                                   # Agent name
                info["text_color"],                     # Font color
                info["font_size"],                      # Font size
                content,                                # Content
                info["head_url"]                        # URL of the avatar
            )
        elif info["id"] == "SYSTEM":
            output = gc.BUBBLE_CSS["SYSTEM"].format(
                info["bubble_color"],                   # Background-color
                info["font_size"],                      # Font size
                info["text_color"],                     # Font color
                name,                                   # Agent name
                content                                 # Content
            )
        elif info["id"] == "AGENT":
            output = gc.BUBBLE_CSS["AGENT"].format(
                info["head_url"],                       # URL of the avatar
                info["bubble_color"],                   # Background-color
                info["text_color"],                     # Font color
                name,                                   # Agent name
                info["text_color"],                     # Font color
                info["font_size"],                      # Font size
                content,                                # Content
            )
        else:
            assert False, f"Id `{info['id']}` is invalid. The valid id is in ['SYSTEM', 'AGENT', 'USER']"
        return output

    @classmethod
    def novel_filter(cls, content, agent_name):
        
        """比如<CONTENT>...</CONTENT>οΌŒε°±εΊ”θ―₯θΎ“ε‡ΊCONTENT:..."""
        IS_RECORDER = agent_name.lower() in ["recorder", "summary"]
        if IS_RECORDER:
            BOLD_FORMAT = """<div style="color: #000000; display:inline">

    <b>{}</b>

</div>

<span style="color: black;">

"""
        else:
            BOLD_FORMAT = "<b>{}</b>"
        CENTER_FORMAT = """<div style="background-color: #F0F0F0; text-align: center; padding: 5px; color: #000000">

    <b>{}</b>

</div>

"""
        START_FORMAT = "<{}>"
        END_FORMAT = "</{}>"
        mapping = {
            "TARGET": "🎯 Current Target: ",
            "NUMBER": "πŸ– Required Number: ",
            "THOUGHT": "πŸ€” Overall Thought: ",
            "FIRST NAME": "βšͺ First Name: ",
            "LAST NAME": "βšͺ Last Name: ",
            "ROLE": "🀠 Character Properties: ",
            "RATIONALES": "πŸ€” Design Rationale: ",
            "BACKGROUND": "🚊 Character Background: ",
            "ID": "πŸ”΄ ID: ",
            "TITLE": "🧩 Chapter Title: ",
            "ABSTRACT": "🎬 Abstract: ",
            "CHARACTER INVOLVED": "β˜ƒοΈ Character Involved: ",
            "ADVICE": "πŸ’¬ Advice:",
            "NAME": "πŸ“› Name: ",
            "GENDER": "πŸ‘©β€πŸ‘©β€πŸ‘¦β€πŸ‘¦ Gender: ",
            "AGE": "⏲️ Age: ",
            "WORK": "πŸ‘¨β€πŸ”§ Work: ",
            "PERSONALITY": "🧲 Character Personality: ",
            "SPEECH STYLE": "πŸ—£οΈ Speaking Style: ",
            "RELATION": "🏠 Relation with Others: ",
            "WORD COUNT": "🎰 Word Count: ",
            "CHARACTER DESIGN": "πŸ“ˆ Character Design: ",
            "CHARACTER REQUIRE": "πŸ“ˆ Character Require: ",
            "CHARACTER NAME": "πŸ“ˆ Character Naming Analysis: ",
            "CHARACTER NOW": "πŸ“ˆ Character Now: ",
            "OUTLINE DESIGN": "πŸ“ˆ Outline Design: ",
            "OUTLINE REQUIRE": "πŸ“ˆ Outline Require: ",
            "OUTLINE NOW": "πŸ“ˆ Outline Now: ",
            "SUB TASK": "🎯 Current Sub Task: ",
            "CHARACTER ADVICE": "πŸ’¬ Character Design Advice: ",
            "OUTLINE ADVANTAGE": "πŸ“ˆ Outline Advantage: ",
            "OUTLINE DISADVANTAGE": "πŸ“ˆ Outline Disadvantage: ",
            "OUTLINE ADVICE": "πŸ’¬ Outline Advice: ",
            "NEXT": "➑️ Next Advice: ",
            "TOTAL NUMBER": "πŸ”’ Total Number: "
        }
        for i in range(1, 10):
            mapping[f"CHARACTER {i}"] = f"πŸ¦„ Character {i}"
            mapping[f"SECTION {i}"] = f"🏷️ Chapter {i}"
        for key in mapping:
            if key in [f"CHARACTER {i}" for i in range(1, 10)] \
                    or key in [f"SECTION {i}" for i in range(1, 10)] \
                    :
                content = content.replace(
                    START_FORMAT.format(key), CENTER_FORMAT.format(mapping[key])
                )
            elif key in ["TOTAL NUMBER"]:
                content = content.replace(
                    START_FORMAT.format(key), CENTER_FORMAT.format(mapping[key]) + """<span style="color: black;">"""
                )
                content = content.replace(
                    END_FORMAT.format(key), "</span>"
                )
            else:
                content = content.replace(
                    START_FORMAT.format(key), BOLD_FORMAT.format(mapping[key])
                )

            content = content.replace(
                END_FORMAT.format(key), "</span>" if IS_RECORDER else ""
            )
        return content
    
    @classmethod
    def singleagent_filter(cls, content, agent_name):
        return content
    
    @classmethod
    def debate_filter(cls, content, agent_name):
        return content
    
    @classmethod
    def code_filter(cls, content, agent_name):
        # return content.replace("```python", "<pre><code>").replace("```","</pre></code>")
        return content
    
    @classmethod
    def general_filter(cls, content, agent_name):
        return content
    
    @classmethod
    def filter(cls, content: str, agent_name: str, ui_name: str):
        """

        Description:

            Make certain modifications to the output content to enhance its aesthetics when content is showed in gradio.

        Input:

            content: output content

            agent_name: Whose output is it

            ui_name: What UI is currently launching

        Output:

            Modified content

        """
        mapping = {
            "SingleAgentUI": cls.singleagent_filter,
            "DebateUI": cls.debate_filter,
            "NovelUI": cls.novel_filter,
            "CodeUI": cls.code_filter,
            "GeneralUI": cls.general_filter
        }
        if ui_name in mapping:
            return mapping[ui_name](content, agent_name)
        else:
            return content

class Client:
    """

    For inter-process communication, this is the client. 

    `gradio_backend.PY` serves as the backend, while `run_gradio` is the frontend. 

    Communication between the frontend and backend is accomplished using Sockets.

    """
    # =======================Radio Const String======================
    SINGLE_MODE = "Single Mode"
    AUTO_MODE = "Auto Mode"
    MODE_LABEL = "Select the execution mode"
    MODE_INFO = "Single mode refers to when the current agent output ends, it will stop running until you click to continue. Auto mode refers to when you complete the input, all agents will continue to output until the task ends."
    # ===============================================================
    mode = AUTO_MODE
    FIRST_RUN:bool = True
    # if last agent is user, then next agent will be executed automatically rather than click button 
    LAST_USER:bool = False
    
    receive_server = None
    send_server = None
    current_node = None
    cache = {}

    def __init__(self, host=HOST, port=PORT, bufsize=1024):
        assert Client.mode in [Client.SINGLE_MODE, Client.AUTO_MODE]
        self.SIGN = SPECIAL_SIGN
        self.bufsize = bufsize
        assert bufsize > 0
        self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.client_socket.connect((host, port))
        while True:
            data = self.client_socket.recv(self.bufsize).decode('utf-8')
            if data == "hi":
                self.client_socket.send("hello agent".encode('utf-8'))
                time.sleep(1)
            elif data == "check":
                break
        print_log("Client: connecting successfully......")

    def start_server(self):
        while True:
            message = yield
            if message == 'exit':
                break
            self.send_message(message=message)

    def send_message(self, message):
        """Send the message to the server."""
        if isinstance(message, list) or isinstance(message, dict):
            message = str(message)
        assert isinstance(message, str)
        message = message + self.SIGN["SPLIT"]
        self.client_socket.send(message.encode('utf-8'))

    def receive_message(self, end_identifier: str = None, split_identifier: str = SPECIAL_SIGN["SPLIT"]) -> List:
        """Receive messages from the server, and it will block the process. Supports receiving long text."""
        remaining = ""
        while True:
            # receive message
            dataset = self.client_socket.recv(self.bufsize)
            try:
                # If decoding fails, it indicates that the current transmission is a long text.
                dataset = dataset.decode('utf-8')
            except UnicodeDecodeError:
                if not isinstance(remaining, bytes):
                    remaining = remaining.encode('utf-8')
                assert isinstance(dataset, bytes)
                remaining += dataset
                try:
                    dataset = remaining.decode('utf-8')
                    remaining = ""
                except UnicodeDecodeError:
                    continue
            assert isinstance(remaining, str)
            dataset = remaining + dataset
            list_dataset = dataset.split(split_identifier)
            if len(list_dataset) == 1:
                # If there is only one result from the split, it indicates that the current sequence itself has not yet ended.
                remaining = list_dataset[0]
                continue
            else:
                remaining = list_dataset[-1]
            # Receive successfully
            list_dataset = list_dataset[:-1]
            return_value = []
            for item in list_dataset:
                if end_identifier is not None and item == end_identifier:
                    break
                return_value.append(item)
            identifier = yield return_value
            if identifier is not None:
                end_identifier, split_identifier = identifier

    def listening_for_start_(self):
        """

        When the server starts, the client is automatically launched. 

        At this point, process synchronization is required, 

        such as sending client data to the server for rendering, 

        then the server sending the modified data back to the client, 

        and simultaneously sending a startup command. 

        Once the client receives the data, it will start running.

        """
        Client.receive_server = self.receive_message()
        # Waiting for information from the server.
        data: list = next(Client.receive_server)
        assert len(data) == 1
        data = eval(data[0])
        assert isinstance(data, dict)
        Client.cache.update(data)
        # Waiting for start command from the server.
        data:list = Client.receive_server.send(None)
        assert len(data) == 1
        assert data[0] == "<START>"

class WebUI:
    """

    The base class for the frontend, which encapsulates some functions for process information synchronization. 

    When a new frontend needs to be created, you should inherit from this class, 

    then implement the `construct_ui()` method and set up event listeners. 

    Finally, execute `run()` to load it.

    """
    
    def receive_message(

        self,

        end_identifier:str=None,

        split_identifier:str=SPECIAL_SIGN["SPLIT"]

    )->List:
        """This is the same as in Client class."""
        yield "hello"
        remaining = ""
        while True:
            dataset = self.client_socket.recv(self.bufsize)
            try:
                dataset = dataset.decode('utf-8')
            except UnicodeDecodeError:
                if not isinstance(remaining, bytes):
                    remaining = remaining.encode('utf-8')
                assert isinstance(dataset, bytes)
                remaining += dataset
                try:
                    dataset = remaining.decode('utf-8')
                    remaining = ""
                except UnicodeDecodeError:
                    continue
            assert isinstance(remaining, str)
            dataset = remaining + dataset
            list_dataset = dataset.split(split_identifier)
            if len(list_dataset) == 1:
                remaining = list_dataset[0]
                continue
            else:
                remaining = list_dataset[-1]
            list_dataset = list_dataset[:-1]
            return_value = []
            for item in list_dataset:
                if end_identifier is not None and item == end_identifier:
                    break
                return_value.append(item)
            identifier = yield return_value
            if identifier is not None:
                end_identifier, split_identifier = identifier

    def send_message(self, message:str):
        """Send message to client."""
        SEP = self.SIGN["SPLIT"]
        self.client_socket.send(
            (message+SEP).encode("utf-8")
        )
    
    def _connect(self):
        # check 
        if self.server_socket:
            self.server_socket.close()
            assert not os.path.isfile("PORT.txt")
            self.socket_port = check_port(PORT)
        # Step1. initialize
        self.server_socket = socket.socket(
            socket.AF_INET, socket.SOCK_STREAM
        )
        # Step2. binding ip and port
        self.server_socket.bind((self.socket_host, self.socket_port))
        # Step3. run client
        self._start_client()

        # Step4. listening for connect
        self.server_socket.listen(1)

        # Step5. test connection
        client_socket, client_address = self.server_socket.accept()
        print_log("server: establishing connection......")
        self.client_socket = client_socket
        while True:
            client_socket.send("hi".encode('utf-8'))
            time.sleep(1)
            data = client_socket.recv(self.bufsize).decode('utf-8')
            if data == "hello agent":
                client_socket.send("check".encode('utf-8'))
                print_log("server: connect successfully")
                break
        assert os.path.isfile("PORT.txt")
        os.remove("PORT.txt")
        if self.receive_server:
            del self.receive_server
        self.receive_server = self.receive_message()
        assert next(self.receive_server) == "hello"
    
    @abstractmethod
    def render_and_register_ui(self):
        # You need to implement this function. 
        # The function's purpose is to bind the name of the agent with an image. 
        # The name of the agent is stored in `self.cache[]`, 
        # and the function for binding is in the method `add_agents` of the class `GradioConfig` in `Gradio_Config/gradio_config.py``.
        # This function will be executed in `self.first_recieve_from_client()`
        pass
    
    def first_recieve_from_client(self, reset_mode:bool=False):
        """

        This function is used to receive information from the client and is typically executed during the initialization of the class.

        If `reset_mode` is False, it will bind the name of the agent with an image.

        """
        self.FIRST_RECIEVE_FROM_CLIENT = True
        data_list:List = self.receive_server.send(None)
        assert len(data_list) == 1
        data = eval(data_list[0])
        assert isinstance(data, dict)
        self.cache.update(data)
        if not reset_mode:
            self.render_and_register_ui()
    
    def _second_send(self, message:dict):
        # Send the modified message.
        # It will be executed in `self.send_start_cmd()` automatically.
        self.send_message(str(message))
    
    def _third_send(self):
        # Send start command.
        # It will be executed in `self.send_start_cmd()` automatically.
        self.send_message(self.SIGN['START'])
    
    def send_start_cmd(self, message:dict={"hello":"hello"}):
        # If you have no message to send, you can ignore the args `message`.
        assert self.FIRST_RECIEVE_FROM_CLIENT, "Please make sure you have executed `self.first_recieve_from_client()` manually."
        self._second_send(message=message)
        time.sleep(1)
        self._third_send()
        self.FIRST_RECIEVE_FROM_CLIENT = False
    
    def __init__(

        self,

        client_cmd: list,           # ['python','test.py','--a','b','--c','d']

        socket_host: str = HOST,

        socket_port: int = PORT,

        bufsize: int = 1024,

        ui_name: str = ""

    ):
        self.ui_name = ui_name
        self.server_socket = None
        self.SIGN = SPECIAL_SIGN
        self.socket_host = socket_host
        self.socket_port = socket_port
        self.bufsize = bufsize
        self.client_cmd = client_cmd
        
        self.receive_server = None
        self.cache = {}
        assert self.bufsize > 0
        self._connect()

    def _start_client(self):
        print(f"server: executing `{' '.join(self.client_cmd)}` ...")
        self.backend = subprocess.Popen(self.client_cmd)
        
    def _close_client(self):
        print(f"server: killing `{' '.join(self.client_cmd)}` ...")
        self.backend.terminate()
    
    def reset(self):
        print("server: restarting ...")
        self._close_client()
        time.sleep(1)
        self._connect()

    def render_bubble(self, rendered_data, agent_response, node_name, render_node_name:bool=True):
        # Rendered bubbles (HTML format) are used for gradio output.
        output = f"**{node_name}**<br>" if render_node_name else ""
        for item in agent_response:
            for agent_name in item:
                content = item[agent_name].replace("\n", "<br>")
                content = UIHelper.filter(content, agent_name, self.ui_name)
                output = f"{output}<br>{UIHelper.wrap_css(content, agent_name)}"
        rendered_data[-1] = [rendered_data[-1][0], output]
        return rendered_data

    def run(self,share: bool = True):
        self.demo.queue()
        self.demo.launch()


if __name__ == '__main__':
    pass