这是一个基于 Streamlit 的内网视频通话应用。让我为您详细解读代码的各个部分:

代码结构分析

1. 导入模块

import streamlit as st  # 主框架
import av  # 音视频处理
from streamlit_webrtc import webrtc_streamer, WebRtcMode  # WebRTC集成
import sqlite3  # 数据库

2. 数据库设计

创建了5个核心表:

  • devices:设备信息(类型、屏幕尺寸等)

  • users:用户信息和在线状态

  • messages:聊天记录

  • call_invitations:通话邀请管理

  • notifications:通知系统

3. 设备管理

def get_or_create_device_id():
    # 基于硬件信息生成唯一设备ID
    device_info = f"{platform.platform()}_{socket.gethostname()}"
    device_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, device_info))

4. 核心功能模块

用户认证系统
  • 侧边栏用户名输入

  • 自动设备注册

  • 在线状态管理

通话管理系统
def create_call_invitation(conn, from_user, to_user, call_type, room_id):
    # 创建通话邀请并生成房间ID
通知系统
  • 实时通知铃铛显示

  • 模态框接听界面

  • 通知标记已读

聊天系统
  • 实时消息显示

  • 对话历史记录

  • 支持富文本渲染

5. WebRTC 视频通话

webrtc_ctx = webrtc_streamer(
    key=f"video-{room_id}",
    mode=WebRtcMode.SENDRECV,  # 双向音视频
    rtc_configuration=RTC_CONFIGURATION,
    media_stream_constraints={"video": True, "audio": True}
)

6. 响应式设计

CSS媒体查询适配不同设备:

  • 手机:垂直布局,触摸优化

  • 平板:自适应网格

  • 桌面:多列布局

工作流程

  1. 1.

    用户注册​ → 输入用户名自动注册设备

  2. 2.

    查看在线用户​ → 显示可通话对象

  3. 3.

    发起通话​ → 创建邀请并发送通知

  4. 4.

    接听邀请​ → 弹出模态框选择接听/拒绝

  5. 5.

    视频通话​ → 建立WebRTC连接

  6. 6.

    结束通话​ → 清理资源返回主界面

import streamlit as st
import av
import threading
import queue
import json
import logging
import uuid
import time
import sqlite3
from datetime import datetime, timedelta
from streamlit_webrtc import webrtc_streamer, WebRtcMode, RTCConfiguration
import socket
import platform
import asyncio
from typing import Optional, Dict, List

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

# 页面配置 - 响应式设计
st.set_page_config(
    page_title="内网视频通话",
    page_icon="📹",
    layout="wide",
    initial_sidebar_state="expanded"
)

# 修复Python 3.12的SQLite日期时间适配器警告
def adapt_datetime(val):
    return val.isoformat()

def convert_datetime(val):
    try:
        return datetime.fromisoformat(val.decode())
    except (ValueError, AttributeError):
        return datetime.now()

sqlite3.register_adapter(datetime, adapt_datetime)
sqlite3.register_converter("TIMESTAMP", convert_datetime)

# 初始化数据库
def init_db():
    conn = sqlite3.connect('video_chat.db', check_same_thread=False, detect_types=sqlite3.PARSE_DECLTYPES)
    c = conn.cursor()
    
    # 创建设备表
    c.execute('''CREATE TABLE IF NOT EXISTS devices
                 (id TEXT PRIMARY KEY, 
                  device_name TEXT,
                  username TEXT,
                  device_type TEXT,
                  screen_width INTEGER,
                  screen_height INTEGER,
                  user_agent TEXT,
                  first_seen TIMESTAMP,
                  last_seen TIMESTAMP)''')
    
    # 创建用户表
    c.execute('''CREATE TABLE IF NOT EXISTS users
                 (id TEXT PRIMARY KEY,
                  username TEXT UNIQUE,
                  device_id TEXT,
                  status TEXT,
                  last_active TIMESTAMP,
                  FOREIGN KEY (device_id) REFERENCES devices (id))''')
    
    # 创建消息表
    c.execute('''CREATE TABLE IF NOT EXISTS messages
                 (id TEXT PRIMARY KEY,
                  from_user TEXT,
                  to_user TEXT,
                  content TEXT,
                  timestamp TIMESTAMP,
                  is_read BOOLEAN)''')
    
    # 创建通话邀请表
    c.execute('''CREATE TABLE IF NOT EXISTS call_invitations
                 (id TEXT PRIMARY KEY,
                  from_user TEXT,
                  to_user TEXT,
                  call_type TEXT,
                  status TEXT,
                  room_id TEXT,
                  created_at TIMESTAMP,
                  responded_at TIMESTAMP)''')
    
    # 创建通知表
    c.execute('''CREATE TABLE IF NOT EXISTS notifications
                 (id TEXT PRIMARY KEY,
                  user_id TEXT,
                  title TEXT,
                  message TEXT,
                  notification_type TEXT,
                  is_read BOOLEAN,
                  created_at TIMESTAMP,
                  related_call_id TEXT)''')
    
    conn.commit()
    return conn

