File size: 15,643 Bytes
b589299
 
 
 
 
 
 
 
 
11f2441
b589299
11f2441
b589299
 
 
11f2441
 
 
 
 
 
 
2357765
b589299
632031b
b589299
 
 
632031b
b589299
 
 
 
 
 
2357765
163490b
 
 
b589299
11f2441
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b589299
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5c0e29d
b589299
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
792134a
 
 
 
 
 
 
 
ac0e573
792134a
 
 
 
 
 
 
 
86d62fd
 
 
 
11f2441
86d62fd
 
 
11f2441
86d62fd
 
 
 
632031b
 
 
 
 
 
 
 
 
b589299
632031b
 
 
 
 
 
 
b589299
 
80a016f
632031b
b589299
792134a
 
86d62fd
5c0e29d
b589299
11f2441
163490b
 
 
b589299
 
11f2441
163490b
 
 
11f2441
163490b
 
11f2441
163490b
2357765
163490b
 
 
11f2441
163490b
 
2357765
163490b
 
11f2441
163490b
 
 
2357765
 
 
163490b
b589299
 
 
 
11f2441
b589299
 
 
 
 
 
 
 
 
 
 
163490b
 
 
b589299
 
632031b
 
 
 
 
 
 
 
b589299
9ff52c9
 
2357765
 
632031b
2357765
632031b
 
 
 
 
 
9ff52c9
2357765
632031b
 
b589299
2357765
 
 
 
 
632031b
2357765
632031b
2357765
 
9ff52c9
2357765
 
 
 
 
11f2441
2357765
9ff52c9
2357765
9ff52c9
2357765
11f2441
2357765
11f2441
 
 
 
 
632031b
 
 
b589299
 
632031b
b589299
 
632031b
 
9ff52c9
 
 
b589299
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2357765
163490b
b589299
163490b
 
b589299
2357765
b589299
 
 
 
 
 
163490b
2357765
 
b589299
 
 
 
 
 
 
 
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
"""
This is the source code for the webscraper agents that use ChainLit
Features:
- Uses top N google search based on a keyword, create a JSON file, uploads it to Google Cloud Object Storage or Locally
- Continuous messaging
- Multithreading 
Written by: Antoine Ross - October 2023.
"""

import os
from typing import Dict, Optional, Union
from dotenv import load_dotenv, find_dotenv

import chainlit as cl
from chainlit.client.base import ConversationDict
from chainlit.types import AskFileResponse
from langchain.document_loaders import PyPDFLoader, TextLoader
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain.chains import ConversationalRetrievalChain

import autogen
from autogen import Agent, AssistantAgent, UserProxyAgent, config_list_from_json
from webscrapereddit import grab_articles

load_dotenv(find_dotenv())

WELCOME_MESSAGE = f"""Soap Opera Team 👾
\n\n
Let's make some soap opera! What topic do you want to search? 
"""

# Agents
USER_PROXY_NAME = "Query Agent"
PROOF_READER = "Proofreader"
WRITER = "Writer"
WRITER2 = "Writer2"
PLANNER = "Planner"
CHARACTER_DEVELOPER = "Character Developer"
DIALOGUE_SPECIALIST = "Dialogue Specialist"
ARTICLES = None

text_splitter = RecursiveCharacterTextSplitter(chunk_size=8192, chunk_overlap=100)

def load_article(file_path):
    try:
        with open(file_path, 'r') as file:
            article = file.read()
        return article
    except FileNotFoundError:
        print("File not found")
        return None

# Function to process the file
def process_file(file: AskFileResponse):
    import tempfile

    if file.type == "text/plain":
        Loader = TextLoader
    elif file.type == "application/pdf":
        Loader = PyPDFLoader

    with tempfile.NamedTemporaryFile(mode="wb", delete=False) as tempfile:
        if file.type == "text/plain":
            tempfile.write(file.content)
        elif file.type == "application/pdf":
            with open(tempfile.name, "wb") as f:
                f.write(file.content)

        loader = Loader(tempfile.name)
        documents = loader.load()
        docs = text_splitter.split_documents(documents)
        for i, doc in enumerate(docs):
            doc.metadata["source"] = f"source_{i}"
        cl.user_session.set("docs", docs)
        return docs
    
