添加了会议标签

main
mula.liu 2025-09-19 16:51:07 +08:00
parent f6e1fd6d94
commit 9fb07bb435
5 changed files with 112 additions and 15 deletions

BIN
app.zip

Binary file not shown.

View File

@ -1,5 +1,6 @@
from fastapi import APIRouter, HTTPException, UploadFile, File, Form, Depends, BackgroundTasks from fastapi import APIRouter, HTTPException, UploadFile, File, Form, Depends, BackgroundTasks
from app.models.models import Meeting, TranscriptSegment, TranscriptionTaskStatus, CreateMeetingRequest, UpdateMeetingRequest, SpeakerTagUpdateRequest, BatchSpeakerTagUpdateRequest, TranscriptUpdateRequest, BatchTranscriptUpdateRequest from app.models.models import Meeting, TranscriptSegment, TranscriptionTaskStatus, CreateMeetingRequest, UpdateMeetingRequest, SpeakerTagUpdateRequest, BatchSpeakerTagUpdateRequest, TranscriptUpdateRequest, BatchTranscriptUpdateRequest, Tag
from app.core.database import get_db_connection from app.core.database import get_db_connection
from app.core.config import BASE_DIR, UPLOAD_DIR, AUDIO_DIR, MARKDOWN_DIR, ALLOWED_EXTENSIONS, ALLOWED_IMAGE_EXTENSIONS, MAX_FILE_SIZE, MAX_IMAGE_SIZE from app.core.config import BASE_DIR, UPLOAD_DIR, AUDIO_DIR, MARKDOWN_DIR, ALLOWED_EXTENSIONS, ALLOWED_IMAGE_EXTENSIONS, MAX_FILE_SIZE, MAX_IMAGE_SIZE
from app.services.qiniu_service import qiniu_service from app.services.qiniu_service import qiniu_service
@ -7,7 +8,7 @@ from app.services.llm_service import LLMService
from app.services.async_transcription_service import AsyncTranscriptionService from app.services.async_transcription_service import AsyncTranscriptionService
from app.services.async_llm_service import async_llm_service from app.services.async_llm_service import async_llm_service
from app.core.auth import get_current_user, get_optional_current_user from app.core.auth import get_current_user, get_optional_current_user
from typing import Optional from typing import List, Optional
from datetime import datetime from datetime import datetime
from pydantic import BaseModel from pydantic import BaseModel
import os import os
@ -27,6 +28,24 @@ transcription_service = AsyncTranscriptionService()
class GenerateSummaryRequest(BaseModel): class GenerateSummaryRequest(BaseModel):
user_prompt: Optional[str] = "" user_prompt: Optional[str] = ""
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 []
# Ensure all tags exist in the 'tags' table
insert_ignore_query = "INSERT IGNORE INTO tags (name) VALUES (%s)"
cursor.executemany(insert_ignore_query, [(name,) for name in tag_names])
# Fetch the full tag objects
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]
@router.get("/meetings", response_model=list[Meeting]) @router.get("/meetings", response_model=list[Meeting])
def get_meetings(current_user: dict = Depends(get_current_user), user_id: Optional[int] = None): def get_meetings(current_user: dict = Depends(get_current_user), user_id: Optional[int] = None):
with get_db_connection() as connection: with get_db_connection() as connection:
@ -34,7 +53,7 @@ def get_meetings(current_user: dict = Depends(get_current_user), user_id: Option
base_query = ''' base_query = '''
SELECT SELECT
m.meeting_id, m.title, m.meeting_time, m.summary, m.created_at, 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 m.user_id as creator_id, u.caption as creator_username
FROM meetings m FROM meetings m
JOIN users u ON m.user_id = u.user_id JOIN users u ON m.user_id = u.user_id
@ -67,6 +86,8 @@ def get_meetings(current_user: dict = Depends(get_current_user), user_id: Option
attendees_data = cursor.fetchall() attendees_data = cursor.fetchall()
attendees = [{'user_id': row['user_id'], 'caption': row['caption']} for row in attendees_data] attendees = [{'user_id': row['user_id'], 'caption': row['caption']} for row in attendees_data]
tags = _process_tags(cursor, meeting.get('tags'))
meeting_list.append(Meeting( meeting_list.append(Meeting(
meeting_id=meeting['meeting_id'], meeting_id=meeting['meeting_id'],
title=meeting['title'], title=meeting['title'],
@ -75,7 +96,8 @@ def get_meetings(current_user: dict = Depends(get_current_user), user_id: Option
created_at=meeting['created_at'], created_at=meeting['created_at'],
attendees=attendees, attendees=attendees,
creator_id=meeting['creator_id'], creator_id=meeting['creator_id'],
creator_username=meeting['creator_username'] creator_username=meeting['creator_username'],
tags=tags
)) ))
return meeting_list return meeting_list
@ -87,7 +109,7 @@ def get_meeting_details(meeting_id: int, current_user: dict = Depends(get_curren
query = ''' query = '''
SELECT SELECT
m.meeting_id, m.title, m.meeting_time, m.summary, m.created_at, 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, m.user_id as creator_id, u.caption as creator_username,
af.file_path as audio_file_path af.file_path as audio_file_path
FROM meetings m FROM meetings m
@ -112,6 +134,8 @@ def get_meeting_details(meeting_id: int, current_user: dict = Depends(get_curren
attendees_data = cursor.fetchall() attendees_data = cursor.fetchall()
attendees = [{'user_id': row['user_id'], 'caption': row['caption']} for row in attendees_data] attendees = [{'user_id': row['user_id'], 'caption': row['caption']} for row in attendees_data]
tags = _process_tags(cursor, meeting.get('tags'))
# 关闭游标,避免与转录服务的数据库连接冲突 # 关闭游标,避免与转录服务的数据库连接冲突
cursor.close() cursor.close()
@ -123,7 +147,8 @@ def get_meeting_details(meeting_id: int, current_user: dict = Depends(get_curren
created_at=meeting['created_at'], created_at=meeting['created_at'],
attendees=attendees, attendees=attendees,
creator_id=meeting['creator_id'], creator_id=meeting['creator_id'],
creator_username=meeting['creator_username'] creator_username=meeting['creator_username'],
tags=tags
) )
# Add audio file path if exists # Add audio file path if exists
@ -182,16 +207,24 @@ def create_meeting(meeting_request: CreateMeetingRequest, current_user: dict = D
with get_db_connection() as connection: with get_db_connection() as connection:
cursor = connection.cursor(dictionary=True) cursor = connection.cursor(dictionary=True)
# Process tags
if meeting_request.tags:
tag_names = [name.strip() for name in meeting_request.tags.split(',') if name.strip()]
if tag_names:
insert_ignore_query = "INSERT IGNORE INTO tags (name) VALUES (%s)"
cursor.executemany(insert_ignore_query, [(name,) for name in tag_names])
# Create meeting # Create meeting
meeting_query = ''' meeting_query = '''
INSERT INTO meetings (user_id, title, meeting_time, summary,created_at) INSERT INTO meetings (user_id, title, meeting_time, summary, tags, created_at)
VALUES (%s, %s, %s, %s, %s) VALUES (%s, %s, %s, %s, %s, %s)
''' '''
cursor.execute(meeting_query, ( cursor.execute(meeting_query, (
meeting_request.user_id, meeting_request.user_id,
meeting_request.title, meeting_request.title,
meeting_request.meeting_time, meeting_request.meeting_time,
None, None,
meeting_request.tags,
datetime.now().isoformat() datetime.now().isoformat()
)) ))
@ -222,16 +255,24 @@ def update_meeting(meeting_id: int, meeting_request: UpdateMeetingRequest, curre
if meeting['user_id'] != current_user['user_id']: if meeting['user_id'] != current_user['user_id']:
raise HTTPException(status_code=403, detail="Permission denied") raise HTTPException(status_code=403, detail="Permission denied")
# Process tags
if meeting_request.tags:
tag_names = [name.strip() for name in meeting_request.tags.split(',') if name.strip()]
if tag_names:
insert_ignore_query = "INSERT IGNORE INTO tags (name) VALUES (%s)"
cursor.executemany(insert_ignore_query, [(name,) for name in tag_names])
# Update meeting # Update meeting
update_query = ''' update_query = '''
UPDATE meetings UPDATE meetings
SET title = %s, meeting_time = %s, summary = %s SET title = %s, meeting_time = %s, summary = %s, tags = %s
WHERE meeting_id = %s WHERE meeting_id = %s
''' '''
cursor.execute(update_query, ( cursor.execute(update_query, (
meeting_request.title, meeting_request.title,
meeting_request.meeting_time, meeting_request.meeting_time,
meeting_request.summary, meeting_request.summary,
meeting_request.tags,
meeting_id meeting_id
)) ))
@ -282,7 +323,7 @@ def get_meeting_for_edit(meeting_id: int, current_user: dict = Depends(get_curre
query = ''' query = '''
SELECT SELECT
m.meeting_id, m.title, m.meeting_time, m.summary, m.created_at, 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, m.user_id as creator_id, u.caption as creator_username,
af.file_path as audio_file_path af.file_path as audio_file_path
FROM meetings m FROM meetings m
@ -308,6 +349,8 @@ def get_meeting_for_edit(meeting_id: int, current_user: dict = Depends(get_curre
attendees_data = cursor.fetchall() attendees_data = cursor.fetchall()
attendees = [{'user_id': row['user_id'], 'caption': row['caption']} for row in attendees_data] attendees = [{'user_id': row['user_id'], 'caption': row['caption']} for row in attendees_data]
tags = _process_tags(cursor, meeting.get('tags'))
# 关闭游标,避免与转录服务的数据库连接冲突 # 关闭游标,避免与转录服务的数据库连接冲突
cursor.close() cursor.close()
@ -319,7 +362,8 @@ def get_meeting_for_edit(meeting_id: int, current_user: dict = Depends(get_curre
created_at=meeting['created_at'], created_at=meeting['created_at'],
attendees=attendees, attendees=attendees,
creator_id=meeting['creator_id'], creator_id=meeting['creator_id'],
creator_username=meeting['creator_username'] creator_username=meeting['creator_username'],
tags=tags
) )
# Add audio file path if exists # Add audio file path if exists

View File

@ -0,0 +1,45 @@
from fastapi import APIRouter, HTTPException, Depends
from app.core.database import get_db_connection
from app.models.models import Tag
from typing import List
import mysql.connector
router = APIRouter()
@router.get("/tags/", response_model=List[Tag])
def get_all_tags():
"""_summary_
获取所有标签
"""
query = "SELECT id, name, color FROM tags ORDER BY name"
try:
with get_db_connection() as connection:
with connection.cursor(dictionary=True) as cursor:
cursor.execute(query)
tags = cursor.fetchall()
return tags
except mysql.connector.Error as err:
print(f"Error: {err}")
raise HTTPException(status_code=500, detail="Failed to retrieve tags from database.")
@router.post("/tags/", response_model=Tag)
def create_tag(tag_in: Tag):
"""_summary_
创建一个新标签
"""
query = "INSERT INTO tags (name, color) VALUES (%s, %s)"
try:
with get_db_connection() as connection:
with connection.cursor(dictionary=True) as cursor:
try:
cursor.execute(query, (tag_in.name, tag_in.color))
connection.commit()
tag_id = cursor.lastrowid
return {"id": tag_id, "name": tag_in.name, "color": tag_in.color}
except mysql.connector.IntegrityError:
connection.rollback()
raise HTTPException(status_code=400, detail=f"Tag '{tag_in.name}' already exists.")
except mysql.connector.Error as err:
print(f"Error: {err}")
raise HTTPException(status_code=500, detail="Failed to create tag in database.")

View File

@ -1,4 +1,3 @@
from pydantic import BaseModel, EmailStr from pydantic import BaseModel, EmailStr
from typing import Optional, Union, List from typing import Optional, Union, List
import datetime import datetime
@ -51,6 +50,11 @@ class AttendeeInfo(BaseModel):
user_id: int user_id: int
caption: str caption: str
class Tag(BaseModel):
id: int
name: str
color: str
class TranscriptionTaskStatus(BaseModel): class TranscriptionTaskStatus(BaseModel):
task_id: str task_id: str
status: str # 'pending', 'processing', 'completed', 'failed' status: str # 'pending', 'processing', 'completed', 'failed'
@ -72,6 +76,7 @@ class Meeting(BaseModel):
creator_username: str creator_username: str
audio_file_path: Optional[str] = None audio_file_path: Optional[str] = None
transcription_status: Optional[TranscriptionTaskStatus] = None transcription_status: Optional[TranscriptionTaskStatus] = None
tags: Optional[List[Tag]] = []
class TranscriptSegment(BaseModel): class TranscriptSegment(BaseModel):
segment_id: int segment_id: int
@ -87,12 +92,14 @@ class CreateMeetingRequest(BaseModel):
title: str title: str
meeting_time: Optional[datetime.datetime] meeting_time: Optional[datetime.datetime]
attendee_ids: list[int] attendee_ids: list[int]
tags: Optional[str] = None
class UpdateMeetingRequest(BaseModel): class UpdateMeetingRequest(BaseModel):
title: str title: str
meeting_time: Optional[datetime.datetime] meeting_time: Optional[datetime.datetime]
summary: Optional[str] summary: Optional[str]
attendee_ids: list[int] attendee_ids: list[int]
tags: Optional[str] = None
class SpeakerTagUpdateRequest(BaseModel): class SpeakerTagUpdateRequest(BaseModel):
speaker_id: int # 使用原始speaker_id整数 speaker_id: int # 使用原始speaker_id整数

View File

@ -2,7 +2,7 @@ import uvicorn
from fastapi import FastAPI, Request, HTTPException from fastapi import FastAPI, Request, HTTPException
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from app.api.endpoints import auth, users, meetings from app.api.endpoints import auth, users, meetings, tags
from app.core.config import UPLOAD_DIR, API_CONFIG, MAX_FILE_SIZE from app.core.config import UPLOAD_DIR, API_CONFIG, MAX_FILE_SIZE
from app.services.async_llm_service import async_llm_service from app.services.async_llm_service import async_llm_service
import os import os
@ -30,6 +30,7 @@ if UPLOAD_DIR.exists():
app.include_router(auth.router, prefix="/api", tags=["Authentication"]) app.include_router(auth.router, prefix="/api", tags=["Authentication"])
app.include_router(users.router, prefix="/api", tags=["Users"]) app.include_router(users.router, prefix="/api", tags=["Users"])
app.include_router(meetings.router, prefix="/api", tags=["Meetings"]) app.include_router(meetings.router, prefix="/api", tags=["Meetings"])
app.include_router(tags.router, prefix="/api", tags=["Tags"])
@app.get("/") @app.get("/")
def read_root(): def read_root():