# 获取或创建设备ID
def get_or_create_device_id():
    if 'device_id' not in st.session_state:
        # 生成基于硬件和浏览器的唯一标识
        device_info = f"{platform.platform()}_{socket.gethostname()}"
        device_id = str(uuid.uuid5(uuid.NAMESPACE_DNS, device_info))
        st.session_state.device_id = device_id
    
    return st.session_state.device_id

# 检测设备类型
def detect_device_type():
    try:
        user_agent = st.query_params.get('user_agent', '')
        if any(device in user_agent.lower() for device in ['mobile', 'android', 'iphone']):
            return "mobile"
        elif any(device in user_agent.lower() for device in ['tablet', 'ipad']):
            return "tablet"
        else:
            return "desktop"
    except:
        return "desktop"

# 注册或更新设备信息
def register_device(conn, username):
    device_id = get_or_create_device_id()
    device_type = detect_device_type()
    
    c = conn.cursor()
    
    # 检查设备是否已存在
    c.execute("SELECT * FROM devices WHERE id = ?", (device_id,))
    existing_device = c.fetchone()
    
    current_time = datetime.now()
    
    if existing_device:
        # 更新设备信息
        c.execute('''UPDATE devices 
                     SET username = ?, last_seen = ?
                     WHERE id = ?''', 
                 (username, current_time, device_id))
    else:
        # 插入新设备
        c.execute('''INSERT INTO devices 
                     (id, device_name, username, device_type, screen_width, screen_height, user_agent, first_seen, last_seen)
                     VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)''',
                 (device_id, f"{device_type}_{device_id[:8]}", username, device_type, 
                  1920, 1080, "streamlit_app", current_time, current_time))
    
    # 更新用户信息
    user_id = str(uuid.uuid4())
    c.execute('''INSERT OR REPLACE INTO users 
                 (id, username, device_id, status, last_active)
                 VALUES (?, ?, ?, ?, ?)''',
             (user_id, username, device_id, "online", current_time))
    
    conn.commit()
    return device_id, user_id

# 获取在线用户列表
def get_online_users(conn, exclude_user=None):
    c = conn.cursor()
    
    if exclude_user:
        c.execute('''SELECT u.username, d.device_type, u.last_active 
                     FROM users u
                     JOIN devices d ON u.device_id = d.id
                     WHERE u.status = 'online' AND u.username != ?
                     ORDER BY u.last_active DESC''', (exclude_user,))
    else:
        c.execute('''SELECT u.username, d.device_type, u.last_active 
                     FROM users u
                     JOIN devices d ON u.device_id = d.id
                     WHERE u.status = 'online'
                     ORDER BY u.last_active DESC''')
    
    return c.fetchall()

# 创建通话邀请
def create_call_invitation(conn, from_user, to_user, call_type="video", room_id=None):
    if room_id is None:
        room_id = f"room_{uuid.uuid4().hex[:8]}"
    
    call_id = str(uuid.uuid4())
    current_time = datetime.now()
    
    c = conn.cursor()
    c.execute('''INSERT INTO call_invitations 
                 (id, from_user, to_user, call_type, status, room_id, created_at)
                 VALUES (?, ?, ?, ?, ?, ?, ?)''',
             (call_id, from_user, to_user, call_type, "pending", room_id, current_time))
    
    # 创建通知
    notification_id = str(uuid.uuid4())
    c.execute('''INSERT INTO notifications
                 (id, user_id, title, message, notification_type, is_read, created_at, related_call_id)
                 VALUES (?, ?, ?, ?, ?, ?, ?, ?)''',
             (notification_id, to_user, "视频通话邀请", 
              f"{from_user} 向您发起了视频通话邀请", "call_invitation", 
              False, current_time, call_id))
    
    conn.commit()
    return call_id, room_id