async def ask_helper(func, **kwargs):
    res = await func(**kwargs).send()
    while not res:
        res = await func(**kwargs).send()
    return res

class ChainlitAssistantAgent(AssistantAgent):
    """
    Wrapper for AutoGens Assistant Agent
    """
    def send(
        self,
        message: Union[Dict, str],
        recipient: Agent,
        request_reply: Optional[bool] = None,
        silent: Optional[bool] = False,
    ) -> bool:
        cl.run_sync(
            cl.Message(
                content=f'*Sending message to "{recipient.name}":*\n\n{message}',
                author=self.name,
            ).send()
        )
        super(ChainlitAssistantAgent, self).send(
            message=message,
            recipient=recipient,
            request_reply=request_reply,
            silent=silent,
        )
class ChainlitUserProxyAgent(UserProxyAgent):
    """
    Wrapper for AutoGens UserProxy Agent. Simplifies the UI by adding CL Actions. 
    """
    def get_human_input(self, prompt: str) -> str:
        if prompt.startswith(
            "Provide feedback to chat_manager. Press enter to skip and use auto-reply"
        ):
            res = cl.run_sync(
                ask_helper(
                    cl.AskActionMessage,
                    content="Continue or provide feedback?",
                    actions=[
                        cl.Action( name="continue", value="continue", label="✅ Continue" ),
                        cl.Action( name="feedback",value="feedback", label="💬 Provide feedback"),
                        cl.Action( name="exit",value="exit", label="🔚 Exit Conversation" )
                    ],
                )
            )
            if res.get("value") == "continue":
                return ""
            if res.get("value") == "exit":
                return "exit"

        reply = cl.run_sync(ask_helper(cl.AskUserMessage, content=prompt, timeout=120))

        return reply["content"].strip()

    def send(
        self,
        message: Union[Dict, str],
        recipient: Agent,
        request_reply: Optional[bool] = None,
        silent: Optional[bool] = False,
    ):
        cl.run_sync(
            cl.Message(
                content=f'*Sending message to "{recipient.name}"*:\n\n{message}',
                author=self.name,
            ).send()
        )
        super(ChainlitUserProxyAgent, self).send(
            message=message,
            recipient=recipient,
            request_reply=request_reply,
            silent=silent,
        )

# @cl.oauth_callback
# def oauth_callback(
#   provider_id: str,
#   token: str,
#   raw_user_data: Dict[str, str],
#   default_app_user: cl.AppUser,
# ) -> Optional[cl.AppUser]:
#   return default_app_user

# @cl.on_chat_resume
# async def on_chat_resume(conversation: ConversationDict):
#     # Access the root messages
#     root_messages = [m for m in conversation["messages"] if m["parentId"] == None]
#     # Access the user_session
#     cl.user_session.get("chat_profile")
#     # Or just pass if you do not need to run any custom logic
#     pass

config_list = autogen.config_list_from_dotenv(
    dotenv_file_path='.env',
    model_api_key_map={
        "gpt-4-1106-preview": "OPENAI_API_KEY",
    },
    filter_dict={
        "model": {
            "gpt-4-1106-preview",
        }
    }
)

@cl.action_callback("confirm_action")
async def on_action(action: cl.Action):
    if action.value == "everything":
        content = "everything"
    elif action.value == "top-headlines":
        content = "top_headlines"
    else:
        await cl.ErrorMessage(content="Invalid action").send()
        return

    prev_msg = cl.user_session.get("url_actions")  # type: cl.Message
    if prev_msg:
        await prev_msg.remove_actions()
        cl.user_session.set("url_actions", None)

    await cl.Message(content=content).send()
    
