feat: 添加 Tauri Mac 原生应用支持
- 新增 mac-app/ 目录:Tauri 项目结构(Rust 外壳 + sidecar 进程管理)
- 前端添加 Tauri 环境检测,API 调用和 WebSocket 自动适配
- 后端支持 MEETING_DATA_DIR 环境变量指定数据目录
- 新增 /api/meetings/{id}/retranscribe 接口用于重新转录
- 修复轮询超时:2分钟→30分钟,适配长音频
- 添加 .gitignore 排除构建产物
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import axios from 'axios'
|
||||
|
||||
const API_BASE = '/api'
|
||||
const isTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window
|
||||
const API_BASE = isTauri ? 'http://localhost:8501/api' : '/api'
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: API_BASE,
|
||||
|
||||
@@ -87,7 +87,7 @@ export default function UploadPage() {
|
||||
|
||||
await pollMeetingStatus(result.meeting_id, (status) => {
|
||||
if (status === 'processing') {
|
||||
setTranscribeProgress(prev => Math.min(prev + 5, 90))
|
||||
setTranscribeProgress(prev => Math.min(prev + 2, 95))
|
||||
}
|
||||
})
|
||||
|
||||
@@ -111,7 +111,9 @@ export default function UploadPage() {
|
||||
|
||||
const connectWebSocket = () => {
|
||||
const sessionId = `realtime_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
|
||||
const ws = new WebSocket(`ws://localhost:8501/ws/realtime/${sessionId}`)
|
||||
const isTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window
|
||||
const wsHost = isTauri ? 'localhost:8501' : window.location.host
|
||||
const ws = new WebSocket(`ws://${wsHost}/ws/realtime/${sessionId}`)
|
||||
|
||||
ws.onopen = () => {
|
||||
setWsConnected(true)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { create } from 'zustand'
|
||||
|
||||
const isTauri = typeof window !== 'undefined' && '__TAURI_INTERNALS__' in window
|
||||
const API_BASE = isTauri ? 'http://localhost:8501' : ''
|
||||
|
||||
export const useAppStore = create((set, get) => ({
|
||||
// 会议列表
|
||||
meetings: [],
|
||||
@@ -38,7 +41,7 @@ export const useAppStore = create((set, get) => ({
|
||||
refreshMeetings: async () => {
|
||||
set({ loading: true, error: null })
|
||||
try {
|
||||
const res = await fetch('/api/meetings')
|
||||
const res = await fetch(`${API_BASE}/api/meetings`)
|
||||
const data = await res.json()
|
||||
set({
|
||||
meetings: data.meetings || [],
|
||||
@@ -59,7 +62,7 @@ export const useAppStore = create((set, get) => ({
|
||||
fetchMeetingDetail: async (meetingId) => {
|
||||
set({ loading: true, error: null })
|
||||
try {
|
||||
const res = await fetch(`/api/meetings/${meetingId}`)
|
||||
const res = await fetch(`${API_BASE}/api/meetings/${meetingId}`)
|
||||
const data = await res.json()
|
||||
set({ currentMeeting: data, loading: false })
|
||||
return data
|
||||
@@ -101,7 +104,7 @@ export const useAppStore = create((set, get) => ({
|
||||
set({ loading: false, isTranscribing: false })
|
||||
reject(new Error('Network error'))
|
||||
}
|
||||
xhr.open('POST', '/api/meetings/upload')
|
||||
xhr.open('POST', `${API_BASE}/api/meetings/upload`)
|
||||
xhr.send(formData)
|
||||
})
|
||||
} catch (err) {
|
||||
@@ -112,7 +115,7 @@ export const useAppStore = create((set, get) => ({
|
||||
|
||||
// 轮询会议状态直到完成
|
||||
pollMeetingStatus: async (meetingId, onStatusChange) => {
|
||||
const maxAttempts = 60 // 最多轮询60次(约2分钟)
|
||||
const maxAttempts = 600 // 最多轮询600次(约30分钟)
|
||||
let attempts = 0
|
||||
|
||||
const poll = async () => {
|
||||
@@ -122,7 +125,7 @@ export const useAppStore = create((set, get) => ({
|
||||
attempts++
|
||||
|
||||
try {
|
||||
const res = await fetch(`/api/meetings/${meetingId}`)
|
||||
const res = await fetch(`${API_BASE}/api/meetings/${meetingId}`)
|
||||
const data = await res.json()
|
||||
|
||||
onStatusChange?.(data.status)
|
||||
@@ -132,7 +135,7 @@ export const useAppStore = create((set, get) => ({
|
||||
}
|
||||
|
||||
// 继续轮询
|
||||
await new Promise(r => setTimeout(r, 2000)) // 每2秒轮询一次
|
||||
await new Promise(r => setTimeout(r, 3000)) // 每3秒轮询一次
|
||||
return poll()
|
||||
} catch (err) {
|
||||
throw err
|
||||
|
||||
Reference in New Issue
Block a user