invincible-jha commited on
Commit
788d26b
Β·
verified Β·
1 Parent(s): e8513b3

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +234 -243
  2. requirements.txt +0 -1
app.py CHANGED
@@ -17,7 +17,7 @@ import logging
17
  import json
18
  import numpy as np
19
  from dataclasses import dataclass, asdict
20
- import queue
21
  import threading
22
  from collections import defaultdict
23
 
@@ -25,6 +25,20 @@ from collections import defaultdict
25
  logging.basicConfig(level=logging.INFO)
26
  logger = logging.getLogger(__name__)
27
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
28
  @dataclass
29
  class VCStyle:
30
  """Store VC's personal style preferences"""
@@ -44,280 +58,257 @@ class LiveCallContext:
44
  questions_asked: List[str]
45
  action_items: List[str]
46
 
47
- class RealTimeProcessor:
48
- """Handle real-time audio processing and analysis"""
49
 
50
- def __init__(self, whisper_model, llm_pipeline):
51
- self.whisper_model = whisper_model
52
- self.llm_pipeline = llm_pipeline
53
- self.audio_buffer = queue.Queue()
54
- self.transcript_buffer = queue.Queue()
55
- self.context = defaultdict(list)
56
-
57
- def process_audio_chunk(self, audio_chunk):
58
- """Process incoming audio chunk"""
59
  try:
60
- # Simulate real-time processing for Spaces
61
- result = self.whisper_model.transcribe(audio_chunk)
62
- return result["text"]
63
  except Exception as e:
64
- logger.error(f"Error processing audio chunk: {e}")
 
65
  return None
66
 
67
- def generate_live_insights(self, transcript: str, vc_style: VCStyle) -> Dict[str, Any]:
68
- """Generate real-time insights based on transcript"""
69
- prompt = f"""Given this conversation transcript and VC's interests, provide real-time insights:
70
- Transcript: {transcript}
71
- VC Interests: {vc_style.key_interests}
72
-
73
- Provide insights in these areas:
74
- 1. Key Discussion Points
75
- 2. Potential Red Flags
76
- 3. Follow-up Questions
77
- 4. Market Insights
78
- """
79
-
80
  try:
81
- response = self.llm_pipeline(prompt, max_length=512)
82
- return json.loads(response[0]['generated_text'])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
83
  except Exception as e:
84
- logger.error(f"Error generating insights: {e}")
85
- return {}
86
-
87
- def update_context(self, transcript: str, insights: Dict[str, Any]):
88
- """Update call context with new information"""
89
- self.context['transcripts'].append(transcript)
90
- self.context['insights'].extend(insights.get('key_points', []))
91
- self.context['questions'].extend(insights.get('questions', []))
92
 
93
- class DynamicTemplate:
94
- """Handle dynamic template management"""
95
-
96
- def __init__(self):
97
- self.default_template = {
98
- "product": {"problem": "", "solution": "", "other": ""},
99
- "finances": {
100
- "capital_raised": "", "cash_on_hand": "",
101
- "monthly_burn": "", "gross_margin": "",
102
- "deal_dynamics": ""
103
- },
104
- "market": {
105
- "revenue": "", "yoy_growth": "", "tam": "",
106
- "pricing": "", "acv_arpa": "", "churn": "",
107
- "gtm": "", "competition": "", "other": ""
108
- },
109
- "concerns": {"risks": "", "mitigations": "", "other": ""},
110
- "free_form": "",
111
- "spv_actions": []
112
- }
113
- self.active_templates = {}
114
 
115
- def create_custom_template(self, vc_style: VCStyle) -> Dict[str, Any]:
116
- """Create a template based on VC's style"""
117
- template = self.default_template.copy()
118
 
119
- # Add custom sections
120
- for section in vc_style.custom_sections:
121
- template[section] = {"notes": "", "actions": []}
 
 
 
 
 
 
 
 
 
 
122
 
123
- return template
124
-
125
- def update_template(self, template_id: str, section: str, content: str):
126
- """Update template content in real-time"""
127
- if template_id in self.active_templates:
128
- try:
129
- section_path = section.split('.')
130
- target = self.active_templates[template_id]
131
- for key in section_path[:-1]:
132
- target = target[key]
133
- target[section_path[-1]] = content
134
- except Exception as e:
135
- logger.error(f"Error updating template: {e}")
136
 