# 获取待处理的通知
def get_pending_notifications(conn, username):
    c = conn.cursor()
    c.execute('''SELECT id, title, message, notification_type, created_at, related_call_id
                 FROM notifications 
                 WHERE user_id = ? AND is_read = FALSE
                 ORDER BY created_at DESC''', (username,))
    return c.fetchall()

# 标记通知为已读
def mark_notification_read(conn, notification_id):
    c = conn.cursor()
    c.execute('''UPDATE notifications SET is_read = TRUE WHERE id = ?''', (notification_id,))
    conn.commit()

# 处理通话邀请响应
def respond_to_call_invitation(conn, call_id, response, username):
    """响应通话邀请:accept 或 reject"""
    c = conn.cursor()
    current_time = datetime.now()
    
    # 更新邀请状态
    c.execute('''UPDATE call_invitations 
                 SET status = ?, responded_at = ?
                 WHERE id = ? AND to_user = ?''',
             (response, current_time, call_id, username))
    
    # 获取房间ID
    c.execute('''SELECT room_id FROM call_invitations WHERE id = ?''', (call_id,))
    result = c.fetchone()
    room_id = result[0] if result else None
    
    conn.commit()
    return room_id if response == "accepted" else None

# 获取待处理的通话邀请
def get_pending_call_invitations(conn, username):
    c = conn.cursor()
    c.execute('''SELECT id, from_user, call_type, room_id, created_at
                 FROM call_invitations 
                 WHERE to_user = ? AND status = 'pending'
                 ORDER BY created_at DESC''', (username,))
    return c.fetchall()

# 检查媒体权限状态
def check_media_permissions():
    # 简化处理,实际部署时应该通过前端检测
    video_permission = st.session_state.get('video_permission', False)
    audio_permission = st.session_state.get('audio_permission', False)
    
    return video_permission, audio_permission

# 请求媒体权限
def request_media_permissions():
    st.warning("请允许摄像头和麦克风权限")
    
    col1, col2 = st.columns(2)
    with col1:
        if st.button("授予摄像头和麦克风权限"):
            st.session_state.video_permission = True
            st.session_state.audio_permission = True
            st.rerun()
    with col2:
        if st.button("稍后再说"):
            st.session_state.video_permission = False
            st.session_state.audio_permission = False

# WebRTC配置
RTC_CONFIGURATION = RTCConfiguration(
    {"iceServers": [{"urls": ["stun:stun.l.google.com:19302"]}]}
)

# 初始化数据库连接
conn = init_db()

# 响应式CSS
st.markdown("""
<style>
/* 响应式设计 */
@media (max-width: 768px) {
    .main-header { font-size: 1.5rem !important; }
    .user-card { padding: 8px !important; margin: 4px 0 !important; }
    .video-container { height: 200px !important; }
    .notification-bell { font-size: 1.2rem !important; }
}

@media (min-width: 769px) and (max-width: 1024px) {
    .video-container { height: 300px !important; }
}

@media (min-width: 1025px) {
    .video-container { height: 400px !important; }
}

/* 通用样式 */
.user-card {
    border: 1px solid #ddd;
    border-radius: 10px;
    padding: 12px;
    margin: 8px 0;
    cursor: pointer;
    transition: all 0.3s ease;
}

.user-card:hover {
    background-color: #f5f5f5;
    transform: translateY(-2px);
}

.user-online {
    border-left: 4px solid #28a745;
}

.user-offline {
    border-left: 4px solid #6c757d;
}

.chat-window {
    border: 1px solid #ddd;
    border-radius: 10px;
    padding: 15px;
    margin: 10px 0;
    max-height: 400px;
    overflow-y: auto;
}

.video-container {
    border: 2px solid #007bff;
    border-radius: 10px;
    margin: 10px 0;
    background-color: #000;
}

.notification-bell {
    position: relative;
    display: inline-block;
    font-size: 1.5rem;
}

.notification-badge {
    position: absolute;
    top: -5px;
    right: -5px;
    background-color: #ff4b4b;
    color: white;
    border-radius: 50%;
    width: 20px;
    height: 20px;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 0.8rem;
}

.notification-item {
    border-left: 4px solid #007bff;
    padding: 10px;
    margin: 5px 0;
    background-color: #f8f9fa;
    border-radius: 5px;
}

.call-invitation {
    border-left: 4px solid #28a745;
    background-color: #e8f5e8;
}

.modal-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0,0,0,0.5);
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 1000;
}

.modal-content {
    background: white;
    padding: 20px;
    border-radius: 10px;
    width: 300px;
    text-align: center;
}
</style>
""", unsafe_allow_html=True)

