T-K-O-H commited on
Commit
ef98f85
·
0 Parent(s):

Initial commit: YouTube to LinkedIn Post Converter

Browse files
Files changed (4) hide show
  1. .gitignore +68 -0
  2. README.md +42 -0
  3. app.py +1249 -0
  4. requirements.txt +8 -0
.gitignore ADDED
@@ -0,0 +1,68 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Python virtual environment
2
+ venv/
3
+ env/
4
+ ENV/
5
+
6
+ # Environment variables
7
+ .env
8
+ .env.*
9
+
10
+ # Python cache files
11
+ __pycache__/
12
+ *.py[cod]
13
+ *$py.class
14
+ *.so
15
+ .Python
16
+
17
+ # Build and distribution
18
+ build/
19
+ develop-eggs/
20
+ dist/
21
+ downloads/
22
+ eggs/
23
+ .eggs/
24
+ lib/
25
+ lib64/
26
+ parts/
27
+ sdist/
28
+ var/
29
+ wheels/
30
+ *.egg-info/
31
+ .installed.cfg
32
+ *.egg
33
+
34
+ # Chroma database
35
+ chroma_db/
36
+
37
+ # Gradio cache
38
+ .gradio/
39
+
40
+ # IDE specific files
41
+ .idea/
42
+ .vscode/
43
+ *.swp
44
+ *.swo
45
+ .DS_Store
46
+
47
+ # Logs
48
+ *.log
49
+ logs/
50
+ log/
51
+
52
+ # Local development
53
+ *.local
54
+ local_settings.py
55
+
56
+ # Coverage reports
57
+ htmlcov/
58
+ .tox/
59
+ .coverage
60
+ .coverage.*
61
+ .cache
62
+ nosetests.xml
63
+ coverage.xml
64
+ *.cover
65
+ .hypothesis/
66
+
67
+ # Jupyter Notebook
68
+ .ipynb_checkpoints
README.md ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: YouTube to LinkedIn Post Converter
3
+ emoji: 🎥
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: gradio
7
+ sdk_version: 4.19.2
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
+ # YouTube to LinkedIn Post Converter
13
+
14
+ Transform your YouTube videos into professional LinkedIn posts with AI-powered content enhancement. This application:
15
+
16
+ - Extracts transcripts from YouTube videos
17
+ - Enhances content using AI
18
+ - Formats posts for LinkedIn
19
+ - Verifies content quality
20
+ - Provides improvement suggestions
21
+
22
+ ## Features
23
+
24
+ - 🎥 YouTube video processing
25
+ - ✨ AI-powered content enhancement
26
+ - 🔗 LinkedIn post formatting
27
+ - ✓ Content verification
28
+ - 📊 Quality improvement suggestions
29
+
30
+ ## How to Use
31
+
32
+ 1. Enter a YouTube video URL
33
+ 2. Click "Generate Post"
34
+ 3. Review the enhanced content
35
+ 4. Copy your LinkedIn-ready post
36
+
37
+ ## Sample Videos
38
+
39
+ Try these videos to test the application:
40
+ - Open AI video: https://www.youtube.com/watch?v=LsMxX86mm2Y
41
+ - Financial News: https://www.youtube.com/watch?v=hvP1UNALZ3g
42
+ - Video About AI: https://www.youtube.com/watch?v=Yq0QkCxoTHM
app.py ADDED
@@ -0,0 +1,1249 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ from dotenv import load_dotenv
4
+ from youtube_transcript_api import YouTubeTranscriptApi
5
+ from langchain_openai import ChatOpenAI, OpenAIEmbeddings
6
+ from langchain.prompts import ChatPromptTemplate
7
+ from langchain_core.output_parsers import StrOutputParser
8
+ from langgraph.graph import StateGraph, END
9
+ from typing import Dict, TypedDict, Annotated, List, Tuple, Union, Optional
10
+ import json
11
+ from langchain_chroma import Chroma
12
+ from langchain.schema import Document
13
+ from datetime import datetime
14
+ from langchain.text_splitter import RecursiveCharacterTextSplitter
15
+
16
+ # Load environment variables
17
+ load_dotenv(verbose=True)
18
+
19
+ # Verify OpenAI API key
20
+ if not os.getenv("OPENAI_API_KEY"):
21
+ raise ValueError("OpenAI API key not found.")
22
+
23
+ # Define state types
24
+ class ProcessState(TypedDict):
25
+ video_url: str
26
+ transcript: str
27
+ enhanced: str
28
+ linkedin_post: str
29
+ verification: dict
30
+ error: str
31
+ status: str
32
+ verification_score: float
33
+ enhancement_attempts: int
34
+ needs_improvement: bool
35
+ research_context: str
36
+
37
+ def extract_video_id(url: str) -> str:
38
+ """Extract video ID from YouTube URL."""
39
+ if "youtu.be" in url:
40
+ return url.split("/")[-1]
41
+ return url.split("v=")[-1].split("&")[0]
42
+
43
+ def get_transcript(state: ProcessState, progress=gr.Progress()) -> ProcessState:
44
+ """Get transcript from YouTube video."""
45
+ try:
46
+ progress(0.25, desc="Fetching transcript...")
47
+ video_id = extract_video_id(state["video_url"])
48
+ transcript = YouTubeTranscriptApi.get_transcript(video_id)
49
+ state["transcript"] = " ".join([segment["text"] for segment in transcript])
50
+ state["status"] = "✅ Transcript fetched"
51
+ return state
52
+ except Exception as e:
53
+ error_message = str(e).lower()
54
+ if "too many requests" in error_message or "429" in error_message:
55
+ state["error"] = "⚠️ YouTube API rate limit reached. Please wait a few minutes and try again."
56
+ state["status"] = "❌ Rate limit exceeded"
57
+ else:
58
+ state["error"] = f"⚠️ Error fetching transcript: {str(e)}"
59
+ state["status"] = "❌ Failed to fetch transcript"
60
+ return state
61
+
62
+ def get_chroma_collection():
63
+ """Get or create a Chroma collection using OpenAI embeddings."""
64
+ try:
65
+ collection = Chroma(
66
+ collection_name="youtube_videos",
67
+ embedding_function=OpenAIEmbeddings(model="text-embedding-3-small"),
68
+ persist_directory="./chroma_db"
69
+ )
70
+ return collection
71
+ except Exception as e:
72
+ raise Exception(f"Error creating Chroma collection: {str(e)}")
73
+
74
+ def enhance_content(state: ProcessState, progress=gr.Progress()) -> ProcessState:
75
+ """Enhance the transcript content with semantic search and similarity analysis."""
76
+ try:
77
+ if not state["transcript"]:
78
+ return state
79
+
80
+ progress(0.50, desc="Enhancing content...")
81
+
82
+ # Get similar content from the vector store
83
+ collection = get_chroma_collection()
84
+ similar_docs = collection.similarity_search(
85
+ state["transcript"],
86
+ k=3
87
+ )
88
+
89
+ # Initialize LLM for content generation
90
+ llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)
91
+ prompt = ChatPromptTemplate.from_messages([
92
+ ("system", """You are an expert content enhancer. Transform this transcript into engaging content:
93
+
94
+ 1. Identify and emphasize key points
95
+ 2. Add context and examples
96
+ 3. Make it more engaging and professional
97
+ 4. Keep it concise (max 3000 characters)
98
+ 5. Maintain factual accuracy
99
+
100
+ Transcript:
101
+ {transcript}
102
+
103
+ Similar Content for Context:
104
+ {similar_content}
105
+ """),
106
+ ("human", "Enhance this content for a professional audience.")
107
+ ])
108
+
109
+ chain = prompt | llm | StrOutputParser()
110
+ state["enhanced"] = chain.invoke({
111
+ "transcript": state["transcript"],
112
+ "similar_content": "\n".join([doc.page_content for doc in similar_docs])
113
+ })
114
+ state["status"] = "✅ Content enhanced"
115
+ return state
116
+ except Exception as e:
117
+ state["error"] = f"⚠️ Error enhancing content: {str(e)}"
118
+ state["status"] = "❌ Failed to enhance content"
119
+ return state
120
+
121
+ def format_linkedin_post(state: ProcessState, progress=gr.Progress()) -> ProcessState:
122
+ """Format content as a LinkedIn post."""
123
+ try:
124
+ if not state["enhanced"]:
125
+ return state
126
+
127
+ progress(0.75, desc="Formatting for LinkedIn...")
128
+
129
+ # Initialize LLM for formatting
130
+ llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)
131
+ prompt = ChatPromptTemplate.from_messages([
132
+ ("system", """Create an engaging LinkedIn post from this content. The post should be:
133
+
134
+ 1. Natural and conversational - write like a real person sharing insights
135
+ 2. Focused on value - emphasize practical takeaways and actionable insights
136
+ 3. Authentic - avoid overused phrases or corporate speak
137
+ 4. Visually clean - use line breaks and emojis sparingly and purposefully
138
+ 5. Under 1500 characters
139
+
140
+ Content Preservation Rules:
141
+ - MUST maintain the exact same topic and subject matter
142
+ - MUST keep all specific examples, techniques, and exercises mentioned
143
+ - MUST preserve the original context and purpose
144
+ - MUST include all key points from the original content
145
+ - MUST maintain the same level of technical detail
146
+ - MUST keep the same target audience in mind
147
+ - MUST preserve any specific terminology or jargon that's important to the topic
148
+ - MUST maintain the same tone and expertise level
149
+
150
+ Formatting Guidelines:
151
+ - Start with a hook that grabs attention
152
+ - Share insights in a natural flow
153
+ - Use 2-3 relevant hashtags maximum
154
+ - End with a genuine call to action
155
+ - Avoid numbered lists unless absolutely necessary
156
+ - Don't use section headers or dividers
157
+ - Don't use bullet points or emoji bullets
158
+ - Don't use multiple hashtag groups
159
+
160
+ Content to transform:
161
+ {content}
162
+
163
+ Remember: The goal is to make the content more engaging while keeping ALL the original information, examples, and technical details intact."""),
164
+ ("human", "Create a natural, engaging LinkedIn post that preserves all the original content and context.")
165
+ ])
166
+
167
+ chain = prompt | llm | StrOutputParser()
168
+ state["linkedin_post"] = chain.invoke({"content": state["enhanced"]})
169
+ state["status"] = "✅ LinkedIn post formatted"
170
+ return state
171
+ except Exception as e:
172
+ state["error"] = f"⚠️ Error formatting LinkedIn post: {str(e)}"
173
+ state["status"] = "❌ Failed to format LinkedIn post"
174
+ return state
175
+
176
+ def verify_content(state: ProcessState, progress=gr.Progress()) -> ProcessState:
177
+ """Verify the enhanced content against the original using semantic similarity."""
178
+ try:
179
+ if not state["enhanced"] or not state["transcript"]:
180
+ return state
181
+
182
+ progress(1.0, desc="Verifying content...")
183
+
184
+ # Initialize enhancement attempts if not present
185
+ if "enhancement_attempts" not in state:
186
+ state["enhancement_attempts"] = 0
187
+
188
+ # Calculate semantic similarity using Chroma
189
+ collection = get_chroma_collection()
190
+ similar_docs = collection.similarity_search(
191
+ state["enhanced"],
192
+ k=1
193
+ )
194
+ similarity_score = 0.0
195
+ if similar_docs:
196
+ # Chroma returns a list of Document objects with a score attribute
197
+ # But the default similarity_search does not return scores, so we just check if content is similar
198
+ similarity_score = 1.0 if similar_docs[0].page_content == state["transcript"] else 0.0
199
+
200
+ # Initialize LLM for verification
201
+ llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
202
+ prompt = ChatPromptTemplate.from_messages([
203
+ ("system", """Verify the enhanced content against the original:
204
+
205
+ 1. Check factual accuracy
206
+ 2. Ensure key messages are preserved
207
+ 3. Look for any misrepresentations
208
+
209
+ Return JSON in this format:
210
+ {{
211
+ "verified": boolean,
212
+ "score": float between 0-1,
213
+ "feedback": string with details
214
+ }}
215
+
216
+ Original:
217
+ {original}
218
+
219
+ Enhanced:
220
+ {enhanced}
221
+
222
+ Semantic Similarity Score: {similarity_score}"""),
223
+ ("human", "Verify this content.")
224
+ ])
225
+
226
+ chain = prompt | llm | StrOutputParser()
227
+ verification_result = json.loads(chain.invoke({
228
+ "original": state["transcript"],
229
+ "enhanced": state["enhanced"],
230
+ "similarity_score": similarity_score
231
+ }))
232
+
233
+ # Update state with verification results
234
+ state["verification"] = verification_result
235
+ state["verification_score"] = verification_result["score"]
236
+
237
+ # Trigger agent decision if score is below threshold
238
+ if verification_result["score"] < 0.85 and state["enhancement_attempts"] < 3:
239
+ state["needs_improvement"] = True
240
+ # Create improvement plan
241
+ state = agent_decide(state)
242
+ state["status"] = f"🔄 Planning improvements (Attempt {state['enhancement_attempts'] + 1}/3)"
243
+ else:
244
+ state["needs_improvement"] = False
245
+ if verification_result["score"] >= 0.85:
246
+ state["status"] = "✅ Content quality threshold met"
247
+ else:
248
+ state["status"] = "⚠️ Max enhancement attempts reached"
249
+
250
+ return state
251
+ except Exception as e:
252
+ state["error"] = f"⚠️ Error verifying content: {str(e)}"
253
+ state["status"] = "❌ Failed to verify content"
254
+ return state
255
+
256
+ def should_continue(state: ProcessState) -> bool:
257
+ """Determine if processing should continue."""
258
+ return not state.get("error", "")
259
+
260
+ def create_workflow() -> StateGraph:
261
+ """Create the LangGraph workflow."""
262
+ workflow = StateGraph(ProcessState)
263
+
264
+ # Add nodes
265
+ workflow.add_node("get_transcript", get_transcript)
266
+ workflow.add_node("enhance_content", enhance_content)
267
+ workflow.add_node("format_linkedin", format_linkedin_post)
268
+ workflow.add_node("verify_content", verify_content)
269
+ workflow.add_node("agent_decide", agent_decide)
270
+ workflow.add_node("research_content", research_content)
271
+ workflow.add_node("enhance_again", enhance_again)
272
+
273
+ # Set entry point
274
+ workflow.set_entry_point("get_transcript")
275
+
276
+ # Add edges for main flow
277
+ workflow.add_edge("get_transcript", "enhance_content")
278
+ workflow.add_edge("enhance_content", "format_linkedin")
279
+ workflow.add_edge("format_linkedin", "verify_content")
280
+ workflow.add_edge("verify_content", "agent_decide")
281
+
282
+ # Add conditional edges for agentic flow
283
+ workflow.add_conditional_edges(
284
+ "agent_decide",
285
+ lambda x: x["needs_improvement"],
286
+ {
287
+ True: "research_content",
288
+ False: END
289
+ }
290
+ )
291
+
292
+ # Add edges for enhancement loop
293
+ workflow.add_edge("research_content", "enhance_again")
294
+ workflow.add_edge("enhance_again", "verify_content")
295
+
296
+ # Add conditional edges for error handling
297
+ workflow.add_conditional_edges(
298
+ "get_transcript",
299
+ should_continue,
300
+ {
301
+ True: "enhance_content",
302
+ False: END
303
+ }
304
+ )
305
+ workflow.add_conditional_edges(
306
+ "enhance_content",
307
+ should_continue,
308
+ {
309
+ True: "format_linkedin",
310
+ False: END
311
+ }
312
+ )
313
+ workflow.add_conditional_edges(
314
+ "format_linkedin",
315
+ should_continue,
316
+ {
317
+ True: "verify_content",
318
+ False: END
319
+ }
320
+ )
321
+ workflow.add_conditional_edges(
322
+ "verify_content",
323
+ should_continue,
324
+ {
325
+ True: "agent_decide",
326
+ False: END
327
+ }
328
+ )
329
+ workflow.add_conditional_edges(
330
+ "research_content",
331
+ should_continue,
332
+ {
333
+ True: "enhance_again",
334
+ False: END
335
+ }
336
+ )
337
+ workflow.add_conditional_edges(
338
+ "enhance_again",
339
+ should_continue,
340
+ {
341
+ True: "verify_content",
342
+ False: END
343
+ }
344
+ )
345
+
346
+ return workflow
347
+
348
+ def process_video(video_url: str, progress=gr.Progress()) -> tuple:
349
+ """Process YouTube video and generate LinkedIn post."""
350
+ try:
351
+ # Input validation
352
+ if not video_url:
353
+ return (
354
+ "⚠️ Please enter a YouTube URL", # error
355
+ "❌ Failed: No URL provided", # status
356
+ "", # transcript
357
+ "", # enhanced
358
+ "", # linkedin
359
+ "" # verification
360
+ )
361
+
362
+ if "youtube.com" not in video_url and "youtu.be" not in video_url:
363
+ return (
364
+ "⚠️ Invalid URL. Please enter a YouTube URL", # error
365
+ "❌ Failed: Invalid URL", # status
366
+ "", # transcript
367
+ "", # enhanced
368
+ "", # linkedin
369
+ "" # verification
370
+ )
371
+
372
+ # Initialize state
373
+ initial_state = ProcessState(
374
+ video_url=video_url,
375
+ transcript="",
376
+ enhanced="",
377
+ linkedin_post="",
378
+ verification={},
379
+ error="",
380
+ status="Starting..."
381
+ )
382
+
383
+ # Create and run workflow
384
+ workflow = create_workflow()
385
+ app = workflow.compile()
386
+ final_state = app.invoke(initial_state)
387
+
388
+ # Format verification text
389
+ if final_state.get("verification"):
390
+ verification_text = f"""Verification Results:
391
+ • Status: {"✅ Verified" if final_state["verification"]["verified"] else "❌ Not Verified"}
392
+ • Accuracy Score: {final_state["verification"]["score"]:.2f}
393
+ • Feedback: {final_state["verification"]["feedback"]}"""
394
+ else:
395
+ verification_text = ""
396
+
397
+ return (
398
+ final_state.get("error", ""), # error
399
+ final_state.get("status", ""), # status
400
+ final_state.get("transcript", ""), # transcript
401
+ final_state.get("enhanced", ""), # enhanced
402
+ final_state.get("linkedin_post", ""), # linkedin
403
+ verification_text # verification
404
+ )
405
+
406
+ except Exception as e:
407
+ return (
408
+ f"⚠️ Error: {str(e)}", # error
409
+ "❌ Processing failed", # status
410
+ "", # transcript
411
+ "", # enhanced
412
+ "", # linkedin
413
+ "" # verification
414
+ )
415
+
416
+ def process_from_stage(state: ProcessState, start_stage: str, progress=gr.Progress()) -> tuple:
417
+ """Process content from a specific stage onwards."""
418
+ try:
419
+ # Select appropriate workflow based on stage
420
+ if start_stage == "enhance":
421
+ workflow = create_workflow()
422
+ if not state["transcript"]:
423
+ return (
424
+ "⚠️ No transcript available to enhance",
425
+ "❌ Failed: No transcript",
426
+ state.get("transcript", ""),
427
+ "",
428
+ "",
429
+ ""
430
+ )
431
+ elif start_stage == "format":
432
+ workflow = create_workflow()
433
+ if not state["enhanced"]:
434
+ return (
435
+ "⚠️ No enhanced content available to format",
436
+ "❌ Failed: No enhanced content",
437
+ state.get("transcript", ""),
438
+ state.get("enhanced", ""),
439
+ "",
440
+ ""
441
+ )
442
+ else:
443
+ workflow = create_workflow()
444
+
445
+ app = workflow.compile()
446
+ final_state = app.invoke(state)
447
+
448
+ # Format verification text
449
+ if final_state.get("verification"):
450
+ verification_text = f"""Verification Results:
451
+ • Status: {"✅ Verified" if final_state["verification"]["verified"] else "❌ Not Verified"}
452
+ • Accuracy Score: {final_state["verification"]["score"]:.2f}
453
+ • Feedback: {final_state["verification"]["feedback"]}"""
454
+ else:
455
+ verification_text = ""
456
+
457
+ return (
458
+ final_state.get("error", ""),
459
+ final_state.get("status", ""),
460
+ final_state.get("transcript", ""),
461
+ final_state.get("enhanced", ""),
462
+ final_state.get("linkedin_post", ""),
463
+ verification_text
464
+ )
465
+
466
+ except Exception as e:
467
+ return (
468
+ f"⚠️ Error: {str(e)}",
469
+ "❌ Processing failed",
470
+ state.get("transcript", ""),
471
+ state.get("enhanced", ""),
472
+ state.get("linkedin_post", ""),
473
+ ""
474
+ )
475
+
476
+ def format_verification_text(verification: dict) -> str:
477
+ """Format verification results into a readable string."""
478
+ if not verification:
479
+ return ""
480
+
481
+ return f"""Verification Results:
482
+ • Status: {"✅ Verified" if verification.get("verified") else "❌ Not Verified"}
483
+ • Accuracy Score: {verification.get("score", 0):.2f}
484
+ • Feedback: {verification.get("feedback", "No feedback available")}"""
485
+
486
+ def safe_json_loads(json_str: str, default: dict = None) -> dict:
487
+ """Safely parse JSON string with error handling."""
488
+ if default is None:
489
+ default = {}
490
+ try:
491
+ return json.loads(json_str) if json_str else default
492
+ except json.JSONDecodeError:
493
+ return default
494
+
495
+ def format_improvement_plan(plan: dict) -> str:
496
+ """Format the improvement plan into a readable string."""
497
+ if not plan:
498
+ return "No improvement plan available"
499
+
500
+ text = "📋 Improvement Plan:\n\n"
501
+
502
+ # Improvement Areas
503
+ if "improvement_areas" in plan:
504
+ text += "🎯 Priority Areas:\n"
505
+ for area in plan["improvement_areas"]:
506
+ text += f"• {area.get('area', 'N/A')} (Priority: {area.get('priority', 'N/A')}/5)\n"
507
+ text += f" Strategy: {area.get('strategy', 'N/A')}\n"
508
+ text += f" Research Focus: {area.get('research_focus', 'N/A')}\n\n"
509
+
510
+ # Research Priorities
511
+ if "research_priorities" in plan:
512
+ text += "🔍 Research Priorities:\n"
513
+ for topic in plan["research_priorities"]:
514
+ text += f"• {topic.get('topic', 'N/A')}\n"
515
+ text += f" Reason: {topic.get('reason', 'N/A')}\n"
516
+ text += f" Expected Impact: {topic.get('expected_impact', 'N/A')}\n\n"
517
+
518
+ # Enhancement Strategy
519
+ if "enhancement_strategy" in plan:
520
+ text += "⚡ Enhancement Strategy:\n"
521
+ strategy = plan["enhancement_strategy"]
522
+ text += f"• Approach: {strategy.get('approach', 'N/A')}\n"
523
+ text += f"• Key Focus: {strategy.get('key_focus', 'N/A')}\n"
524
+ text += "• Expected Improvements:\n"
525
+ for imp in strategy.get("expected_improvements", []):
526
+ text += f" - {imp}\n"
527
+
528
+ return text
529
+
530
+ def format_research_results(research: dict) -> str:
531
+ """Format the research results into a readable string."""
532
+ if not research:
533
+ return "No research results available"
534
+
535
+ text = "📚 Research Results:\n\n"
536
+
537
+ # Focused Research
538
+ if "focused_research" in research:
539
+ text += "🎯 Focused Research by Area:\n"
540
+ for area, data in research["focused_research"].items():
541
+ text += f"• {area} (Priority: {data.get('priority', 'N/A')}/5)\n"
542
+ text += f" Strategy: {data.get('strategy', 'N/A')}\n"
543
+ text += " Key Findings:\n"
544
+ for content in data.get("content", [])[:1]: # Show first finding
545
+ text += f" - {content[:200]}...\n\n"
546
+
547
+ # Additional Research
548
+ if research.get("similar_content"):
549
+ text += "📖 Additional Research:\n"
550
+ for content in research["similar_content"][:2]: # Show first two
551
+ text += f"• {content[:200]}...\n\n"
552
+
553
+ return text
554
+
555
+ def create_ui():
556
+ with gr.Blocks(theme='JohnSmith9982/small_and_pretty') as demo:
557
+ current_state = gr.State({
558
+ "video_url": "",
559
+ "transcript": "",
560
+ "enhanced": "",
561
+ "linkedin_post": "",
562
+ "verification": {},
563
+ "error": "",
564
+ "status": "",
565
+ "improvement_plan": {},
566
+ "research_context": "{}",
567
+ "enhancement_attempts": 0,
568
+ "needs_improvement": False
569
+ })
570
+
571
+ gr.Markdown(
572
+ """
573
+ # YouTube to LinkedIn Post Converter
574
+ Transform your YouTube videos into professional LinkedIn posts with AI content enhancement.
575
+
576
+ ### 🎬 Sample Videos to Try
577
+ Copy any of these URLs to test the application:
578
+ ```
579
+ 1. Open AI video: https://www.youtube.com/watch?v=LsMxX86mm2Y
580
+ Agent will likely find high quality initial content and not improve
581
+
582
+ 2. Financial News: https://www.youtube.com/watch?v=hvP1UNALZ3g
583
+ Agent will likely decide to not improve this post
584
+
585
+ 3. Video About AI: https://www.youtube.com/watch?v=Yq0QkCxoTHM
586
+ Agent will likely decide to improve this post
587
+ ```
588
+ These videos are chosen to show the application's ability to handle different types of professional content.
589
+ """
590
+ )
591
+
592
+ with gr.Row():
593
+ with gr.Column():
594
+ video_url = gr.Textbox(
595
+ label="YouTube URL",
596
+ placeholder="https://www.youtube.com/watch?v=e1GJ5tZePjk",
597
+ show_label=True
598
+ )
599
+ youtube_convert_btn = gr.Button("🚀 Generate from YouTube", variant="primary", size="lg")
600
+
601
+ status = gr.Textbox(
602
+ label="Status",
603
+ value="Ready to process...",
604
+ interactive=False
605
+ )
606
+
607
+ error = gr.Textbox(
608
+ label="Error",
609
+ visible=False,
610
+ interactive=False
611
+ )
612
+
613
+ with gr.Tabs() as tabs:
614
+ with gr.TabItem("📝 Content"):
615
+ with gr.Row():
616
+ with gr.Column():
617
+ transcript = gr.TextArea(
618
+ label="📄 Raw Transcript",
619
+ interactive=False,
620
+ show_copy_button=True,
621
+ lines=8
622
+ )
623
+ with gr.Column():
624
+ enhanced = gr.TextArea(
625
+ label="✨ Enhanced Content",
626
+ interactive=False,
627
+ show_copy_button=True,
628
+ lines=8
629
+ )
630
+
631
+ with gr.Row():
632
+ with gr.Column():
633
+ linkedin = gr.TextArea(
634
+ label="🔗 LinkedIn Post",
635
+ interactive=False,
636
+ show_copy_button=True,
637
+ lines=6
638
+ )
639
+
640
+ with gr.Row():
641
+ with gr.Column():
642
+ verification = gr.TextArea(
643
+ label="✓ Verification Results",
644
+ interactive=False,
645
+ lines=4
646
+ )
647
+
648
+ with gr.Row():
649
+ with gr.Column():
650
+ improvement_plan = gr.TextArea(
651
+ label="📋 Improvement Plan",
652
+ interactive=False,
653
+ show_copy_button=True,
654
+ lines=8,
655
+ visible=True,
656
+ value="Waiting for verification..."
657
+ )
658
+
659
+ with gr.Row():
660
+ with gr.Column():
661
+ research_results = gr.TextArea(
662
+ label="🔍 Research Results",
663
+ interactive=False,
664
+ show_copy_button=True,
665
+ lines=8,
666
+ visible=True,
667
+ value="Waiting for research..."
668
+ )
669
+
670
+ with gr.Row():
671
+ with gr.Column():
672
+ improved_linkedin = gr.TextArea(
673
+ label="🚀 Improved LinkedIn Post Final",
674
+ interactive=False,
675
+ show_copy_button=True,
676
+ lines=6,
677
+ visible=True,
678
+ value="Waiting for improvements..."
679
+ )
680
+
681
+ # Loading indicators
682
+ with gr.Row(visible=False) as loading_indicators:
683
+ transcript_loading = gr.Markdown("🔄 Fetching transcript...")
684
+ enhanced_loading = gr.Markdown("🔄 Enhancing content...")
685
+ linkedin_loading = gr.Markdown("🔄 Formatting for LinkedIn...")
686
+ verify_loading = gr.Markdown("🔄 Verifying content...")
687
+ plan_loading = gr.Markdown("🔄 Creating improvement plan...")
688
+ research_loading = gr.Markdown("🔄 Researching content...")
689
+ improved_loading = gr.Markdown("🔄 Creating improved post...")
690
+
691
+ with gr.TabItem("ℹ️ Help"):
692
+ gr.Markdown(
693
+ """
694
+ ### How to Use
695
+ 1. **Input**: Paste a YouTube video URL in the input field
696
+ 2. **Process**: Click the "Generate Post" button
697
+ 3. **Wait**: The system will process your video through multiple steps
698
+ 4. **Review**: Check the generated content in each tab
699
+ 5. **Copy**: Use the copy button to grab your LinkedIn post
700
+
701
+ ### 🔄 Regeneration Options
702
+ - Click 🔄 next to "Enhanced Content" to regenerate from the enhancement stage
703
+ - Click 🔄 next to "LinkedIn Post" to regenerate from the formatting stage
704
+
705
+ ### 💡 Tips for Best Results
706
+ - Use videos with clear English audio
707
+ - Optimal video length: 5-15 minutes
708
+ - Ensure videos have accurate captions
709
+ - Review and personalize the post before sharing
710
+ - Consider your target audience when selecting videos
711
+
712
+ """
713
+ )
714
+
715
+ def update_loading_state(stage: str):
716
+ """Update loading indicators based on current stage."""
717
+ states = {
718
+ "transcript": [True, False, False, False, False, False, False],
719
+ "enhance": [False, True, False, False, False, False, False],
720
+ "format": [False, False, True, False, False, False, False],
721
+ "verify": [False, False, False, True, False, False, False],
722
+ "plan": [False, False, False, False, True, False, False],
723
+ "research": [False, False, False, False, False, True, False],
724
+ "improved": [False, False, False, False, False, False, True],
725
+ "done": [False, False, False, False, False, False, False]
726
+ }
727
+
728
+ # Loading messages for each stage
729
+ loading_messages = {
730
+ "transcript": "🔄 Fetching transcript...\n⏳ Please wait...",
731
+ "enhance": "✨ Enhancing content...\n⚡ AI is working its magic...",
732
+ "format": "🎨 Formatting for LinkedIn...\n📝 Creating engaging post...",
733
+ "verify": "🔍 Verifying content...\n⚖️ Checking accuracy...",
734
+ "plan": "🔄 Creating improvement plan...",
735
+ "research": "🔎 Researching content...\n📚 Finding relevant information...",
736
+ "improved": "🚀 Creating improved LinkedIn post...\n✨ Applying enhancements..."
737
+ }
738
+
739
+ # Get current stage message
740
+ current_message = loading_messages.get(stage, "")
741
+
742
+ # Return loading states and message
743
+ return [
744
+ gr.update(visible=state) for state in states.get(stage, [False] * 7)
745
+ ], current_message
746
+
747
+ def process_with_loading(url, state):
748
+ """Process video with loading indicators."""
749
+ try:
750
+ # Initialize state if needed
751
+ if "improvement_plan" not in state:
752
+ state["improvement_plan"] = {}
753
+ if "research_context" not in state:
754
+ state["research_context"] = "{}"
755
+ if "enhancement_attempts" not in state:
756
+ state["enhancement_attempts"] = 0
757
+ if "needs_improvement" not in state:
758
+ state["needs_improvement"] = False
759
+
760
+ # Show loading indicators
761
+ loading_states, message = update_loading_state("transcript")
762
+ yield [
763
+ "", # error
764
+ "Processing...", # status
765
+ message, # transcript (loading)
766
+ "", # enhanced
767
+ "", # linkedin
768
+ "", # verification
769
+ "Waiting for verification...", # improvement plan
770
+ "Waiting for research...", # research results
771
+ "Waiting for improvements...", # improved linkedin
772
+ state, # current_state
773
+ *loading_states # loading indicators
774
+ ]
775
+
776
+ # Get transcript
777
+ state["video_url"] = url
778
+ transcript_text = get_transcript(state)["transcript"]
779
+
780
+ # Show enhancing state
781
+ loading_states, message = update_loading_state("enhance")
782
+ yield [
783
+ "",
784
+ "Enhancing content...",
785
+ transcript_text,
786
+ message, # enhanced (loading)
787
+ "",
788
+ "",
789
+ "",
790
+ "",
791
+ "",
792
+ state,
793
+ *loading_states
794
+ ]
795
+
796
+ # Enhance content
797
+ state["transcript"] = transcript_text
798
+ enhanced_state = enhance_content(state)
799
+ enhanced_text = enhanced_state["enhanced"]
800
+
801
+ # Show formatting state
802
+ loading_states, message = update_loading_state("format")
803
+ yield [
804
+ "",
805
+ "Formatting for LinkedIn...",
806
+ transcript_text,
807
+ enhanced_text,
808
+ message, # linkedin (loading)
809
+ "",
810
+ "",
811
+ "",
812
+ "",
813
+ state,
814
+ *loading_states
815
+ ]
816
+
817
+ # Format LinkedIn post
818
+ state["enhanced"] = enhanced_text
819
+ linkedin_state = format_linkedin_post(state)
820
+ linkedin_text = linkedin_state["linkedin_post"]
821
+
822
+ # Show verifying state
823
+ loading_states, message = update_loading_state("verify")
824
+ yield [
825
+ "",
826
+ "Verifying content...",
827
+ transcript_text,
828
+ enhanced_text,
829
+ linkedin_text,
830
+ "🔍 Verifying...\n⚖️ Analyzing accuracy...", # verification (loading)
831
+ "",
832
+ "",
833
+ "",
834
+ state,
835
+ *loading_states
836
+ ]
837
+
838
+ # Verify content
839
+ state["linkedin_post"] = linkedin_text
840
+ final_state = verify_content(state)
841
+ verification_text = format_verification_text(final_state.get("verification", {}))
842
+
843
+ # Update improvement plan and research results
844
+ improvement_plan_text = format_improvement_plan(final_state.get("improvement_plan", {}))
845
+ research_results_text = format_research_results(safe_json_loads(final_state.get("research_context", "{}")))
846
+
847
+ # Check if enhancement is needed
848
+ if final_state.get("needs_improvement", False):
849
+ # Show planning state
850
+ loading_states, message = update_loading_state("plan")
851
+ yield [
852
+ "",
853
+ f"Creating improvement plan (Attempt {final_state.get('enhancement_attempts', 1)}/3)...",
854
+ transcript_text,
855
+ enhanced_text,
856
+ linkedin_text,
857
+ verification_text,
858
+ improvement_plan_text,
859
+ research_results_text,
860
+ "",
861
+ state,
862
+ *loading_states
863
+ ]
864
+
865
+ # Show researching state
866
+ loading_states, message = update_loading_state("research")
867
+ yield [
868
+ "",
869
+ f"Researching content (Attempt {final_state.get('enhancement_attempts', 1)}/3)...",
870
+ transcript_text,
871
+ enhanced_text,
872
+ linkedin_text,
873
+ verification_text,
874
+ improvement_plan_text,
875
+ research_results_text,
876
+ "",
877
+ state,
878
+ *loading_states
879
+ ]
880
+
881
+ # Research content
882
+ state = research_content(state)
883
+ research_results_text = format_research_results(safe_json_loads(state.get("research_context", "{}")))
884
+
885
+ # Show enhancing again state
886
+ loading_states, message = update_loading_state("enhance")
887
+ yield [
888
+ "",
889
+ f"Enhancing content again (Attempt {final_state.get('enhancement_attempts', 1)}/3)...",
890
+ transcript_text,
891
+ enhanced_text,
892
+ linkedin_text,
893
+ verification_text,
894
+ improvement_plan_text,
895
+ research_results_text,
896
+ "",
897
+ state,
898
+ *loading_states
899
+ ]
900
+
901
+ # Enhance again
902
+ state = enhance_again(state)
903
+ enhanced_text = state["enhanced"]
904
+
905
+ # Update LinkedIn post
906
+ state["enhanced"] = enhanced_text
907
+ linkedin_state = format_linkedin_post(state)
908
+ linkedin_text = linkedin_state["linkedin_post"]
909
+
910
+ # Verify again
911
+ state["linkedin_post"] = linkedin_text
912
+ final_state = verify_content(state)
913
+ verification_text = format_verification_text(final_state.get("verification", {}))
914
+ improvement_plan_text = format_improvement_plan(final_state.get("improvement_plan", {}))
915
+ research_results_text = format_research_results(safe_json_loads(final_state.get("research_context", "{}")))
916
+
917
+ # After research and enhancement, create improved LinkedIn post
918
+ if final_state.get("needs_improvement", False):
919
+ # Show improved post loading state
920
+ loading_states, message = update_loading_state("improved")
921
+ yield [
922
+ "",
923
+ f"Creating improved LinkedIn post (Attempt {final_state.get('enhancement_attempts', 1)}/3)...",
924
+ transcript_text,
925
+ enhanced_text,
926
+ linkedin_text,
927
+ verification_text,
928
+ improvement_plan_text,
929
+ research_results_text,
930
+ message, # improved linkedin (loading)
931
+ state,
932
+ *loading_states
933
+ ]
934
+
935
+ # Create improved LinkedIn post
936
+ improved_state = format_linkedin_post(final_state)
937
+ improved_text = improved_state["linkedin_post"]
938
+
939
+ # Update final state
940
+ final_state["improved_linkedin"] = improved_text
941
+
942
+ # Complete
943
+ loading_states, _ = update_loading_state("done")
944
+ yield [
945
+ "",
946
+ "✅ Processing complete!",
947
+ transcript_text,
948
+ enhanced_text,
949
+ linkedin_text,
950
+ verification_text,
951
+ improvement_plan_text,
952
+ research_results_text,
953
+ final_state.get("improved_linkedin", "No improvements needed"),
954
+ final_state,
955
+ *loading_states
956
+ ]
957
+
958
+ except Exception as e:
959
+ loading_states, _ = update_loading_state("done")
960
+ yield [
961
+ f"⚠️ Error: {str(e)}",
962
+ "❌ Processing failed",
963
+ state.get("transcript", ""),
964
+ state.get("enhanced", ""),
965
+ state.get("linkedin_post", ""),
966
+ "",
967
+ "Error occurred during processing",
968
+ "Error occurred during processing",
969
+ "Error occurred during processing",
970
+ state,
971
+ *loading_states
972
+ ]
973
+
974
+ # Set up event handlers
975
+ youtube_convert_btn.click(
976
+ fn=process_with_loading,
977
+ inputs=[video_url, current_state],
978
+ outputs=[
979
+ error,
980
+ status,
981
+ transcript,
982
+ enhanced,
983
+ linkedin,
984
+ verification,
985
+ improvement_plan,
986
+ research_results,
987
+ improved_linkedin,
988
+ current_state,
989
+ transcript_loading,
990
+ enhanced_loading,
991
+ linkedin_loading,
992
+ verify_loading,
993
+ plan_loading,
994
+ research_loading,
995
+ improved_loading
996
+ ],
997
+ show_progress=True, # Show progress bar
998
+ api_name="convert" # Name the API endpoint
999
+ )
1000
+
1001
+ # Update error visibility with immediate feedback
1002
+ error.change(
1003
+ lambda x: gr.update(visible=bool(x), value=x), # Update both visibility and value
1004
+ error,
1005
+ error,
1006
+ queue=False # Process immediately
1007
+ )
1008
+
1009
+ # Add loading state visibility updates
1010
+ def update_loading_visibility(is_loading):
1011
+ return {
1012
+ loading: gr.update(visible=is_loading)
1013
+ for loading in [
1014
+ transcript_loading,
1015
+ enhanced_loading,
1016
+ linkedin_loading,
1017
+ verify_loading,
1018
+ plan_loading,
1019
+ research_loading,
1020
+ improved_loading
1021
+ ]
1022
+ }
1023
+
1024
+ youtube_convert_btn.click(
1025
+ lambda: update_loading_visibility(True),
1026
+ None,
1027
+ [transcript_loading, enhanced_loading, linkedin_loading,
1028
+ verify_loading, plan_loading, research_loading, improved_loading],
1029
+ queue=False
1030
+ )
1031
+
1032
+ return demo
1033
+
1034
+ def agent_decide(state: ProcessState, progress=gr.Progress()) -> ProcessState:
1035
+ """Agent decides whether to enhance content further based on verification score and creates an improvement plan."""
1036
+ try:
1037
+ progress(0.95, desc="Analyzing content quality and planning improvements...")
1038
+
1039
+ # Get verification score and attempts
1040
+ score = state.get("verification", {}).get("score", 0)
1041
+ attempts = state.get("enhancement_attempts", 0)
1042
+ feedback = state.get("verification", {}).get("feedback", "")
1043
+
1044
+ # Initialize LLM for agentic decision making
1045
+ llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)
1046
+ prompt = ChatPromptTemplate.from_messages([
1047
+ ("system", """You are an expert content strategist. Analyze the content quality and create an improvement plan.
1048
+
1049
+ Current Content:
1050
+ {content}
1051
+
1052
+ Verification Results:
1053
+ - Score: {score}
1054
+ - Feedback: {feedback}
1055
+ - Previous Attempts: {attempts}
1056
+
1057
+ Create a detailed improvement plan in JSON format:
1058
+ {{
1059
+ "needs_improvement": boolean,
1060
+ "improvement_areas": [
1061
+ {{
1062
+ "area": string,
1063
+ "priority": number (1-5),
1064
+ "strategy": string,
1065
+ "research_focus": string
1066
+ }}
1067
+ ],
1068
+ "research_priorities": [
1069
+ {{
1070
+ "topic": string,
1071
+ "reason": string,
1072
+ "expected_impact": string
1073
+ }}
1074
+ ],
1075
+ "enhancement_strategy": {{
1076
+ "approach": string,
1077
+ "key_focus": string,
1078
+ "expected_improvements": [string]
1079
+ }}
1080
+ }}
1081
+
1082
+ Consider:
1083
+ 1. Content quality and engagement
1084
+ 2. Information accuracy and completeness
1085
+ 3. Target audience needs
1086
+ 4. Previous enhancement attempts
1087
+ 5. Available research context"""),
1088
+ ("human", "Analyze this content and create an improvement plan.")
1089
+ ])
1090
+
1091
+ chain = prompt | llm | StrOutputParser()
1092
+ plan = json.loads(chain.invoke({
1093
+ "content": state["enhanced"],
1094
+ "score": score,
1095
+ "feedback": feedback,
1096
+ "attempts": attempts
1097
+ }))
1098
+
1099
+ # Update state with plan
1100
+ state["verification_score"] = score
1101
+ state["enhancement_attempts"] = attempts
1102
+ state["needs_improvement"] = plan["needs_improvement"]
1103
+ state["improvement_plan"] = plan
1104
+
1105
+ # Create detailed status message
1106
+ if plan["needs_improvement"] and attempts < 3:
1107
+ status = f"🔄 Planning improvements (Attempt {attempts + 1}/3)\n"
1108
+ status += "Key focus areas:\n"
1109
+ for area in plan["improvement_areas"][:2]: # Show top 2 priorities
1110
+ status += f"• {area['area']} (Priority: {area['priority']})\n"
1111
+ state["status"] = status
1112
+ else:
1113
+ if score >= 0.95:
1114
+ state["status"] = "✅ Content quality threshold met"
1115
+ else:
1116
+ state["status"] = "⚠️ Max enhancement attempts reached"
1117
+
1118
+ return state
1119
+ except Exception as e:
1120
+ state["error"] = f"⚠️ Error in agent decision: {str(e)}"
1121
+ state["status"] = "❌ Failed to analyze content"
1122
+ return state
1123
+
1124
+ def research_content(state: ProcessState, progress=gr.Progress()) -> ProcessState:
1125
+ """Research additional context based on the improvement plan."""
1126
+ try:
1127
+ progress(0.96, desc="Researching based on improvement plan...")
1128
+
1129
+ # Get improvement plan
1130
+ plan = state.get("improvement_plan", {})
1131
+ if not plan:
1132
+ raise Exception("No improvement plan found")
1133
+
1134
+ # Initialize research results
1135
+ research_results = {
1136
+ "similar_content": [],
1137
+ "focused_research": {},
1138
+ "verification_feedback": state.get("verification", {}).get("feedback", "")
1139
+ }
1140
+
1141
+ # Get similar content from vector store
1142
+ collection = get_chroma_collection()
1143
+
1144
+ # Research each priority area
1145
+ for area in plan["improvement_areas"]:
1146
+ # Search for content related to this area
1147
+ similar_docs = collection.similarity_search(
1148
+ f"{area['area']} {area['research_focus']}",
1149
+ k=2
1150
+ )
1151
+
1152
+ # Store research results
1153
+ research_results["focused_research"][area["area"]] = {
1154
+ "content": [doc.page_content for doc in similar_docs],
1155
+ "priority": area["priority"],
1156
+ "strategy": area["strategy"]
1157
+ }
1158
+
1159
+ # Research specific topics from research_priorities
1160
+ for topic in plan["research_priorities"]:
1161
+ topic_docs = collection.similarity_search(
1162
+ topic["topic"],
1163
+ k=1
1164
+ )
1165
+ if topic_docs:
1166
+ research_results["similar_content"].extend([doc.page_content for doc in topic_docs])
1167
+
1168
+ # Store research results
1169
+ state["research_context"] = json.dumps(research_results)
1170
+ state["status"] = "✅ Research completed based on improvement plan"
1171
+ return state
1172
+ except Exception as e:
1173
+ state["error"] = f"⚠️ Error researching content: {str(e)}"
1174
+ state["status"] = "❌ Failed to research content"
1175
+ return state
1176
+
1177
+ def enhance_again(state: ProcessState, progress=gr.Progress()) -> ProcessState:
1178
+ """Enhance content using research and improvement plan."""
1179
+ try:
1180
+ progress(0.97, desc="Enhancing content based on research and plan...")
1181
+
1182
+ # Get research context and improvement plan
1183
+ research_context = json.loads(state["research_context"])
1184
+ plan = state.get("improvement_plan", {})
1185
+ if not plan:
1186
+ raise Exception("No improvement plan found")
1187
+
1188
+ # Initialize LLM for enhancement
1189
+ llm = ChatOpenAI(model="gpt-3.5-turbo", temperature=0.7)
1190
+ prompt = ChatPromptTemplate.from_messages([
1191
+ ("system", """You are an expert content enhancer. Improve the content based on the research and improvement plan while maintaining the original topic and key messages.
1192
+
1193
+ Current Content:
1194
+ {content}
1195
+
1196
+ Improvement Plan:
1197
+ {plan}
1198
+
1199
+ Research Results:
1200
+ {research}
1201
+
1202
+ Enhancement Strategy:
1203
+ {strategy}
1204
+
1205
+ Create enhanced content that:
1206
+ 1. Maintains the original topic and key messages
1207
+ 2. Addresses each improvement area according to its priority
1208
+ 3. Incorporates relevant research findings
1209
+ 4. Follows the enhancement strategy
1210
+ 5. Improves engagement and clarity
1211
+ 6. Keeps the same core subject matter and examples
1212
+
1213
+ Important:
1214
+ - DO NOT change the main topic or subject matter
1215
+ - DO NOT replace specific examples with generic ones
1216
+ - DO NOT lose the original context or purpose
1217
+ - DO NOT generate content about a different topic
1218
+ - DO preserve and enhance the original message"""),
1219
+ ("human", "Enhance this content while maintaining its original topic and key messages.")
1220
+ ])
1221
+
1222
+ chain = prompt | llm | StrOutputParser()
1223
+ enhanced = chain.invoke({
1224
+ "content": state["enhanced"],
1225
+ "plan": json.dumps(plan),
1226
+ "research": json.dumps(research_context),
1227
+ "strategy": json.dumps(plan["enhancement_strategy"])
1228
+ })
1229
+
1230
+ # Update state
1231
+ state["enhanced"] = enhanced
1232
+ state["enhancement_attempts"] = state.get("enhancement_attempts", 0) + 1
1233
+ state["status"] = f"✅ Content enhanced with research (Attempt {state['enhancement_attempts']}/3)"
1234
+ return state
1235
+ except Exception as e:
1236
+ state["error"] = f"⚠️ Error enhancing content: {str(e)}"
1237
+ state["status"] = "❌ Failed to enhance content"
1238
+ return state
1239
+
1240
+ if __name__ == "__main__":
1241
+ demo = create_ui()
1242
+ demo.queue() # Enable queuing for better handling of concurrent requests
1243
+ demo.launch(
1244
+ server_name="0.0.0.0", # Required for Hugging Face Spaces
1245
+ server_port=7860, # Standard port for Hugging Face Spaces
1246
+ show_error=True,
1247
+ share=False, # Disable sharing for production
1248
+ show_api=False
1249
+ )
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ gradio>=4.19.2
2
+ python-dotenv>=1.0.1
3
+ youtube-transcript-api>=0.6.2
4
+ langchain-openai>=0.0.8
5
+ langchain>=0.1.9
6
+ langgraph>=0.0.27
7
+ langchain-community>=0.0.27
8
+ langchain-chroma>=0.1.4