2025-09-30 04:14:19 +00:00
from fastapi import APIRouter , UploadFile , File , Form , Depends , BackgroundTasks
from app . models . models import Meeting , TranscriptSegment , TranscriptionTaskStatus , CreateMeetingRequest , UpdateMeetingRequest , SpeakerTagUpdateRequest , BatchSpeakerTagUpdateRequest , BatchTranscriptUpdateRequest , Tag
2025-08-05 01:46:40 +00:00
from app . core . database import get_db_connection
2025-09-25 03:48:02 +00:00
from app . core . config import BASE_DIR , AUDIO_DIR , MARKDOWN_DIR , ALLOWED_EXTENSIONS , ALLOWED_IMAGE_EXTENSIONS
import app . core . config as config_module
2025-08-26 13:57:16 +00:00
from app . services . llm_service import LLMService
2025-08-28 08:02:59 +00:00
from app . services . async_transcription_service import AsyncTranscriptionService
2025-09-09 03:35:56 +00:00
from app . services . async_llm_service import async_llm_service
2025-09-25 03:48:02 +00:00
from app . core . auth import get_current_user
2025-09-26 07:47:37 +00:00
from app . core . response import create_api_response
2025-09-19 08:51:07 +00:00
from typing import List , Optional
2025-09-09 03:35:56 +00:00
from datetime import datetime
2025-08-26 13:57:16 +00:00
from pydantic import BaseModel
2025-08-05 01:46:40 +00:00
import os
import uuid
import shutil
router = APIRouter ( )
2025-08-26 13:57:16 +00:00
llm_service = LLMService ( )
2025-08-28 08:02:59 +00:00
transcription_service = AsyncTranscriptionService ( )
2025-08-26 13:57:16 +00:00
class GenerateSummaryRequest ( BaseModel ) :
user_prompt : Optional [ str ] = " "
2025-09-19 08:51:07 +00:00
def _process_tags ( cursor , tag_string : Optional [ str ] ) - > List [ Tag ] :
if not tag_string :
return [ ]
tag_names = [ name . strip ( ) for name in tag_string . split ( ' , ' ) if name . strip ( ) ]
if not tag_names :
return [ ]
insert_ignore_query = " INSERT IGNORE INTO tags (name) VALUES ( %s ) "
cursor . executemany ( insert_ignore_query , [ ( name , ) for name in tag_names ] )
format_strings = ' , ' . join ( [ ' %s ' ] * len ( tag_names ) )
cursor . execute ( f " SELECT id, name, color FROM tags WHERE name IN ( { format_strings } ) " , tuple ( tag_names ) )
tags_data = cursor . fetchall ( )
return [ Tag ( * * tag ) for tag in tags_data ]
2025-09-26 07:47:37 +00:00
@router.get ( " /meetings " )
2025-08-29 08:37:29 +00:00
def get_meetings ( current_user : dict = Depends ( get_current_user ) , user_id : Optional [ int ] = None ) :
2025-08-05 01:46:40 +00:00
with get_db_connection ( ) as connection :
cursor = connection . cursor ( dictionary = True )
base_query = '''
2025-09-26 07:47:37 +00:00
SELECT m . meeting_id , m . title , m . meeting_time , m . summary , m . created_at , m . tags ,
2025-10-15 06:31:25 +00:00
m . user_id as creator_id , u . caption as creator_username , af . file_path as audio_file_path
FROM meetings m JOIN users u ON m . user_id = u . user_id LEFT JOIN audio_files af ON m . meeting_id = af . meeting_id
2025-08-05 01:46:40 +00:00
'''
if user_id :
2025-09-26 07:47:37 +00:00
query = f ' { base_query } LEFT JOIN attendees a ON m.meeting_id = a.meeting_id WHERE m.user_id = %s OR a.user_id = %s GROUP BY m.meeting_id ORDER BY m.meeting_time DESC, m.created_at DESC '
2025-08-05 01:46:40 +00:00
cursor . execute ( query , ( user_id , user_id ) )
else :
query = f " { base_query } ORDER BY m.meeting_time DESC, m.created_at DESC "
cursor . execute ( query )
meetings = cursor . fetchall ( )
meeting_list = [ ]
for meeting in meetings :
2025-09-26 07:47:37 +00:00
attendees_query = ' SELECT u.user_id, u.caption FROM attendees a JOIN users u ON a.user_id = u.user_id WHERE a.meeting_id = %s '
2025-08-05 01:46:40 +00:00
cursor . execute ( attendees_query , ( meeting [ ' meeting_id ' ] , ) )
attendees_data = cursor . fetchall ( )
attendees = [ { ' user_id ' : row [ ' user_id ' ] , ' caption ' : row [ ' caption ' ] } for row in attendees_data ]
2025-09-19 08:51:07 +00:00
tags = _process_tags ( cursor , meeting . get ( ' tags ' ) )
2025-08-05 01:46:40 +00:00
meeting_list . append ( Meeting (
2025-09-26 07:47:37 +00:00
meeting_id = meeting [ ' meeting_id ' ] , title = meeting [ ' title ' ] , meeting_time = meeting [ ' meeting_time ' ] ,
2025-10-15 06:31:25 +00:00
summary = meeting [ ' summary ' ] , created_at = meeting [ ' created_at ' ] , audio_file_path = meeting [ ' audio_file_path ' ] ,
attendees = attendees , creator_id = meeting [ ' creator_id ' ] , creator_username = meeting [ ' creator_username ' ] , tags = tags
2025-08-05 01:46:40 +00:00
) )
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 200 " , message = " 获取会议列表成功 " , data = meeting_list )
2025-08-05 01:46:40 +00:00
2025-09-26 07:47:37 +00:00
@router.get ( " /meetings/ {meeting_id} " )
2025-08-29 08:37:29 +00:00
def get_meeting_details ( meeting_id : int , current_user : dict = Depends ( get_current_user ) ) :
2025-08-05 01:46:40 +00:00
with get_db_connection ( ) as connection :
cursor = connection . cursor ( dictionary = True )
query = '''
2025-09-26 07:47:37 +00:00
SELECT m . meeting_id , m . title , m . meeting_time , m . summary , m . created_at , m . tags ,
m . user_id as creator_id , u . caption as creator_username , af . file_path as audio_file_path
FROM meetings m JOIN users u ON m . user_id = u . user_id LEFT JOIN audio_files af ON m . meeting_id = af . meeting_id
2025-08-05 01:46:40 +00:00
WHERE m . meeting_id = % s
'''
cursor . execute ( query , ( meeting_id , ) )
meeting = cursor . fetchone ( )
if not meeting :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 404 " , message = " Meeting not found " )
attendees_query = ' SELECT u.user_id, u.caption FROM attendees a JOIN users u ON a.user_id = u.user_id WHERE a.meeting_id = %s '
2025-08-05 01:46:40 +00:00
cursor . execute ( attendees_query , ( meeting [ ' meeting_id ' ] , ) )
attendees_data = cursor . fetchall ( )
attendees = [ { ' user_id ' : row [ ' user_id ' ] , ' caption ' : row [ ' caption ' ] } for row in attendees_data ]
2025-09-19 08:51:07 +00:00
tags = _process_tags ( cursor , meeting . get ( ' tags ' ) )
2025-08-28 08:02:59 +00:00
cursor . close ( )
2025-08-05 01:46:40 +00:00
meeting_data = Meeting (
2025-09-26 07:47:37 +00:00
meeting_id = meeting [ ' meeting_id ' ] , title = meeting [ ' title ' ] , meeting_time = meeting [ ' meeting_time ' ] ,
summary = meeting [ ' summary ' ] , created_at = meeting [ ' created_at ' ] , attendees = attendees ,
creator_id = meeting [ ' creator_id ' ] , creator_username = meeting [ ' creator_username ' ] , tags = tags
2025-08-05 01:46:40 +00:00
)
if meeting [ ' audio_file_path ' ] :
meeting_data . audio_file_path = meeting [ ' audio_file_path ' ]
2025-08-28 08:02:59 +00:00
try :
transcription_status_data = transcription_service . get_meeting_transcription_status ( meeting_id )
if transcription_status_data :
meeting_data . transcription_status = TranscriptionTaskStatus ( * * transcription_status_data )
except Exception as e :
print ( f " Warning: Failed to get transcription status for meeting { meeting_id } : { e } " )
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 200 " , message = " 获取会议详情成功 " , data = meeting_data )
2025-08-05 01:46:40 +00:00
2025-09-26 07:47:37 +00:00
@router.get ( " /meetings/ {meeting_id} /transcript " )
2025-08-29 08:37:29 +00:00
def get_meeting_transcript ( meeting_id : int , current_user : dict = Depends ( get_current_user ) ) :
2025-08-05 01:46:40 +00:00
with get_db_connection ( ) as connection :
cursor = connection . cursor ( dictionary = True )
2025-09-26 07:47:37 +00:00
cursor . execute ( " SELECT meeting_id FROM meetings WHERE meeting_id = %s " , ( meeting_id , ) )
2025-08-05 01:46:40 +00:00
if not cursor . fetchone ( ) :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 404 " , message = " Meeting not found " )
2025-08-05 01:46:40 +00:00
transcript_query = '''
2025-08-25 08:10:29 +00:00
SELECT segment_id , meeting_id , speaker_id , speaker_tag , start_time_ms , end_time_ms , text_content
2025-09-26 07:47:37 +00:00
FROM transcript_segments WHERE meeting_id = % s ORDER BY start_time_ms ASC
2025-08-05 01:46:40 +00:00
'''
cursor . execute ( transcript_query , ( meeting_id , ) )
segments = cursor . fetchall ( )
2025-09-26 07:47:37 +00:00
transcript_segments = [ TranscriptSegment (
segment_id = s [ ' segment_id ' ] , meeting_id = s [ ' meeting_id ' ] , speaker_id = s [ ' speaker_id ' ] ,
speaker_tag = s [ ' speaker_tag ' ] if s [ ' speaker_tag ' ] else f " 发言人 { s [ ' speaker_id ' ] } " ,
start_time_ms = s [ ' start_time_ms ' ] , end_time_ms = s [ ' end_time_ms ' ] , text_content = s [ ' text_content ' ]
) for s in segments ]
return create_api_response ( code = " 200 " , message = " 获取转录内容成功 " , data = transcript_segments )
2025-08-05 01:46:40 +00:00
@router.post ( " /meetings " )
2025-08-29 08:37:29 +00:00
def create_meeting ( meeting_request : CreateMeetingRequest , current_user : dict = Depends ( get_current_user ) ) :
2025-08-05 01:46:40 +00:00
with get_db_connection ( ) as connection :
cursor = connection . cursor ( dictionary = True )
2025-09-19 08:51:07 +00:00
if meeting_request . tags :
tag_names = [ name . strip ( ) for name in meeting_request . tags . split ( ' , ' ) if name . strip ( ) ]
if tag_names :
2025-09-26 07:47:37 +00:00
cursor . executemany ( " INSERT IGNORE INTO tags (name) VALUES ( %s ) " , [ ( name , ) for name in tag_names ] )
meeting_query = ' INSERT INTO meetings (user_id, title, meeting_time, summary, tags, created_at) VALUES ( %s , %s , %s , %s , %s , %s ) '
cursor . execute ( meeting_query , ( meeting_request . user_id , meeting_request . title , meeting_request . meeting_time , None , meeting_request . tags , datetime . now ( ) . isoformat ( ) ) )
2025-08-05 01:46:40 +00:00
meeting_id = cursor . lastrowid
for attendee_id in meeting_request . attendee_ids :
2025-09-26 07:47:37 +00:00
cursor . execute ( ' INSERT IGNORE INTO attendees (meeting_id, user_id) VALUES ( %s , %s ) ' , ( meeting_id , attendee_id ) )
2025-08-05 01:46:40 +00:00
connection . commit ( )
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 200 " , message = " Meeting created successfully " , data = { " meeting_id " : meeting_id } )
2025-08-05 01:46:40 +00:00
@router.put ( " /meetings/ {meeting_id} " )
2025-08-29 08:37:29 +00:00
def update_meeting ( meeting_id : int , meeting_request : UpdateMeetingRequest , current_user : dict = Depends ( get_current_user ) ) :
2025-08-05 01:46:40 +00:00
with get_db_connection ( ) as connection :
cursor = connection . cursor ( dictionary = True )
2025-08-29 08:37:29 +00:00
cursor . execute ( " SELECT user_id FROM meetings WHERE meeting_id = %s " , ( meeting_id , ) )
meeting = cursor . fetchone ( )
if not meeting :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 404 " , message = " Meeting not found " )
2025-08-29 08:37:29 +00:00
if meeting [ ' user_id ' ] != current_user [ ' user_id ' ] :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 403 " , message = " Permission denied " )
2025-09-19 08:51:07 +00:00
if meeting_request . tags :
tag_names = [ name . strip ( ) for name in meeting_request . tags . split ( ' , ' ) if name . strip ( ) ]
if tag_names :
2025-09-26 07:47:37 +00:00
cursor . executemany ( " INSERT IGNORE INTO tags (name) VALUES ( %s ) " , [ ( name , ) for name in tag_names ] )
update_query = ' UPDATE meetings SET title = %s , meeting_time = %s , summary = %s , tags = %s WHERE meeting_id = %s '
cursor . execute ( update_query , ( meeting_request . title , meeting_request . meeting_time , meeting_request . summary , meeting_request . tags , meeting_id ) )
2025-08-05 01:46:40 +00:00
cursor . execute ( " DELETE FROM attendees WHERE meeting_id = %s " , ( meeting_id , ) )
for attendee_id in meeting_request . attendee_ids :
2025-09-26 07:47:37 +00:00
cursor . execute ( ' INSERT INTO attendees (meeting_id, user_id) VALUES ( %s , %s ) ' , ( meeting_id , attendee_id ) )
2025-08-05 01:46:40 +00:00
connection . commit ( )
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 200 " , message = " Meeting updated successfully " )
2025-08-05 01:46:40 +00:00
@router.delete ( " /meetings/ {meeting_id} " )
2025-08-29 08:37:29 +00:00
def delete_meeting ( meeting_id : int , current_user : dict = Depends ( get_current_user ) ) :
2025-08-05 01:46:40 +00:00
with get_db_connection ( ) as connection :
cursor = connection . cursor ( dictionary = True )
2025-08-29 08:37:29 +00:00
cursor . execute ( " SELECT user_id FROM meetings WHERE meeting_id = %s " , ( meeting_id , ) )
meeting = cursor . fetchone ( )
if not meeting :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 404 " , message = " Meeting not found " )
2025-08-29 08:37:29 +00:00
if meeting [ ' user_id ' ] != current_user [ ' user_id ' ] :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 403 " , message = " Permission denied " )
2025-08-05 01:46:40 +00:00
cursor . execute ( " DELETE FROM transcript_segments WHERE meeting_id = %s " , ( meeting_id , ) )
cursor . execute ( " DELETE FROM audio_files WHERE meeting_id = %s " , ( meeting_id , ) )
cursor . execute ( " DELETE FROM attachments WHERE meeting_id = %s " , ( meeting_id , ) )
cursor . execute ( " DELETE FROM attendees WHERE meeting_id = %s " , ( meeting_id , ) )
cursor . execute ( " DELETE FROM meetings WHERE meeting_id = %s " , ( meeting_id , ) )
connection . commit ( )
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 200 " , message = " Meeting deleted successfully " )
2025-08-05 01:46:40 +00:00
2025-09-26 07:47:37 +00:00
@router.get ( " /meetings/ {meeting_id} /edit " )
2025-08-29 08:37:29 +00:00
def get_meeting_for_edit ( meeting_id : int , current_user : dict = Depends ( get_current_user ) ) :
2025-08-05 01:46:40 +00:00
with get_db_connection ( ) as connection :
cursor = connection . cursor ( dictionary = True )
query = '''
2025-09-26 07:47:37 +00:00
SELECT m . meeting_id , m . title , m . meeting_time , m . summary , m . created_at , m . tags ,
m . user_id as creator_id , u . caption as creator_username , af . file_path as audio_file_path
FROM meetings m JOIN users u ON m . user_id = u . user_id LEFT JOIN audio_files af ON m . meeting_id = af . meeting_id
2025-08-05 01:46:40 +00:00
WHERE m . meeting_id = % s
'''
cursor . execute ( query , ( meeting_id , ) )
meeting = cursor . fetchone ( )
if not meeting :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 404 " , message = " Meeting not found " )
attendees_query = ' SELECT u.user_id, u.caption FROM attendees a JOIN users u ON a.user_id = u.user_id WHERE a.meeting_id = %s '
cursor . execute ( attendees_query , ( meeting [ ' meeting_id ' ] , ) )
2025-08-05 01:46:40 +00:00
attendees_data = cursor . fetchall ( )
attendees = [ { ' user_id ' : row [ ' user_id ' ] , ' caption ' : row [ ' caption ' ] } for row in attendees_data ]
2025-09-19 08:51:07 +00:00
tags = _process_tags ( cursor , meeting . get ( ' tags ' ) )
2025-08-28 08:02:59 +00:00
cursor . close ( )
2025-08-05 01:46:40 +00:00
meeting_data = Meeting (
2025-09-26 07:47:37 +00:00
meeting_id = meeting [ ' meeting_id ' ] , title = meeting [ ' title ' ] , meeting_time = meeting [ ' meeting_time ' ] ,
summary = meeting [ ' summary ' ] , created_at = meeting [ ' created_at ' ] , attendees = attendees ,
creator_id = meeting [ ' creator_id ' ] , creator_username = meeting [ ' creator_username ' ] , tags = tags
2025-08-05 01:46:40 +00:00
)
2025-08-26 13:57:16 +00:00
if meeting . get ( ' audio_file_path ' ) :
2025-08-05 01:46:40 +00:00
meeting_data . audio_file_path = meeting [ ' audio_file_path ' ]
2025-08-28 08:02:59 +00:00
try :
transcription_status_data = transcription_service . get_meeting_transcription_status ( meeting_id )
if transcription_status_data :
meeting_data . transcription_status = TranscriptionTaskStatus ( * * transcription_status_data )
except Exception as e :
print ( f " Warning: Failed to get transcription status for meeting { meeting_id } : { e } " )
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 200 " , message = " 获取会议编辑信息成功 " , data = meeting_data )
2025-08-05 01:46:40 +00:00
@router.post ( " /meetings/upload-audio " )
2025-09-26 07:47:37 +00:00
async def upload_audio ( audio_file : UploadFile = File ( . . . ) , meeting_id : int = Form ( . . . ) , force_replace : str = Form ( " false " ) , current_user : dict = Depends ( get_current_user ) ) :
2025-08-28 08:02:59 +00:00
force_replace_bool = force_replace . lower ( ) in ( " true " , " 1 " , " yes " )
2025-08-05 01:46:40 +00:00
file_extension = os . path . splitext ( audio_file . filename ) [ 1 ] . lower ( )
if file_extension not in ALLOWED_EXTENSIONS :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 400 " , message = f " Unsupported file type. Allowed types: { ' , ' . join ( ALLOWED_EXTENSIONS ) } " )
2025-09-25 03:48:02 +00:00
max_file_size = getattr ( config_module , ' MAX_FILE_SIZE ' , 100 * 1024 * 1024 )
if audio_file . size > max_file_size :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 400 " , message = f " File size exceeds { max_file_size / / ( 1024 * 1024 ) } MB limit " )
2025-08-28 08:02:59 +00:00
try :
with get_db_connection ( ) as connection :
cursor = connection . cursor ( dictionary = True )
2025-08-29 08:37:29 +00:00
cursor . execute ( " SELECT user_id FROM meetings WHERE meeting_id = %s " , ( meeting_id , ) )
meeting = cursor . fetchone ( )
if not meeting :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 404 " , message = " Meeting not found " )
2025-08-29 08:37:29 +00:00
if meeting [ ' user_id ' ] != current_user [ ' user_id ' ] :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 403 " , message = " Permission denied " )
cursor . execute ( " SELECT file_name, file_path, upload_time FROM audio_files WHERE meeting_id = %s " , ( meeting_id , ) )
2025-08-28 08:02:59 +00:00
existing_info = cursor . fetchone ( )
2025-09-26 07:47:37 +00:00
has_transcription = False
2025-08-28 08:02:59 +00:00
if existing_info :
2025-09-26 07:47:37 +00:00
cursor . execute ( " SELECT COUNT(*) as segment_count FROM transcript_segments WHERE meeting_id = %s " , ( meeting_id , ) )
has_transcription = cursor . fetchone ( ) [ ' segment_count ' ] > 0
2025-08-28 08:02:59 +00:00
cursor . close ( )
except Exception as e :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 500 " , message = f " Failed to check existing files: { str ( e ) } " )
2025-08-28 08:02:59 +00:00
if existing_info and has_transcription and not force_replace_bool :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 300 " , message = " 该会议已有音频文件和转录记录,重新上传将删除现有的转录内容 " , data = {
2025-08-28 08:02:59 +00:00
" requires_confirmation " : True ,
" existing_file " : {
" file_name " : existing_info [ ' file_name ' ] ,
" upload_time " : existing_info [ ' upload_time ' ] . isoformat ( ) if existing_info [ ' upload_time ' ] else None
2025-09-26 07:47:37 +00:00
}
} )
2025-08-25 03:31:24 +00:00
meeting_dir = AUDIO_DIR / str ( meeting_id )
meeting_dir . mkdir ( exist_ok = True )
2025-08-05 01:46:40 +00:00
unique_filename = f " { uuid . uuid4 ( ) } { file_extension } "
2025-08-25 03:31:24 +00:00
absolute_path = meeting_dir / unique_filename
relative_path = absolute_path . relative_to ( BASE_DIR )
2025-08-05 01:46:40 +00:00
try :
2025-08-25 03:31:24 +00:00
with open ( absolute_path , " wb " ) as buffer :
2025-08-05 01:46:40 +00:00
shutil . copyfileobj ( audio_file . file , buffer )
except Exception as e :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 500 " , message = f " Failed to save file: { str ( e ) } " )
2025-08-28 08:02:59 +00:00
task_id = None
replaced_existing = existing_info is not None
try :
with get_db_connection ( ) as connection :
cursor = connection . cursor ( dictionary = True )
if replaced_existing and force_replace_bool :
cursor . execute ( " DELETE FROM transcript_segments WHERE meeting_id = %s " , ( meeting_id , ) )
cursor . execute ( " DELETE FROM transcript_tasks WHERE meeting_id = %s " , ( meeting_id , ) )
if existing_info and existing_info [ ' file_path ' ] :
old_file_path = BASE_DIR / existing_info [ ' file_path ' ] . lstrip ( ' / ' )
if old_file_path . exists ( ) :
try :
os . remove ( old_file_path )
except Exception as e :
print ( f " Warning: Failed to delete old file { old_file_path } : { e } " )
if replaced_existing :
2025-09-26 07:47:37 +00:00
cursor . execute ( ' UPDATE audio_files SET file_name = %s , file_path = %s , file_size = %s , upload_time = NOW(), task_id = NULL WHERE meeting_id = %s ' , ( audio_file . filename , ' / ' + str ( relative_path ) , audio_file . size , meeting_id ) )
2025-08-28 08:02:59 +00:00
else :
2025-09-26 07:47:37 +00:00
cursor . execute ( ' INSERT INTO audio_files (meeting_id, file_name, file_path, file_size, upload_time) VALUES ( %s , %s , %s , %s , NOW()) ' , ( meeting_id , audio_file . filename , ' / ' + str ( relative_path ) , audio_file . size ) )
2025-08-28 08:02:59 +00:00
connection . commit ( )
cursor . close ( )
try :
task_id = transcription_service . start_transcription ( meeting_id , ' / ' + str ( relative_path ) )
print ( f " Transcription task started with ID: { task_id } " )
except Exception as e :
print ( f " Failed to start transcription task: { e } " )
except Exception as e :
if os . path . exists ( absolute_path ) :
2025-08-25 03:31:24 +00:00
os . remove ( absolute_path )
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 500 " , message = f " Failed to save file info: { str ( e ) } " )
return create_api_response ( code = " 200 " , message = " Audio file uploaded successfully " + ( " and replaced existing file " if replaced_existing else " " ) , data = {
" file_name " : audio_file . filename , " file_path " : ' / ' + str ( relative_path ) , " task_id " : task_id ,
" transcription_started " : task_id is not None , " replaced_existing " : replaced_existing ,
2025-08-28 08:02:59 +00:00
" previous_transcription_cleared " : replaced_existing and has_transcription
2025-09-26 07:47:37 +00:00
} )
2025-08-05 01:46:40 +00:00
@router.get ( " /meetings/ {meeting_id} /audio " )
2025-08-29 08:37:29 +00:00
def get_audio_file ( meeting_id : int , current_user : dict = Depends ( get_current_user ) ) :
2025-08-05 01:46:40 +00:00
with get_db_connection ( ) as connection :
cursor = connection . cursor ( dictionary = True )
2025-09-26 07:47:37 +00:00
cursor . execute ( " SELECT file_name, file_path, file_size, upload_time FROM audio_files WHERE meeting_id = %s " , ( meeting_id , ) )
2025-08-05 01:46:40 +00:00
audio_file = cursor . fetchone ( )
if not audio_file :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 404 " , message = " Audio file not found for this meeting " )
return create_api_response ( code = " 200 " , message = " Audio file found " , data = audio_file )
2025-08-06 07:07:02 +00:00
2025-08-28 08:02:59 +00:00
@router.get ( " /meetings/ {meeting_id} /transcription/status " )
2025-08-29 08:37:29 +00:00
def get_meeting_transcription_status ( meeting_id : int , current_user : dict = Depends ( get_current_user ) ) :
2025-08-28 08:02:59 +00:00
try :
status_info = transcription_service . get_meeting_transcription_status ( meeting_id )
if not status_info :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 404 " , message = " No transcription task found for this meeting " )
return create_api_response ( code = " 200 " , message = " Transcription status retrieved " , data = status_info )
2025-08-28 08:02:59 +00:00
except Exception as e :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 500 " , message = f " Failed to get meeting transcription status: { str ( e ) } " )
2025-08-28 08:02:59 +00:00
@router.post ( " /meetings/ {meeting_id} /transcription/start " )
2025-08-29 08:37:29 +00:00
def start_meeting_transcription ( meeting_id : int , current_user : dict = Depends ( get_current_user ) ) :
2025-08-28 08:02:59 +00:00
try :
with get_db_connection ( ) as connection :
cursor = connection . cursor ( dictionary = True )
cursor . execute ( " SELECT meeting_id FROM meetings WHERE meeting_id = %s " , ( meeting_id , ) )
if not cursor . fetchone ( ) :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 404 " , message = " Meeting not found " )
cursor . execute ( " SELECT file_path FROM audio_files WHERE meeting_id = %s LIMIT 1 " , ( meeting_id , ) )
2025-08-28 08:02:59 +00:00
audio_file = cursor . fetchone ( )
if not audio_file :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 400 " , message = " No audio file found for this meeting " )
2025-08-28 08:02:59 +00:00
existing_status = transcription_service . get_meeting_transcription_status ( meeting_id )
if existing_status and existing_status [ ' status ' ] in [ ' pending ' , ' processing ' ] :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 409 " , message = " Transcription task already exists " , data = {
" task_id " : existing_status [ ' task_id ' ] , " status " : existing_status [ ' status ' ]
} )
2025-08-28 08:02:59 +00:00
task_id = transcription_service . start_transcription ( meeting_id , audio_file [ ' file_path ' ] )
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 200 " , message = " Transcription task started successfully " , data = {
" task_id " : task_id , " meeting_id " : meeting_id
} )
2025-08-28 08:02:59 +00:00
except Exception as e :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 500 " , message = f " Failed to start transcription: { str ( e ) } " )
2025-08-28 08:02:59 +00:00
2025-08-06 07:07:02 +00:00
@router.post ( " /meetings/ {meeting_id} /upload-image " )
2025-09-26 07:47:37 +00:00
async def upload_image ( meeting_id : int , image_file : UploadFile = File ( . . . ) , current_user : dict = Depends ( get_current_user ) ) :
2025-08-06 07:07:02 +00:00
file_extension = os . path . splitext ( image_file . filename ) [ 1 ] . lower ( )
if file_extension not in ALLOWED_IMAGE_EXTENSIONS :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 400 " , message = f " Unsupported image type. Allowed types: { ' , ' . join ( ALLOWED_IMAGE_EXTENSIONS ) } " )
2025-09-25 03:48:02 +00:00
max_image_size = getattr ( config_module , ' MAX_IMAGE_SIZE ' , 10 * 1024 * 1024 )
if image_file . size > max_image_size :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 400 " , message = f " Image size exceeds { max_image_size / / ( 1024 * 1024 ) } MB limit " )
2025-08-06 07:07:02 +00:00
with get_db_connection ( ) as connection :
cursor = connection . cursor ( dictionary = True )
2025-08-29 08:37:29 +00:00
cursor . execute ( " SELECT user_id FROM meetings WHERE meeting_id = %s " , ( meeting_id , ) )
meeting = cursor . fetchone ( )
if not meeting :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 404 " , message = " Meeting not found " )
2025-08-29 08:37:29 +00:00
if meeting [ ' user_id ' ] != current_user [ ' user_id ' ] :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 403 " , message = " Permission denied " )
2025-08-06 07:07:02 +00:00
meeting_dir = MARKDOWN_DIR / str ( meeting_id )
meeting_dir . mkdir ( exist_ok = True )
unique_filename = f " { uuid . uuid4 ( ) } { file_extension } "
2025-08-25 03:31:24 +00:00
absolute_path = meeting_dir / unique_filename
relative_path = absolute_path . relative_to ( BASE_DIR )
2025-08-06 07:07:02 +00:00
try :
2025-08-25 03:31:24 +00:00
with open ( absolute_path , " wb " ) as buffer :
2025-08-06 07:07:02 +00:00
shutil . copyfileobj ( image_file . file , buffer )
except Exception as e :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 500 " , message = f " Failed to save image: { str ( e ) } " )
2025-09-30 04:14:19 +00:00
return create_api_response ( code = " 200 " , message = " Image uploaded successfully " , data = {
2025-09-26 07:47:37 +00:00
" file_name " : image_file . filename , " file_path " : ' / ' + str ( relative_path )
} )
2025-08-25 08:10:29 +00:00
@router.put ( " /meetings/ {meeting_id} /speaker-tags " )
2025-08-29 08:37:29 +00:00
def update_speaker_tag ( meeting_id : int , request : SpeakerTagUpdateRequest , current_user : dict = Depends ( get_current_user ) ) :
2025-08-25 08:10:29 +00:00
try :
with get_db_connection ( ) as connection :
cursor = connection . cursor ( )
2025-09-26 07:47:37 +00:00
update_query = ' UPDATE transcript_segments SET speaker_tag = %s WHERE meeting_id = %s AND speaker_id = %s '
2025-08-25 08:10:29 +00:00
cursor . execute ( update_query , ( request . new_tag , meeting_id , request . speaker_id ) )
if cursor . rowcount == 0 :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 404 " , message = " No segments found for this speaker " )
2025-08-25 08:10:29 +00:00
connection . commit ( )
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 200 " , message = " Speaker tag updated successfully " , data = { ' updated_count ' : cursor . rowcount } )
2025-08-25 08:10:29 +00:00
except Exception as e :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 500 " , message = f " Failed to update speaker tag: { str ( e ) } " )
2025-08-25 08:10:29 +00:00
@router.put ( " /meetings/ {meeting_id} /speaker-tags/batch " )
2025-08-29 08:37:29 +00:00
def batch_update_speaker_tags ( meeting_id : int , request : BatchSpeakerTagUpdateRequest , current_user : dict = Depends ( get_current_user ) ) :
2025-08-25 08:10:29 +00:00
try :
with get_db_connection ( ) as connection :
cursor = connection . cursor ( )
total_updated = 0
for update_item in request . updates :
2025-09-26 07:47:37 +00:00
update_query = ' UPDATE transcript_segments SET speaker_tag = %s WHERE meeting_id = %s AND speaker_id = %s '
2025-08-25 08:10:29 +00:00
cursor . execute ( update_query , ( update_item . new_tag , meeting_id , update_item . speaker_id ) )
total_updated + = cursor . rowcount
connection . commit ( )
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 200 " , message = " Speaker tags updated successfully " , data = { ' total_updated ' : total_updated } )
2025-08-25 08:10:29 +00:00
except Exception as e :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 500 " , message = f " Failed to batch update speaker tags: { str ( e ) } " )
2025-08-26 13:57:16 +00:00
@router.put ( " /meetings/ {meeting_id} /transcript/batch " )
2025-08-29 08:37:29 +00:00
def batch_update_transcript ( meeting_id : int , request : BatchTranscriptUpdateRequest , current_user : dict = Depends ( get_current_user ) ) :
2025-08-26 13:57:16 +00:00
try :
with get_db_connection ( ) as connection :
cursor = connection . cursor ( )
total_updated = 0
for update_item in request . updates :
2025-09-26 07:47:37 +00:00
cursor . execute ( " SELECT segment_id FROM transcript_segments WHERE segment_id = %s AND meeting_id = %s " , ( update_item . segment_id , meeting_id ) )
2025-08-26 13:57:16 +00:00
if not cursor . fetchone ( ) :
2025-09-26 07:47:37 +00:00
continue
update_query = ' UPDATE transcript_segments SET text_content = %s WHERE segment_id = %s AND meeting_id = %s '
2025-08-26 13:57:16 +00:00
cursor . execute ( update_query , ( update_item . text_content , update_item . segment_id , meeting_id ) )
total_updated + = cursor . rowcount
connection . commit ( )
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 200 " , message = " Transcript updated successfully " , data = { ' total_updated ' : total_updated } )
2025-08-26 13:57:16 +00:00
except Exception as e :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 500 " , message = f " Failed to update transcript: { str ( e ) } " )
2025-08-26 13:57:16 +00:00
@router.get ( " /meetings/ {meeting_id} /summaries " )
2025-08-29 08:37:29 +00:00
def get_meeting_summaries ( meeting_id : int , current_user : dict = Depends ( get_current_user ) ) :
2025-08-26 13:57:16 +00:00
try :
with get_db_connection ( ) as connection :
cursor = connection . cursor ( dictionary = True )
cursor . execute ( " SELECT meeting_id FROM meetings WHERE meeting_id = %s " , ( meeting_id , ) )
if not cursor . fetchone ( ) :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 404 " , message = " Meeting not found " )
2025-08-26 13:57:16 +00:00
summaries = llm_service . get_meeting_summaries ( meeting_id )
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 200 " , message = " Summaries retrieved successfully " , data = { " summaries " : summaries } )
2025-08-26 13:57:16 +00:00
except Exception as e :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 500 " , message = f " Failed to get summaries: { str ( e ) } " )
2025-08-26 13:57:16 +00:00
@router.get ( " /meetings/ {meeting_id} /summaries/ {summary_id} " )
2025-08-29 08:37:29 +00:00
def get_summary_detail ( meeting_id : int , summary_id : int , current_user : dict = Depends ( get_current_user ) ) :
2025-08-26 13:57:16 +00:00
try :
with get_db_connection ( ) as connection :
cursor = connection . cursor ( dictionary = True )
2025-09-26 07:47:37 +00:00
query = " SELECT id, summary_content, user_prompt, created_at FROM meeting_summaries WHERE id = %s AND meeting_id = %s "
2025-08-26 13:57:16 +00:00
cursor . execute ( query , ( summary_id , meeting_id ) )
summary = cursor . fetchone ( )
if not summary :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 404 " , message = " Summary not found " )
return create_api_response ( code = " 200 " , message = " Summary detail retrieved " , data = summary )
2025-08-26 13:57:16 +00:00
except Exception as e :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 500 " , message = f " Failed to get summary detail: { str ( e ) } " )
2025-09-09 03:35:56 +00:00
@router.post ( " /meetings/ {meeting_id} /generate-summary-async " )
def generate_meeting_summary_async ( meeting_id : int , request : GenerateSummaryRequest , background_tasks : BackgroundTasks , current_user : dict = Depends ( get_current_user ) ) :
try :
with get_db_connection ( ) as connection :
cursor = connection . cursor ( dictionary = True )
cursor . execute ( " SELECT meeting_id FROM meetings WHERE meeting_id = %s " , ( meeting_id , ) )
if not cursor . fetchone ( ) :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 404 " , message = " Meeting not found " )
2025-09-09 03:35:56 +00:00
task_id = async_llm_service . start_summary_generation ( meeting_id , request . user_prompt )
background_tasks . add_task ( async_llm_service . _process_task , task_id )
2025-09-30 04:14:19 +00:00
return create_api_response ( code = " 200 " , message = " Summary generation task has been accepted. " , data = {
2025-09-26 07:47:37 +00:00
" task_id " : task_id , " status " : " pending " , " meeting_id " : meeting_id
} )
2025-09-09 03:35:56 +00:00
except Exception as e :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 500 " , message = f " Failed to start summary generation: { str ( e ) } " )
2025-09-09 03:35:56 +00:00
@router.get ( " /meetings/ {meeting_id} /llm-tasks " )
def get_meeting_llm_tasks ( meeting_id : int , current_user : dict = Depends ( get_current_user ) ) :
try :
with get_db_connection ( ) as connection :
cursor = connection . cursor ( dictionary = True )
cursor . execute ( " SELECT meeting_id FROM meetings WHERE meeting_id = %s " , ( meeting_id , ) )
if not cursor . fetchone ( ) :
2025-09-26 07:47:37 +00:00
return create_api_response ( code = " 404 " , message = " Meeting not found " )
2025-09-09 03:35:56 +00:00
tasks = async_llm_service . get_meeting_llm_tasks ( meeting_id )
2025-09-30 04:14:19 +00:00
return create_api_response ( code = " 200 " , message = " LLM tasks retrieved successfully " , data = {
2025-09-26 07:47:37 +00:00
" tasks " : tasks , " total " : len ( tasks )
} )
2025-09-09 03:35:56 +00:00
except Exception as e :
2025-09-30 04:14:19 +00:00
return create_api_response ( code = " 500 " , message = f " Failed to get LLM tasks: { str ( e ) } " )