# 应用标题和通知区域
col1, col2 = st.columns([4, 1])
with col1:
    st.markdown('<h1 class="main-header">📹 内网视频通话应用</h1>', unsafe_allow_html=True)
    st.markdown("支持多设备适配的实时视频通信平台")

with col2:
    # 通知铃铛
    pending_notifications = get_pending_notifications(conn, st.session_state.get('username', ''))
    notification_count = len(pending_notifications)
    
    st.markdown(f'''
    <div class="notification-bell">
        🔔
        {f'<span class="notification-badge">{notification_count}</span>' if notification_count > 0 else ''}
    </div>
    ''', unsafe_allow_html=True)
    
    if st.button("查看通知", use_container_width=True):
        st.session_state.show_notifications = not st.session_state.get('show_notifications', False)

# 显示通知面板
if st.session_state.get('show_notifications', False) and st.session_state.get('username'):
    st.subheader("📢 通知中心")
    notifications = get_pending_notifications(conn, st.session_state.username)
    
    if notifications:
        for notif_id, title, message, notif_type, created_at, call_id in notifications:
            with st.container():
                col1, col2 = st.columns([4, 1])
                with col1:
                    st.markdown(f"**{title}**")
                    st.markdown(f"{message}")
                    st.caption(f"时间: {created_at.strftime('%H:%M:%S') if hasattr(created_at, 'strftime') else created_at}")
                
                with col2:
                    if notif_type == "call_invitation":
                        col2_1, col2_2 = st.columns(2)
                        with col2_1:
                            if st.button("接听", key=f"accept_{notif_id}"):
                                room_id = respond_to_call_invitation(conn, call_id, "accepted", st.session_state.username)
                                mark_notification_read(conn, notif_id)
                                st.session_state.call_active = True
                                st.session_state.current_room = room_id
                                st.session_state.show_notifications = False
                                st.rerun()
                        with col2_2:
                            if st.button("拒绝", key=f"reject_{notif_id}"):
                                respond_to_call_invitation(conn, call_id, "rejected", st.session_state.username)
                                mark_notification_read(conn, notif_id)
                                st.session_state.show_notifications = False
                                st.rerun()
                    else:
                        if st.button("标记已读", key=f"read_{notif_id}"):
                            mark_notification_read(conn, notif_id)
                            st.session_state.show_notifications = False
                            st.rerun()
                
                st.markdown("---")
    else:
        st.info("暂无新通知")

# 用户注册和登录
with st.sidebar:
    st.header("👤 用户设置")
    
    # 用户名输入
    username = st.text_input("用户名", placeholder="请输入您的用户名", key="username_input")
    
    if username and username.strip():
        st.session_state.username = username.strip()
        # 注册设备
        device_id, user_id = register_device(conn, st.session_state.username)
        device_type = detect_device_type()
        
        st.success(f"设备已注册: {device_type.upper()}")
        st.info(f"设备ID: {device_id[:8]}...")
        
        # 通话设置
        st.header("⚙️ 通话设置")
        call_id = st.text_input("房间ID", value=f"room_{uuid.uuid4().hex[:8]}", key="room_input")
        
        # 媒体权限检查
        video_permission, audio_permission = check_media_permissions()
        
        if not video_permission or not audio_permission:
            request_media_permissions()
        else:
            video_enabled = st.checkbox("启用视频", value=video_permission, key="video_check")
            audio_enabled = st.checkbox("启用音频", value=audio_permission, key="audio_check")
        
        # 通话控制
        st.header("📞 通话控制")
        col1, col2 = st.columns(2)
        with col1:
            start_call = st.button("加入通话", type="primary", use_container_width=True, key="join_call")
        with col2:
            end_call = st.button("离开通话", type="secondary", use_container_width=True, key="leave_call")

# 主内容区域
if not st.session_state.get('username'):
    st.warning("请在侧边栏输入用户名开始使用")
    st.stop()

# 初始化会话状态
if 'current_chat' not in st.session_state:
    st.session_state.current_chat = None
if 'call_active' not in st.session_state:
    st.session_state.call_active = False
if 'messages' not in st.session_state:
    st.session_state.messages = {}
if 'current_room' not in st.session_state:
    st.session_state.current_room = None
if 'pending_invitation' not in st.session_state:
    st.session_state.pending_invitation = None