@cl.on_chat_start
async def on_chat_start():
  OPENAI_API_KEY = os.getenv('OPENAI_API_KEY')

  try:
    # app_user = cl.user_session.get("user")
    # await cl.Message(f"Hello {app_user.username}").send()
    # config_list = config_list_from_json(env_or_file="OAI_CONFIG_LIST")
    llm_config = {"config_list": config_list, "api_key": OPENAI_API_KEY, "seed": 42, "request_timeout": 180, "retry_wait_time": 60}
    proof_reader = ChainlitAssistantAgent(
        name="Proof_Reader", llm_config=llm_config,
        system_message="""Proof_Reader. Review and refine the content produced by the Writer and 
        Dialogue Specialist for grammar, style, and coherence. Provide feedback directly to them 
        and collaborate with the Planner to ensure the content aligns with the overall plan."""
    )
    writer = ChainlitAssistantAgent(
        name="Writer", llm_config=llm_config,
        system_message="""Develop the main storyline and setting for the soap opera. Collaborate 
        with the Character Developer for character integration and the Planner for aligning your 
        ideas with the overall concept. Share your drafts with the Dialogue Specialist for dialogue 
        development and the Proof_Reader for quality checks. Write down the final output with headings and subheadings."""
    )
    character_developer = ChainlitAssistantAgent(
        name="Character_Developer", llm_config=llm_config,
        system_message="""Character Developer. Design comprehensive character profiles, including backstories, 
        motivations, and relationships. Advise the Planner to integrate characters into the storyline 
        and with the Dialogue Specialist to ensure character-consistent dialogues."""
    )
    dialogue_specialist = ChainlitAssistantAgent(
        name="Dialogue_Specialist", llm_config=llm_config,
        system_message="""Dialogue Specialist. Develop dialogues that reflect each character's personality 
        and the overall tone of the soap opera. Collaborate with the Character Developer for character insights 
        and with the Planner to fit dialogues into the narrative structure."""
    )
    planner = ChainlitAssistantAgent(
        name="Planner", llm_config=llm_config,
        system_message="""Planner. Guide the overall direction of the soap opera. Generate initial 
        ideas and oversee the development process. Regularly consult with the Writer, Character Developer, 
        and Dialogue Specialist to ensure consistency and alignment with the chosen theme. Provide feedback 
        and adjustments as needed. First make 20 ideas, then narrow it down to 10 ideas, then narrow it down to 5, 
        then to 3, then to 1. Narrow it down based on "What will be the most dramatic, emotional and entertaining idea". 
        Everytime you narrow the idea down ask the User_Proxy to agree with the idea. Finally, when the last idea is finalized, 
        discuss with the Writer to create a general idea of how the Soap Opera should look like."""
    )
    user_proxy = ChainlitUserProxyAgent(
        name="User_Proxy",
        human_input_mode="ALWAYS",
        llm_config=llm_config,
        # max_consecutive_auto_reply=3,
        # is_termination_msg=lambda x: x.get("content", "").rstrip().endswith("TERMINATE"),
        code_execution_config=False,
        system_message="""Manager. Administrate the agents on a plan. Communicate with the proofreader to proofread the output. 
        Communicate with the writer to analyse the content and ask for it to give a summary. 
                Reply CONTINUE, or the reason why the task is not solved yet."""
    )
    
    cl.user_session.set(USER_PROXY_NAME, user_proxy)
    cl.user_session.set(PROOF_READER, proof_reader)
    cl.user_session.set(WRITER, writer)
    cl.user_session.set(PLANNER, planner)
    cl.user_session.set(DIALOGUE_SPECIALIST, dialogue_specialist)
    cl.user_session.set(CHARACTER_DEVELOPER, character_developer)
    
    # WEB-SCRAPING LOGIC
    URL = None
    URL_option = None
    BASE_URL_EVERYTHING = 'https://newsapi.org/v2/everything?'
    BASE_URL_TOP_HEADLINES = 'https://newsapi.org/v2/top-headlines?'
    SORTBY = 'relevancy' # relevancy, popularity, publishedAt
    COUNTRY = 'gb' # Country you want to use
    CATEGORY = None # 'business' # Options: business, entertainment, general health, science, sports, technology
    API_KEY = os.getenv('NEWS_API_KEY')
    
    doc = cl.Action( name="doc", value="doc", label="Document" )
    no_doc = cl.Action( name="no_doc", value="no_doc", label="NoDocument" )
    idea = cl.Action( name="Idea", value="Idea", label="Idea" )
    no_idea = cl.Action( name="NoIdea", value="NoIdea", label="NoIdea" )
    everything = cl.Action( name="everything", value="everything", label="Everything" )
    top_headlines = cl.Action( name="t3op-headlines",value="top-headlines", label="TopHeadlines")
    business = cl.Action( name="business", value="business", label="Business" )
    entertainment = cl.Action( name="entertainment", value="entertainment", label="Entertainment" )
    science = cl.Action( name="science", value="science", label="Science" )
    sports = cl.Action( name="sports", value="sports", label="Sports" )
    technology = cl.Action( name="technology", value="technology", label="Technology" ) 
     
    doc_actions = [doc, no_doc]
    idea_actions = [idea, no_idea]
    url_actions = [everything, top_headlines]
    category_actions = [business, entertainment, science, sports, technology]
    
    # Hi, let’s generate some storyline ideas, if you have any ideas type them if not press enter
    
    IDEA_option = cl.AskActionMessage(
        content="Hi, let’s generate some storyline ideas. Would you like to generate ideas from Reddit, or continue?",
        actions=idea_actions,
    )
    await IDEA_option.send()
    
    IDEA_option = IDEA_option.content.split()[-1]
    if IDEA_option == "Idea":
        print("Using document...")
        TOPIC = None
        while TOPIC is None:
            TOPIC = await cl.AskUserMessage(content="What topic would you like to make a Soap Opera about? [Only send one keyword.]", timeout=180).send()

        print("Topic: ", TOPIC['content'])
        msg = cl.Message(
        content=f"Processing data from Reddit...", disable_human_feedback=True
        )
        await msg.send()
        
        articles = grab_articles(TOPIC['content'])
        msg = cl.Message(
            content=f"Content from Reddit loaded: \n{articles}", disable_human_feedback=True
        )
        await msg.send()
    else:
        article_path = "articles.txt"
        articles = load_article(article_path)
    print("Articles grabbed.")
    
    msg = cl.Message(content=f"Processing `{articles}`...", disable_human_feedback=True, author="User_Proxy")
    await msg.send()
    
    cl.user_session.set(ARTICLES, articles)
    print("Articles set...")
    
    msg = cl.Message(content=f"""This is the Soap Opera Team, please give instructions on how the Soap Opera should be structured and made. 
                     \nSample input: 
                     "Create a captivating soap opera scene inspired by the content of the articles. Feature characters who are entangled in a 
                     complex web of emotions, ambitions, and conflicts. Craft dialogue and actions that convey the essence of the articles, infusing drama, 
                     suspense, and emotion. Leave the audience eagerly anticipating the next twist in this gripping narrative."
                     """, 
                     disable_human_feedback=True, 
                     author="User_Proxy")
    await msg.send()
    
  except Exception as e:
    print("Error: ", e)
    pass

