File size: 10,189 Bytes
5bd42f4
 
ddde4ce
5bd42f4
 
 
ddde4ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5bd42f4
 
 
 
ddde4ce
 
5bd42f4
f19f8cd
ddde4ce
 
f19f8cd
ddde4ce
 
604aa2f
5bd42f4
 
ddde4ce
 
 
 
5bd42f4
 
 
ddde4ce
 
 
 
 
 
5bd42f4
ddde4ce
 
 
61ad22d
ddde4ce
 
 
 
 
 
5bd42f4
9833f9b
ddde4ce
 
5bd42f4
ddde4ce
 
9833f9b
eece1bb
9833f9b
eece1bb
 
9833f9b
eece1bb
 
9833f9b
 
eece1bb
 
 
ddde4ce
5bd42f4
ddde4ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19e4b4c
 
 
 
ddde4ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import streamlit as st
from openai import OpenAI
from script_prompts import SYSTEM_MESSAGE_TEXT, SYSTEM_MESSAGE_TH_VO, USER_MESSAGE_TEXT_VO
from prompts import SYSTEM_MESSAGE, USER_MESSAGE
import json
import os
import time
import requests
from bs4 import BeautifulSoup

PERSPECTIVE_INSTRUCTIONS = {
    "auto": 'The script should match the perspective (first person, third person, etc) of the reference material.',
    "first": 'The script should be written in first person perspective.',
    "third": 'The script should be written in third person perspective.',
}

DEFAULT_TONE_INSTRUCTION = 'the same as the tone of the reference material'
DEFAULT_TONE = 'balanced'

def fetch_blog_content(url):
    try:
        res = requests.get(url, timeout=10)
        res.raise_for_status()  # Raise an error for bad status codes
        soup = BeautifulSoup(res.text, "html.parser")
        # A simple extraction: get all text within <p> tags
        paragraphs = soup.find_all('p')
        content = "\n\n".join([p.get_text() for p in paragraphs])
        return content.strip()
    except Exception as e:
        st.error(f"Error fetching blog content: {e}")
        return ""

# Set Streamlit layout to wide mode
st.set_page_config(layout="wide")

st.title("🎬 AI-Powered Content Planner & Script Writer -  Public Blueprints")
st.markdown("Paste a source content for generating a Public Blueprints Content Plan and Scripts.")

# Placeholder at the very top for response time
plan_time_placeholder = st.empty()
script_time_placeholder = st.empty()

# Models to try
OPENAI_MODELS = ["gpt-4o", "gpt-4o-mini", "o3-mini"]
GROQ_MODELS = ["llama-3.3-70b-specdec", "llama-3.3-70b-versatile", "llama-3.1-8b-instant", "deepseek-r1-distill-llama-70b"]

# Sidebar: Model Selection
st.sidebar.subheader("📤 Model for Plan Generation")
blueprint_plan_model = st.sidebar.selectbox(
    "Choose model for blueprints content plan:",
    GROQ_MODELS + OPENAI_MODELS,
    index=0  # Default selection
)

st.sidebar.subheader("📥 Model for Script Writing")
script_writing_model = st.sidebar.selectbox(
    "Choose model for script writing:",
    GROQ_MODELS + OPENAI_MODELS,
    index=0  # Default selection
)

# Assign the correct URL based on the selected model
if blueprint_plan_model in GROQ_MODELS:
    plan_client = OpenAI(base_url="https://api.groq.com/openai/v1", api_key=os.environ.get("GROQ_API_KEY"))
else:
    plan_client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))

if script_writing_model in GROQ_MODELS:
    script_client = OpenAI(base_url="https://api.groq.com/openai/v1", api_key=os.environ.get("GROQ_API_KEY"))
else:
    script_client = OpenAI(api_key=os.environ.get("OPENAI_API_KEY"))


# Layout: Two columns - left for reference document, right for plans and scripts
col_doctext, col_output = st.columns([1, 1])

# Left Column: Input Source
with col_doctext:
    st.subheader("📝 Input Source")
    blog_url = st.text_input("Enter a blog URL (optional):", key="blog_url")

    # Initialize fetched_content as empty string.
    fetched_content = ""
    if blog_url:
        fetched_content = fetch_blog_content(blog_url)
        if fetched_content:
            st.success("Blog content fetched successfully!")
        else:
            st.warning("Unable to fetch blog content. Please paste your content manually.")

    # The text area uses fetched_content as its default value.
    doctext = st.text_area("Or paste your content here:", value=fetched_content, height=400, key="doctext")