if 'show_invitation_modal' not in st.session_state:
    st.session_state.show_invitation_modal = False

# 检查是否有待处理的通话邀请
if st.session_state.username and not st.session_state.call_active:
    pending_invitations = get_pending_call_invitations(conn, st.session_state.username)
    if pending_invitations and not st.session_state.get('show_invitation_modal', False):
        st.session_state.pending_invitation = pending_invitations[0]
        st.session_state.show_invitation_modal = True

# 显示通话邀请模态框
if st.session_state.get('show_invitation_modal', False) and st.session_state.pending_invitation:
    invitation_id, from_user, call_type, room_id, created_at = st.session_state.pending_invitation
    
    # 使用Streamlit原生组件创建模态框
    st.markdown("""
    <div class="modal-overlay">
        <div class="modal-content">
    """, unsafe_allow_html=True)
    
    st.info(f"📞 {from_user} 邀请您视频通话")
    
    accept_col, reject_col = st.columns(2)
    with accept_col:
        if st.button("接听通话", type="primary", use_container_width=True, key="modal_accept"):
            room_id = respond_to_call_invitation(conn, invitation_id, "accepted", st.session_state.username)
            st.session_state.call_active = True
            st.session_state.current_room = room_id
            st.session_state.show_invitation_modal = False
            st.session_state.pending_invitation = None
            st.rerun()
    with reject_col:
        if st.button("拒绝通话", type="secondary", use_container_width=True, key="modal_reject"):
            respond_to_call_invitation(conn, invitation_id, "rejected", st.session_state.username)
            st.session_state.show_invitation_modal = False
            st.session_state.pending_invitation = None
            st.rerun()
    
    st.markdown("</div></div>", unsafe_allow_html=True)

# 在线用户列表
st.header("👥 在线用户")
online_users = get_online_users(conn, exclude_user=st.session_state.username)

if online_users:
    cols = st.columns(3)
    for i, (user, dev_type, last_active) in enumerate(online_users):
        with cols[i % 3]:
            try:
                if isinstance(last_active, str):
                    last_active = datetime.strptime(last_active, '%Y-%m-%d %H:%M:%S.%f')
                time_diff = (datetime.now() - last_active).seconds
                status_text = "刚刚" if time_diff < 60 else f"{time_diff//60}分钟前"
            except:
                status_text = "未知"
            
            st.markdown(f'''
            <div class="user-card user-online">
                <strong>{user}</strong><br>
                <small>{dev_type.upper()} • {status_text}</small>
            </div>
            ''', unsafe_allow_html=True)
            
            call_col, chat_col = st.columns(2)
            with call_col:
                if st.button("视频通话", key=f"call_{user}", use_container_width=True):
                    # 创建通话邀请
                    call_id, room_id = create_call_invitation(conn, st.session_state.username, user)
                    st.session_state.current_chat = user
                    st.session_state.current_room = room_id
                    st.success(f"已向 {user} 发送通话邀请")
            with chat_col:
                if st.button("发起聊天", key=f"chat_{user}", use_container_width=True):
                    st.session_state.current_chat = user
else:
    st.info("暂无其他在线用户")

# 聊天窗口
if st.session_state.current_chat:
    st.header(f"💬 与 {st.session_state.current_chat} 的对话")
    
    # 初始化聊天记录
    if st.session_state.current_chat not in st.session_state.messages:
        st.session_state.messages[st.session_state.current_chat] = []
    
    # 显示聊天消息
    chat_container = st.container()
    with chat_container:
        for msg in st.session_state.messages[st.session_state.current_chat]:
            alignment = "right" if msg['from'] == st.session_state.username else "left"
            bg_color = "#007bff" if msg['from'] == st.session_state.username else "#f1f1f1"
            text_color = "white" if msg['from'] == st.session_state.username else "black"
            
            st.markdown(f"""
            <div style="text-align: {alignment}; margin: 5px 0;">
                <div style="background: {bg_color}; color: {text_color}; 
                           display: inline-block; padding: 8px 12px; 
                           border-radius: 18px; max-width: 70%;">
                    {msg['content']}
                </div>
                <div style="font-size: 0.8em; color: #666; margin-top: 2px;">
                    {msg['time']}
                </div>
            </div>
            """, unsafe_allow_html=True)
    
    # 消息输入和视频通话按钮
    col1, col2, col3 = st.columns([3, 1, 1])
    
    with col1:
        new_message = st.text_input("输入消息", label_visibility="collapsed", 
                                   placeholder="输入消息...", key="message_input")
    
    with col2:
        if st.button("发送", use_container_width=True, key="send_message") and new_message:
            timestamp = datetime.now().strftime("%H:%M")
            st.session_state.messages[st.session_state.current_chat].append({
                'from': st.session_state.username,
                'content': new_message,
                'time': timestamp
            })
            st.rerun()
    
    with col3:
        if st.button("视频通话", type="primary", use_container_width=True, key="start_video_chat"):
            # 创建通话邀请
            call_id, room_id = create_call_invitation(conn, st.session_state.username, st.session_state.current_chat)
            st.session_state.call_active = True
            st.session_state.current_room = room_id
            st.success(f"已向 {st.session_state.current_chat} 发起视频通话")