137
- class StyleManager:
138
- """Manage VC's personal styles and preferences"""
139
 
140
- def __init__(self):
141
- self.styles = {}
142
 
143
- def create_style(self, vc_name: str, preferences: Dict[str, Any]) -> VCStyle:
144
- """Create or update VC style"""
145
- style = VCStyle(
146
- name=vc_name,
147
- note_format=preferences.get('note_format', {}),
148
- key_interests=preferences.get('key_interests', []),
149
- custom_sections=preferences.get('custom_sections', []),
150
- insight_preferences=preferences.get('insight_preferences', {})
151
- )
152
- self.styles[vc_name] = style
153
- return style
154
-
155
- def get_style(self, vc_name: str) -> Optional[VCStyle]:
156
- """Retrieve VC's style"""
157
- return self.styles.get(vc_name)
158
 
159
- class LiveCallManager:
160
- """Manage live call processing and analysis"""
161
-
162
- def __init__(self, processor: RealTimeProcessor, template_manager: DynamicTemplate):
163
- self.processor = processor
164
- self.template_manager = template_manager
165
- self.active_calls = {}
166
-
167
- def start_call(self, meeting_id: str, vc_style: VCStyle) -> str:
168
- """Initialize a new call session"""
169
- self.active_calls[meeting_id] = {
170
- 'context': LiveCallContext(
171
- meeting_id=meeting_id,
172
- participants=[],
173
- topics=[],
174
- key_points=[],
175
- questions_asked=[],
176
- action_items=[]
177
- ),
178
- 'template': self.template_manager.create_custom_template(vc_style),
179
- 'style': vc_style
180
- }
181
- return meeting_id
182
-
183
- def process_call_segment(self, meeting_id: str, audio_chunk) -> Dict[str, Any]:
184
- """Process a segment of the call"""
185
- if meeting_id not in self.active_calls:
186
- raise ValueError("Invalid meeting ID")
187
-
188
- # Process audio
189
- transcript = self.processor.process_audio_chunk(audio_chunk)
190
- if not transcript:
191
- return {}
192
-
193
- # Generate insights
194
- call_data = self.active_calls[meeting_id]
195
- insights = self.processor.generate_live_insights(
196
- transcript,
197
- call_data['style']
198
- )
199
 
200
- # Update template
201
- self.update_call_notes(meeting_id, transcript, insights)
202
 
203
- return {
204
- 'transcript': transcript,
205
- 'insights': insights,
206
- 'template': call_data['template']
207
- }
208
-
209
- def update_call_notes(self, meeting_id: str, transcript: str, insights: Dict[str, Any]):
210
- """Update call notes with new information"""
211
- call_data = self.active_calls[meeting_id]
212
- template = call_data['template']
213
-
214
- # Update relevant sections based on insights
215
- for key, value in insights.items():
216
- if key in template:
217
- if isinstance(template[key], dict):
218
- template[key].update(value)
219
- else:
220
- template[key] = value
221
 
222
- def main():
223
- st.set_page_config(page_title="VC Call Assistant", layout="wide")
224
-
225
- # Initialize managers
226
- style_manager = StyleManager()
227
- template_manager = DynamicTemplate()
 
 
 
 
 
 
 
 
 
 
228
 
229
- # Sidebar for VC profile
230
- with st.sidebar:
231
- st.header("VC Profile")
232
- vc_name = st.text_input("VC Name")
233
-
234
- # Style preferences
235
- st.subheader("Style Preferences")
236
- note_format = st.multiselect(
237
- "Preferred Note Format",
238
- ["Bullet Points", "Paragraphs", "Q&A Format"],
239
- default=["Bullet Points"]
240
  )
241
-
242
- key_interests = st.multiselect(
243
- "Key Interest Areas",
244
- ["Technical", "Financial", "Market", "Team", "Product"],
245
- default=["Financial", "Market"]
246
- )
247
-
248
- custom_sections = st.multiselect(
249
- "Custom Sections",
250
- ["Competition Deep Dive", "Technical Assessment", "Team Background"],
251
- default=[]
252
- )
253
-
254
- # Main content area
255
- st.title("VC Call Assistant")
256
 
257
- # Create tabs for different modes
258
- tab1, tab2 = st.tabs(["Live Call", "Batch Processing"])
 
 
 
 
 
