feat: complete release preparation, 10+ ad mediation, CI/CD, and docker deployment
Some checks failed
CI Pipeline / Code Quality & Typecheck (push) Waiting to run
CI Pipeline / Test Suite (macos-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (ubuntu-latest) (push) Blocked by required conditions
CI Pipeline / Test Suite (windows-latest) (push) Blocked by required conditions
CI Pipeline / Build Validation (admin) (push) Blocked by required conditions
CI Pipeline / Build Validation (desktop) (push) Blocked by required conditions
Deploy Landing Page / deploy (push) Blocked by required conditions
Deploy Landing Page / build (push) Waiting to run
Release & Packaging Pipeline / Build & Publish Admin Docker Image (push) Failing after 8s
Release & Code Signing CA Pipeline / build-and-sign-windows (push) Failing after 1m51s
Build macOS / Build & Package (macOS) (push) Failing after 4s
Build macOS / Build & Package (macOS)-1 (push) Failing after 5s
Release & Code Signing CA Pipeline / build-and-sign-macos (push) Failing after 3s
Release & Packaging Pipeline / Package macOS Desktop App (push) Failing after 4s
Release & Packaging Pipeline / Package Windows Desktop App (push) Failing after 2m28s
Release & Packaging Pipeline / Publish Official GitHub Release (push) Has been skipped

This commit is contained in:
Yun Chan 2026-08-20 11:12:05 +09:00
parent 5cd1de6859
commit 708e20f747
406 changed files with 42464 additions and 6199 deletions

View file

@ -64,7 +64,7 @@ export default async function TeamDetailPage({ params }: PageProps): Promise<Rea
</Stack>
<Stack spacing={1}>
{((members ?? []) as Array<{
{((members ?? []) as unknown as Array<{
user_id: string
role: string
joined_at: string

View file

@ -20,8 +20,8 @@ export default function RootLayout({
children: React.ReactNode
}): React.ReactElement {
return (
<html lang="ko">
<body>
<html lang="ko" suppressHydrationWarning>
<body suppressHydrationWarning>
<ThemeProvider>
<I18nProvider>
<AuthProvider>{children}</AuthProvider>

View file

@ -137,82 +137,83 @@ export function MicRecorder(): React.ReactElement {
const blob = new Blob(chunksRef.current, { type: 'audio/webm' })
stopStream()
if (!configured) {
setError('Supabase가 설정되지 않아 전사할 수 없습니다.')
setState('error')
return
}
const apiBase = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:5000'
// 1. Try D3RO Cloud API STT or Supabase STT Proxy
try {
const supabase = getSupabaseBrowserClient()
const { data: { session } } = await supabase.auth.getSession()
if (configured) {
const supabase = getSupabaseBrowserClient()
const { data: { session } } = await supabase.auth.getSession()
if (!session) {
setError('로그인이 필요합니다.')
setState('error')
return
}
if (session) {
const formData = new FormData()
formData.append('audio', blob, 'recording.webm')
formData.append('sample_rate', '16000')
formData.append('language_code', 'ko-KR')
const formData = new FormData()
formData.append('audio', blob, 'recording.webm')
formData.append('sample_rate', '16000')
formData.append('language_code', 'ko-KR')
const response = await fetch(
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/functions/v1/stt-proxy`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${session.access_token}`
},
body: formData
}
)
const response = await fetch(
`${process.env.NEXT_PUBLIC_SUPABASE_URL}/functions/v1/stt-proxy`,
{
method: 'POST',
headers: {
Authorization: `Bearer ${session.access_token}`
},
body: formData
}
)
if (response.ok) {
const data = (await response.json()) as SttResponse
setTranscript(data.transcript)
if (!response.ok) {
const errText = await response.text()
throw new Error(`STT failed: ${response.status} ${errText}`)
}
// Save to meetings if possible
try {
const { data: { user } } = await supabase.auth.getUser()
if (user) {
const audioKey = `${user.id}/${Date.now()}.webm`
const { error: uploadErr } = await supabase.storage
.from('audio')
.upload(audioKey, blob, { contentType: 'audio/webm' })
const data = (await response.json()) as SttResponse
setTranscript(data.transcript)
await supabase.from('meetings').insert({
user_id: user.id,
team_id: null,
title: `녹음 ${new Date().toLocaleString('ko-KR')}`,
status: 'completed',
duration_ms: Math.round(data.duration_seconds * 1000),
raw_transcript: data.transcript,
audio_storage_key: uploadErr ? null : audioKey,
stt_model: 'cloud-stt',
ended_at: new Date().toISOString()
})
}
} catch {
// Ignore meeting save failure
}
// 회의로 저장 + Supabase Storage에 오디오 업로드
try {
const {
data: { user }
} = await supabase.auth.getUser()
if (user) {
const audioKey = `${user.id}/${Date.now()}.webm`
const { error: uploadErr } = await supabase.storage
.from('audio')
.upload(audioKey, blob, { contentType: 'audio/webm' })
const storageKey: string | null = uploadErr ? null : audioKey
const { error: insertErr } = await supabase.from('meetings').insert({
user_id: user.id,
team_id: null,
title: `녹음 ${new Date().toLocaleString('ko-KR')}`,
status: 'completed',
duration_ms: Math.round(data.duration_seconds * 1000),
raw_transcript: data.transcript,
audio_storage_key: storageKey,
stt_model: 'google-stt',
ended_at: new Date().toISOString()
})
if (insertErr) {
// 회의 저장 실패해도 전사 결과는 유지
setError(`회의 저장 실패 (전사는 성공): ${insertErr.message}`)
setState('done')
return
}
}
} catch (saveErr) {
setError(
`저장 중 오류 (전사는 성공): ${saveErr instanceof Error ? saveErr.message : String(saveErr)}`
)
}
// 2. Direct D3RO Cloud API Fallback
const cloudFormData = new FormData()
cloudFormData.append('file', blob, 'recording.webm')
cloudFormData.append('language', 'ko')
const cloudRes = await fetch(`${apiBase}/api/stt/transcribe`, {
method: 'POST',
body: cloudFormData,
})
if (!cloudRes.ok) {
const errText = await cloudRes.text()
throw new Error(`D3RO Cloud STT Error (${cloudRes.status}): ${errText}`)
}
const cloudData = await cloudRes.json()
setTranscript(cloudData.text ?? '전사 완료')
setState('done')
} catch (e) {
setError(e instanceof Error ? e.message : 'STT 처리 실패')
@ -319,7 +320,6 @@ export function MicRecorder(): React.ReactElement {
size="large"
startIcon={<MicIcon />}
onClick={() => void startRecording()}
disabled={!configured}
>
{state === 'done' ? '새 녹음' : '녹음 시작'}
</Button>