# 视频通话界面
if st.session_state.call_active:
    st.header("📹 视频通话中")
    st.info(f"房间ID: {st.session_state.current_room or '默认房间'}")
    
    # 检查媒体权限
    video_permission, audio_permission = check_media_permissions()
    
    if not video_permission or not audio_permission:
        st.error("需要摄像头和麦克风权限才能进行视频通话")
        request_media_permissions()
    else:
        # 视频通话布局
        col1, col2 = st.columns(2)
        
        with col1:
            st.subheader("本地视频")
            try:
                webrtc_ctx = webrtc_streamer(
                    key=f"video-{st.session_state.current_room or 'default'}",
                    mode=WebRtcMode.SENDRECV,
                    rtc_configuration=RTC_CONFIGURATION,
                    media_stream_constraints={
                        "video": True,
                        "audio": True,
                    },
                )
            except Exception as e:
                st.error(f"视频流初始化失败: {str(e)}")
                st.info("请确保浏览器已授予摄像头和麦克风权限")
        
        with col2:
            st.subheader("远程视频")
            if st.session_state.current_chat:
                st.info(f"等待 {st.session_state.current_chat} 接听...")
            else:
                st.info("等待对方接听...")
        
        # 通话控制按钮
        st.markdown("---")
        col1, col2, col3 = st.columns([1, 2, 1])
        
        with col2:
            if st.button("结束通话", type="secondary", use_container_width=True, key="end_call"):
                st.session_state.call_active = False
                st.session_state.current_room = None
                st.rerun()

# 设备信息显示
with st.expander("📱 设备信息"):
    device_info = f"""
    - **设备类型**: {detect_device_type().upper()}
    - **设备ID**: {get_or_create_device_id()}
    - **用户名**: {st.session_state.username}
    - **在线用户数**: {len(online_users) + 1}
    - **当前时间**: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
    """
    st.markdown(device_info)

# 使用说明
with st.expander("❓ 使用说明"):
    st.markdown("""
    ### 使用指南:
    1. **用户注册**: 在侧边栏输入用户名自动注册设备
    2. **用户列表**: 查看在线用户,点击用户发起通话或聊天
    3. **通话邀请**: 收到邀请时会弹出通知,可选择接听或拒绝
    4. **权限管理**: 首次使用需要授权摄像头和麦克风
    
    ### 通知功能:
    - 🔔 右上角通知铃铛显示未读通知数量
    - 📞 收到通话邀请时会自动弹出接听界面
    - 💬 聊天过程中可随时发起视频通话
    
    ### 设备适配:
    - 📱 **手机**: 垂直布局,优化触摸操作
    - 📟 **平板**: 自适应网格布局
    - 💻 **电脑**: 多列布局,功能完整展示
    """)

# 自动刷新页面以检查新通知
if st.session_state.get('username'):
    # 每10秒自动刷新一次以检查新通知
    if 'last_refresh' not in st.session_state:
        st.session_state.last_refresh = time.time()
    
    current_time = time.time()
    if current_time - st.session_state.last_refresh > 10:  # 10秒刷新一次
        st.session_state.last_refresh = current_time
        st.rerun()

# 应用退出时清理资源
def cleanup():
    if 'conn' in locals():
        conn.close()

import atexit
atexit.register(cleanup)

Logo

火山引擎开发者社区是火山引擎打造的AI技术生态平台,聚焦Agent与大模型开发,提供豆包系列模型(图像/视频/视觉)、智能分析与会话工具,并配套评测集、动手实验室及行业案例库。社区通过技术沙龙、挑战赛等活动促进开发者成长,新用户可领50万Tokens权益,助力构建智能应用。

更多推荐