259
 
260
- with tab1:
261
- st.header("Live Call Analysis")
 
 
 
 
 
 
262
 
263
- # Initialize call managers if VC profile exists
264
- if vc_name:
265
- # Create/update VC style
266
- vc_style = style_manager.create_style(
267
- vc_name,
268
- {
269
- 'note_format': note_format,
270
- 'key_interests': key_interests,
271
- 'custom_sections': custom_sections,
272
- 'insight_preferences': {'technical': 0.8, 'financial': 0.9}
273
- }
 
 
 
274
  )
275
 
276
- # Audio input (simulated for Spaces)
277
- uploaded_file = st.file_uploader(
278
- "Upload audio segment",
279
- type=['wav', 'mp3', 'm4a']
280
  )
 
 
 
 
 
 
 
 
 
 
 
 
281
 
282
- if uploaded_file:
283
- # Process audio in chunks (simulated)
284
- with st.spinner("Processing audio..."):
285
- # Initialize processors
286
- whisper_model = whisper.load_model("base")
287
- llm = load_llm("Mixtral-8x7B-Instruct") # Function from previous code
288
- processor = RealTimeProcessor(whisper_model, llm)
289
- live_call_manager = LiveCallManager(processor, template_manager)
290
-
291
- # Start call session
292
- meeting_id = live_call_manager.start_call(
293
- str(datetime.now()),
294
- vc_style
295
- )
296
-
297
- # Process call
298
- results = live_call_manager.process_call_segment(
299
- meeting_id,
300
- uploaded_file
301
- )
302
-
303
  # Display results
304
  col1, col2 = st.columns(2)
 
305
  with col1:
306
- st.subheader("Live Transcript")
307
- st.write(results.get('transcript', ''))
308
-
309
- st.subheader("Real-time Insights")
310
- st.write(results.get('insights', {}))
311
 
312
  with col2:
313
- st.subheader("Smart Notes")
314
- st.write(results.get('template', {}))
315
- else:
316
- st.warning("Please enter VC profile information in the sidebar")
317
-
318
- with tab2:
319
- st.header("Batch Processing")
320
- # Previous batch processing code here
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
 
322
  if __name__ == "__main__":
323
  main()
 
17
  import json
18
  import numpy as np
19
  from dataclasses import dataclass, asdict
20
+ from queue import Queue
21
  import threading
22
  from collections import defaultdict
23
 
 
25
  logging.basicConfig(level=logging.INFO)
26
  logger = logging.getLogger(__name__)
27
 
28
+ # Constants for memory optimization
29
+ CHUNK_SIZE = 30 # seconds
30
+ MAX_AUDIO_LENGTH = 600 # seconds (10 minutes)
31
+ BATCH_SIZE = 8
32
+
33
+ # Model configurations with memory optimization
34
+ MODEL_CONFIGS = {
35
+ "Mistral-7B-Instruct": {
36
+ "path": "mistralai/Mistral-7B-Instruct-v0.1",
37
+ "description": "Efficient model for real-time analysis",
38
+ "memory_required": "16GB"
39
+ }
40
+ }
41
+
42
  @dataclass
43
  class VCStyle:
44
  """Store VC's personal style preferences"""
 
58
  questions_asked: List[str]
59
  action_items: List[str]
60
 
61
+ class ModelManager:
62
+ """Handles model loading and resource management"""
63
 
64
+ @staticmethod
65
+ @st.cache_resource
66
+ def load_whisper():
 
 
 
 
 
 
67
  try:
68
+ return whisper.load_model("base")
 
 
69
  except Exception as e:
70
+ logger.error(f"Failed to load Whisper model: {e}")
71
+ st.error("Failed to load speech recognition model. Please refresh the page.")
72
  return None
73
 
74
+ @staticmethod
75
+ @st.cache_resource
76
+ def load_llm(model_name: str):
 
 
 
 
 
 
 
 
 
 
77
  try:
78
+ config = MODEL_CONFIGS[model_name]
79
+
80
+ # Optimized quantization config
81
+ bnb_config = BitsAndBytesConfig(
82
+ load_in_4bit=True,
83
+ bnb_4bit_quant_type="nf4",
84
+ bnb_4bit_compute_dtype=torch.float16,
85
+ bnb_4bit_use_double_quant=True,
86
+ )
87
+
88
+ tokenizer = AutoTokenizer.from_pretrained(
89
+ config["path"],
90
+ token=st.secrets.get("HF_TOKEN"),
91
+ trust_remote_code=True
92
+ )
93
+
94
+ model = AutoModelForCausalLM.from_pretrained(
95
+ config["path"],
96
+ token=st.secrets.get("HF_TOKEN"),
97
+ quantization_config=bnb_config,
98
+ device_map="auto",
99
+ torch_dtype=torch.float16,
100
+ low_cpu_mem_usage=True
101
+ )
102
+
103
+ pipe = pipeline(
104
+ "text-generation",
105
+ model=model,
106
+ tokenizer=tokenizer,
107
+ max_new_tokens=512, # Reduced for memory
108
+ temperature=0.7,
109
+ top_p=0.95,
110
+ repetition_penalty=1.15,
111
+ batch_size=BATCH_SIZE
112
+ )
113
+
114
+ return pipe
115
+
116
  except Exception as e:
117
+ logger.error(f"Failed to load LLM {model_name}: {e}")
118
+ st.error("Failed to load language model. Please try again.")
119
+ return None
 
 
 
 
 
120
 
121
+ class AudioProcessor:
122
+ """Handles audio processing with memory optimization"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
 
124
+ def __init__(self, model):
125
+ self.model = model
126
+ self.chunk_queue = Queue()
127
 
128
+ def process_audio_chunk(self, audio_chunk) -> Optional[str]:
129
+ try:
130
+ # Clear GPU memory before processing
131
+ if torch.cuda.is_available():
132
+ torch.cuda.empty_cache()
133
+
134
+ result = self.model.transcribe(
135
+ audio_chunk,
136
+ language="en",
137
+ task="transcribe",
138
+ fp16=True # Use half precision
139
+ )
140
+ return result["text"]
141
 
142
+ except Exception as e:
143
+ logger.error(f"Error processing audio chunk: {e}")
144
+ return None
145
+ finally:
146
+ # Cleanup
147
+ gc.collect()
148
+ if torch.cuda.is_available():
149
+ torch.cuda.empty_cache()
 
 
 
 
 
150
 
151
+ class ContentAnalyzer:
152
+ """Handles text analysis with optimized prompts"""
153
 
154
+ def __init__(self, generator):
155
+ self.generator = generator
156
 
157
+ def analyze_text(self, text: str, vc_style: VCStyle) -> Optional[Dict[str, Any]]:
158
+ try:
159
+ prompt = self._create_analysis_prompt(text, vc_style)
160
+ response = self._generate_response(prompt, max_length=512)
161
+ return self._parse_response(response)
162
+ except Exception as e:
163
+ logger.error(f"Analysis error: {e}")
164
+ return None
 
 
 
 
 
 
 
165
 
166
+ def _create_analysis_prompt(self, text: str, vc_style: VCStyle) -> str:
167
+ return f"""Analyze this startup pitch focusing on {', '.join(vc_style.key_interests)}:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
168
 
169
+ {text}
 
170
 