@cl.on_message
async def run_conversation(message: cl.Message):
  #try:
    TASK = message.content
    print("Task: ", TASK)
    proof_reader = cl.user_session.get(PROOF_READER)
    user_proxy = cl.user_session.get(USER_PROXY_NAME)
    writer = cl.user_session.get(WRITER)
    writer2 = cl.user_session.get(WRITER2)
    planner = cl.user_session.get(PLANNER)
    articles = cl.user_session.get(ARTICLES)
    character_developer = cl.user_session.get(CHARACTER_DEVELOPER)
    dialogue_specialist = cl.user_session.get(DIALOGUE_SPECIALIST)

    groupchat = autogen.GroupChat(agents=[user_proxy, proof_reader, writer, character_developer, dialogue_specialist, planner], messages=[], max_round=50)
    manager = autogen.GroupChatManager(groupchat=groupchat)
    
    print("Initiated GC messages... \nGC messages length: ", len(groupchat.messages))

    if len(groupchat.messages) == 0:
      message = f"""Access the output from {articles}. Create a Soap Opera based on the article details inside.
                Ensure to always check the length of the context to avoid hitting the context limit. First create 10 ideas, then 5, then 3, then 1.
                Finalize the ideas with the planner and make sure to follow the criteria of choosing based on: "What will be the most dramatic, 
                emotional and entertaining idea" Do not express gratitude in responses.\n Instructions for creating Soap Opera:""" + TASK
      await cl.Message(content=f"""Starting agents on task of creating a Soap Opera...""").send()
      await cl.make_async(user_proxy.initiate_chat)( manager, message=message, )
    else:
      await cl.make_async(user_proxy.send)( manager, message=TASK, )
      
#   except Exception as e:
#     print("Error: ", e)
#     pass