# Right Column: Plan Generation and Script Writing
with col_output:
    st.subheader("📋 Generated Content Plans")
    time_placeholder = st.empty()

    # Button to generate clip plans from the transcript
    if st.button("Generate Plan"):
        if not doctext.strip():
            st.error("❌ Please enter an input source content.")
        else:
            with st.spinner("⏳ Generating content plan... Please wait."):
                try:
                    # Prepare prompts for clip plan generation
                    system_prompt = SYSTEM_MESSAGE
                    user_prompt = USER_MESSAGE.format(source_content=doctext)
                    messages = [
                        {"role": "system", "content": system_prompt},
                        {"role": "user", "content": user_prompt},
                    ]

                    openai_args = {
                        "model": blueprint_plan_model,
                        "messages": messages,
                        "response_format": {"type": "json_object"},
                    }
                    if blueprint_plan_model == "o3-mini":
                        openai_args["reasoning_effort"] = "low"
                    else:
                        openai_args["max_tokens"] = 5000
                        openai_args["temperature"] = 0.45

                    start_time = time.time()
                    response = plan_client.chat.completions.create(**openai_args)
                    end_time = time.time()
                    elapsed_time = end_time - start_time
                    plan_time_placeholder.markdown(f"⏱️ **Response Time for Content Planning:** {elapsed_time:.2f} seconds")

                    generated_response = response.choices[0].message.content.strip()
                    content_plan = json.loads(generated_response)

                    # Assume the response JSON has a single key containing a list of clip plans
                    plan_key = list(content_plan.keys())[0]
                    blueprint_plans = content_plan.get(plan_key, [])

                    # Save clip plans in session state so they persist
                    st.session_state.blueprint_plans = blueprint_plans

                    # Clear any previous extraction outputs
                    for i in range(len(blueprint_plans)):
                        st.session_state.pop(f"extracted_blueprint_{i}", None)
                except json.JSONDecodeError:
                    st.error("⚠️ Failed to parse OpenAI response. Try again.")
                except Exception as e:
                    st.error(f"❌ Error: {str(e)}")

    # Display clip plans if they exist in session state
    if "blueprint_plans" in st.session_state:
        # We'll work with a reference to the clip plans list
        updated_blueprint_plans = st.session_state.blueprint_plans

        for i, blueprint in enumerate(updated_blueprint_plans):
            # Each blueprint is rendered in an expander with editable fields
            with st.expander(f"🎬 Blueprint Plan {i + 1}", expanded=True):
                st.markdown(f"**Blueprint:** {blueprint.get('Blueprint', 'N/A')}")
                st.markdown(f"**Content Focus:** {blueprint.get('Content Focus', 'N/A')}")
                st.markdown(f"**Narrative Thread:** {blueprint.get('Narrative Thread', 'N/A')}")
                st.markdown(f"**Tag:** {blueprint.get('Tag', 'N/A')}")

                # Button for transcript extraction for this clip
                if st.button("Generate Script", key=f"extract_{i}"):
                    with st.spinner("⏳ Generating script... Please wait."):
                        try:
                            # Send only the specific (and possibly edited) content focus to the script writer
                            content_focus_script = updated_blueprint_plans[i].get("Content Focus", "N/A")
                            script_user_prompt = USER_MESSAGE_TEXT_VO.format(
                                doctext=doctext,
                                content_focus=content_focus_script
                            )

                            if updated_blueprint_plans[i].get("Narrative Thread") == 'text':
                                script_system_prompt = SYSTEM_MESSAGE_TEXT.format(
                                    perspective_instruction=PERSPECTIVE_INSTRUCTIONS.get("auto"),
                                    tone_instruction=DEFAULT_TONE_INSTRUCTION,
                                )
                            else:
                                script_system_prompt = SYSTEM_MESSAGE_TH_VO.format(
                                    perspective_instruction=PERSPECTIVE_INSTRUCTIONS.get("auto"),
                                    tone_instruction=DEFAULT_TONE_INSTRUCTION,
                                )

                            clipper_messages = [
                                {"role": "system", "content": script_system_prompt},
                                {"role": "user", "content": script_user_prompt},
                            ]

                            extraction_args = {
                                "model": script_writing_model,
                                "messages": clipper_messages,
                            }
                            if script_writing_model == "o3-mini":
                                extraction_args["reasoning_effort"] = "low"
                            else:
                                extraction_args["max_tokens"] = 5000
                                extraction_args["temperature"] = 0.45

                            clipper_response = script_client.chat.completions.create(**extraction_args)
                            start_time = time.time()
                            extraction_response = clipper_response.choices[0].message.content.strip()
                            end_time = time.time()
                            elapsed_time = end_time - start_time
                            script_time_placeholder.markdown(f"⏱️ **Response Time for Script Writing:** {elapsed_time:.2f} seconds")
                            extracted_clip = extraction_response

                            # Save the extraction result for this clip in session state
                            st.session_state[f"extracted_clip_{i}"] = extracted_clip
                        except Exception as e:
                            st.error(f"❌ Extraction error: {str(e)}")

                # Display extraction output if available
                if f"extracted_clip_{i}" in st.session_state:
                    st.markdown("#### 📝Script: ")
                    st.write(st.session_state[f"extracted_clip_{i}"])