171
+ Provide structured insights for:
172
+ 1. Key Points
173
+ 2. Metrics
174
+ 3. Risks
175
+ 4. Questions"""
 
 
 
 
 
 
 
 
 
 
 
 
 
176
 
177
+ def _generate_response(self, prompt: str, max_length: int) -> str:
178
+ try:
179
+ response = self.generator(
180
+ prompt,
181
+ max_new_tokens=max_length,
182
+ temperature=0.7,
183
+ top_p=0.95,
184
+ repetition_penalty=1.15
185
+ )
186
+ return response[0]['generated_text']
187
+ except Exception as e:
188
+ logger.error(f"Generation error: {e}")
189
+ return ""
190
+
191
+ class UIManager:
192
+ """Manages Streamlit UI with performance optimization"""
193
 
194
+ @staticmethod
195
+ def setup_page():
196
+ st.set_page_config(
197
+ page_title="VC Call Assistant",
198
+ page_icon="πŸŽ™οΈ",
199
+ layout="wide",
200
+ initial_sidebar_state="expanded"
 
 
 
 
201
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
202
 
203
+ @staticmethod
204
+ def show_file_uploader() -> Optional[Any]:
205
+ return st.file_uploader(
206
+ "Upload Audio (Max 10 minutes)",
207
+ type=['wav', 'mp3', 'm4a'],
208
+ help="Supports WAV, MP3, M4A formats. Maximum duration: 10 minutes."
209
+ )
210
 
211
+ @staticmethod
212
+ def show_progress(text: str) -> Any:
213
+ return st.progress(0, text=text)
214
+
215
+ def main():
216
+ try:
217
+ # Initialize UI
218
+ UIManager.setup_page()
219
 
220
+ # Sidebar
221
+ with st.sidebar:
222
+ st.title("VC Assistant Settings")
223
+ model_name = "Mistral-7B-Instruct" # Fixed for stability
224
+
225
+ st.info(f"""Using {model_name}
226
+ Memory Usage: {MODEL_CONFIGS[model_name]['memory_required']}
227
+ Description: {MODEL_CONFIGS[model_name]['description']}""")
228
+
229
+ # VC Profile
230
+ vc_name = st.text_input("Your Name")
231
+ note_style = st.selectbox(
232
+ "Note Style",
233
+ ["Bullet Points", "Paragraphs", "Q&A"]
234
  )
235
 
236
+ interests = st.multiselect(
237
+ "Focus Areas",
238
+ ["Product", "Market", "Team", "Financials", "Technology"],
239
+ default=["Product", "Market"]
240
  )
241
+
242
+ # Main content
243
+ st.title("πŸŽ™οΈ VC Call Assistant")
244
+
245
+ if not vc_name:
246
+ st.warning("Please enter your name in the sidebar.")
247
+ return
248
+
249
+ # Initialize processors
250
+ with st.spinner("Loading models..."):
251
+ whisper_model = ModelManager.load_whisper()
252
+ llm = ModelManager.load_llm(model_name)
253
 
254
+ if not whisper_model or not llm:
255
+ st.error("Failed to initialize models. Please refresh the page.")
256
+ return
257
+
258
+ audio_processor = AudioProcessor(whisper_model)
259
+ analyzer = ContentAnalyzer(llm)
260
+
261
+ # File upload
262
+ audio_file = UIManager.show_file_uploader()
263
+
264
+ if audio_file:
265
+ # Process audio
266
+ with st.spinner("Processing audio..."):
267
+ transcription = audio_processor.process_audio_chunk(audio_file)
268
+
269
+ if transcription:
 
 
 
 
 
270
  # Display results
271
  col1, col2 = st.columns(2)
272
+
273
  with col1:
274
+ st.subheader("πŸ“ Transcript")
275
+ st.write(transcription)
 
 
 
276
 
277
  with col2:
278
+ st.subheader("πŸ” Analysis")
279
+ vc_style = VCStyle(
280
+ name=vc_name,
281
+ note_format={"style": note_style},
282
+ key_interests=interests,
283
+ custom_sections=[],
284
+ insight_preferences={}
285
+ )
286
+
287
+ analysis = analyzer.analyze_text(transcription, vc_style)
288
+ if analysis:
289
+ st.write(analysis)
290
+
291
+ # Export option
292
+ st.download_button(
293
+ "πŸ“₯ Export Analysis",
294
+ data=json.dumps({
295
+ "timestamp": datetime.now().isoformat(),
296
+ "transcription": transcription,
297
+ "analysis": analysis
298
+ }, indent=2),
299
+ file_name=f"vc_analysis_{datetime.now():%Y%m%d_%H%M%S}.json",
300
+ mime="application/json"
301
+ )
302
+
303
+ except Exception as e:
304
+ logger.error(f"Application error: {e}")
305
+ st.error("An unexpected error occurred. Please refresh the page.")
306
+
307
+ finally:
308
+ # Cleanup
309
+ gc.collect()
310
+ if torch.cuda.is_available():
311
+ torch.cuda.empty_cache()
312
 
313
  if __name__ == "__main__":
314
  main()
requirements.txt CHANGED
@@ -11,4 +11,3 @@ sentencepiece==0.1.99
11
  huggingface-hub==0.19.4
12
  python-dotenv==1.0.0
13
  dataclasses-json==0.5.7
14
- queue==1.0.2
 
11
  huggingface-hub==0.19.4
12
  python-dotenv==1.0.0
13
  dataclasses-json==0.5.7