안녕하세요, HolySheep AI 기술 블로그입니다. 오늘은 전 세계 개발자들이 가장 많이 요청하는 음성 인식 기술 중 하나인 Whisper large-v3를 활용한 실시간 전사 구현 방법을 상세히 안내드리겠습니다.
저는 실제 영상 자막 생성, 회의록 자동화, 실시간 번역 시스템 등을 HolySheep AI 기반으로 구축한 경험이 있습니다. 이 튜토리얼에서는 브라우저 기반 실시간 전사부터 Python 백엔드 연동까지, production-ready한 코드를 공유하겠습니다.
왜 HolySheep AI인가?
AI API 비용 최적화는 모든 개발자의 핵심 관심사입니다. 월 1,000만 토큰 기준 주요 모델 비용을 비교해 보겠습니다:
| 모델 | providers | 월 1,000만 토큰 비용 |
|---|---|---|
| GPT-4.1 | OpenAI | $8.00 |
| Claude Sonnet 4.5 | Anthropic | $15.00 |
| Gemini 2.5 Flash | $2.50 | |
| DeepSeek V3.2 | DeepSeek | $0.42 |
| Whisper large-v3 | HolySheep AI | 경쟁력 있는 가격 |
지금 가입하면 무료 크레딧을 즉시 받으실 수 있으며, 해외 신용카드 없이도 로컬 결제로 시작할 수 있습니다.
실시간 전사 아키텍처 개요
Whisper large-v3 실시간 전사는 크게 세 가지 방식으로 구현할 수 있습니다:
- 브라우저 기반: MediaRecorder API 활용, 별도 설치 불필요
- Python 백엔드: PyAudio + 스트리밍, 서버 사이드 처리
- Node.js/API 연동: HolySheep AI REST API 직접 호출
저는 세 가지 모두の実전 경험을 바탕으로 각각의 장단점과 구현 방법을 설명드리겠습니다.
프로젝트 1: 브라우저 기반 실시간 전사
가장 접근하기 쉬운 방법은 브라우저에서 직접 마이크 입력을 캡처하여 HolySheep AI Whisper API로 전송하는 방식입니다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Whisper 실시간 전사 - HolySheep AI</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: 'Segoe UI', system-ui, sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
min-height: 100vh;
padding: 40px 20px;
color: #fff;
}
.container {
max-width: 900px;
margin: 0 auto;
}
h1 {
text-align: center;
margin-bottom: 10px;
font-size: 2rem;
}
.subtitle {
text-align: center;
color: #888;
margin-bottom: 40px;
}
.api-key-section {
background: rgba(255,255,255,0.1);
padding: 20px;
border-radius: 12px;
margin-bottom: 30px;
}
.api-key-section label {
display: block;
margin-bottom: 8px;
font-weight: 600;
}
.api-key-section input {
width: 100%;
padding: 12px;
border: 1px solid #333;
border-radius: 8px;
background: #0f0f23;
color: #fff;
font-family: monospace;
}
.controls {
display: flex;
gap: 15px;
margin-bottom: 30px;
flex-wrap: wrap;
}
button {
padding: 15px 30px;
font-size: 1rem;
border: none;
border-radius: 8px;
cursor: pointer;
font-weight: 600;
transition: all 0.3s;
}
.btn-start {
background: #4ade80;
color: #000;
}
.btn-start:hover { background: #22c55e; }
.btn-stop {
background: #ef4444;
color: #fff;
}
.btn-stop:hover { background: #dc2626; }
.btn-clear {
background: #3b82f6;
color: #fff;
}
.btn-clear:hover { background: #2563eb; }
.status {
padding: 12px;
border-radius: 8px;
margin-bottom: 20px;
text-align: center;
font-weight: 500;
}
.status.recording {
background: rgba(74, 222, 128, 0.2);
border: 1px solid #4ade80;
color: #4ade80;
}
.status.idle {
background: rgba(255,255,255,0.1);
color: #888;
}
.transcript-box {
background: rgba(0,0,0,0.3);
border-radius: 12px;
padding: 25px;
min-height: 400px;
max-height: 600px;
overflow-y: auto;
border: 1px solid #333;
}
.transcript-box h3 {
color: #4ade80;
margin-bottom: 15px;
}
.transcript-item {
padding: 12px;
margin-bottom: 10px;
background: rgba(255,255,255,0.05);
border-radius: 8px;
border-left: 3px solid #4ade80;
}
.transcript-item .timestamp {
color: #888;
font-size: 0.85rem;
margin-bottom: 5px;
}
.transcript-item .text {
font-size: 1.1rem;
line-height: 1.6;
}
.error {
color: #ef4444;
background: rgba(239,68,68,0.1);
border-left-color: #ef4444;
}
.stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
margin-bottom: 20px;
}
.stat-card {
background: rgba(255,255,255,0.05);
padding: 15px;
border-radius: 8px;
text-align: center;
}
.stat-card .value {
font-size: 1.5rem;
font-weight: bold;
color: #4ade80;
}
.stat-card .label {
color: #888;
font-size: 0.85rem;
}
</style>
</head>
<body>
<div class="container">
<h1>🎙️ Whisper large-v3 실시간 전사</h1>
<p class="subtitle">HolySheep AI API 활용 | 브라우저 기반</p>
<div class="api-key-section">
<label>🔑 HolySheep AI API Key</label>
<input type="password" id="apiKey" placeholder="hs-로 시작하는 API 키를 입력하세요">
</div>
<div class="stats">
<div class="stat-card">
<div class="value" id="requestCount">0</div>
<div class="label">전송 횟수</div>
</div>
<div class="stat-card">
<div class="value" id="totalDuration">0s</div>
<div class="label">총 녹음 시간</div>
</div>
<div class="stat-card">
<div class="value" id="avgLatency">0ms</div>
<div class="label">평균 지연 시간</div>
</div>
</div>
<div class="controls">
<button class="btn-start" id="startBtn" onclick="startRecording()">
▶️ 녹음 시작
</button>
<button class="btn-stop" id="stopBtn" onclick="stopRecording()" disabled>
⏹️ 녹음 중지
</button>
<button class="btn-clear" onclick="clearTranscript()">
🗑️ 초기화
</button>
</div>
<div class="status idle" id="status">마이크 권한을 허용하고 녹음을 시작하세요</div>
<div class="transcript-box" id="transcriptBox">
<h3>📝 전사 결과</h3>
<div id="transcripts"></div>
</div>
</div>
<script>
// HolySheep AI API 설정
const HOLYSHEEP_BASE_URL = 'https://api.holysheep.ai/v1';
// 상태 관리
let mediaRecorder = null;
let audioChunks = [];
let isRecording = false;
let recordingStartTime = null;
let stats = { requestCount: 0, latencies: [] };
let recordingInterval = null;
async function startRecording() {
const apiKey = document.getElementById('apiKey').value.trim();
if (!apiKey) {
alert('HolySheep AI API 키를 입력해주세요.');
return;
}
try {
// 마이크 권한 요청
const stream = await navigator.mediaDevices.getUserMedia({
audio: {
echoCancellation: true,
noiseSuppression: true,
sampleRate: 16000
}
});
// MediaRecorder 설정 (webm 형식, 16kHz)
mediaRecorder = new MediaRecorder(stream, {
mimeType: 'audio/webm;codecs=opus'
});
audioChunks = [];
mediaRecorder.ondataavailable = async (event) => {
if (event.data.size > 0) {
audioChunks.push(event.data);
}
};
mediaRecorder.onstop = async () => {
const audioBlob = new Blob(audioChunks, { type: 'audio/webm' });
await transcribeAudio(audioBlob, apiKey);
audioChunks = [];
};
// 5초마다 녹음 세그먼트 전송
recordingInterval = setInterval(() => {
if (mediaRecorder.state === 'recording') {
mediaRecorder.stop();
setTimeout(() => {
if (isRecording) {
mediaRecorder.start();
}
}, 100);
}
}, 5000);
mediaRecorder.start(1000); // 1초마다 데이터 수집
isRecording = true;
recordingStartTime = Date.now();
// UI 업데이트
document.getElementById('startBtn').disabled = true;
document.getElementById('stopBtn').disabled = false;
document.getElementById('status').textContent = '🎤 녹음 중... (5초마다 전사 전송)';
document.getElementById('status').className = 'status recording';
} catch (error) {
console.error('마이크 접근 오류:', error);
alert('마이크 접근이 거부되었습니다. 권한을 허용해주세요.');
}
}
async function transcribeAudio(audioBlob, apiKey) {
const startTime = Date.now();
try {
const formData = new FormData();
formData.append('file', audioBlob, 'audio.webm');
formData.append('model', 'whisper-large-v3');
formData.append('response_format', 'text');
formData.append('language', 'ko'); // 한국어指定
const response = await fetch(${HOLYSHEEP_BASE_URL}/audio/transcriptions, {
method: 'POST',
headers: {
'Authorization': Bearer ${apiKey}
},
body: formData
});
const latency = Date.now() - startTime;
stats.requestCount++;
stats.latencies.push(latency);
if (!response.ok) {
const error = await response.text();
throw new Error(API 오류: ${response.status} - ${error});
}
const text = await response.text();
// 전사 결과 표시
addTranscriptItem(text, latency);
// 통계 업데이트
updateStats();
} catch (error) {
console.error('전사 오류:', error);
addTranscriptItem([오류] ${error.message}, 0, true);
}
}
function stopRecording() {
if (mediaRecorder && mediaRecorder.state !== 'inactive') {
mediaRecorder.stop();
mediaRecorder.stream.getTracks().forEach(track => track.stop());
}
if (recordingInterval) {
clearInterval(recordingInterval);
}
isRecording = false;
// UI 업데이트
document.getElementById('startBtn').disabled = false;
document.getElementById('stopBtn').disabled = true;
document.getElementById('status').textContent = '녹음 중지됨';
document.getElementById('status').className = 'status idle';
}
function addTranscriptItem(text, latency, isError = false) {
const container = document.getElementById('transcripts');
const now = new Date();
const time = now.toLocaleTimeString('ko-KR');
const item = document.createElement('div');
item.className = transcript-item ${isError ? 'error' : ''};
item.innerHTML = `
<div class="timestamp">${time} | ${latency}ms</div>
<div class="text">${text}</div>
`;
container.appendChild(item);
// 자동 스크롤
const box = document.getElementById('transcriptBox');
box.scrollTop = box.scrollHeight;
}
function updateStats() {
const totalDuration = Math.round((Date.now() - recordingStartTime) / 1000);
const avgLatency = stats.latencies.length > 0
? Math.round(stats.latencies.reduce((a, b) => a + b, 0) / stats.latencies.length)
: 0;
document.getElementById('requestCount').textContent = stats.requestCount;
document.getElementById('totalDuration').textContent = ${totalDuration}s;
document.getElementById('avgLatency').textContent = ${avgLatency}ms;
}
function clearTranscript() {
document.getElementById('transcripts').innerHTML = '';
stats = { requestCount: 0, latencies: [] };
recordingStartTime = Date.now();
updateStats();
}
</script>
</body>
</html>
이 코드를 realtime-transcribe.html로 저장하고 브라우저에서 열면 즉시 사용할 수 있습니다.
프로젝트 2: Python 백엔드 실시간 전사 서버
production 환경에서는 Python 백엔드로 서버 사이드 처리가 더 안정적입니다. PyAudio를 사용한 스트리밍 전사 구현입니다.
# requirements.txt
pip install openai pyaudio wave threading requests
import pyaudio
import wave
import threading
import time
import requests
import base64
import json
from datetime import datetime
class HolySheepWhisperClient:
"""HolySheep AI Whisper large-v3 실시간 전사 클라이언트"""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.holysheep.ai/v1"
self.model = "whisper-large-v3"
# 오디오 설정
self.chunk_size = 1024
self.sample_rate = 16000
self.channels = 1
self.sample_format = pyaudio.paInt16
# 녹음 상태
self.is_recording = False
self.frames = []
self.lock = threading.Lock()
# 통계
self.total_requests = 0
self.total_latency = 0
def transcribe_audio(self, audio_data: bytes) -> dict:
"""HolySheep AI Whisper API로 오디오 데이터 전사"""
start_time = time.time()
files = {
'file': ('audio.wav', audio_data, 'audio/wav'),
'model': (None, self.model),
'response_format': (None, 'verbose_json'),
'language': (None, 'ko'),
}
headers = {
'Authorization': f'Bearer {self.api_key}'
}
try:
response = requests.post(
f'{self.base_url}/audio/transcriptions',
files=files,
headers=headers,
timeout=30
)
latency = (time.time() - start_time) * 1000 # ms
self.total_requests += 1
self.total_latency += latency
if response.status_code == 200:
result = response.json()
return {
'success': True,
'text': result.get('text', ''),
'latency_ms': round(latency, 2),
'language': result.get('language', 'unknown')
}
else:
return {
'success': False,
'error': f'HTTP {response.status_code}: {response.text}',
'latency_ms': round(latency, 2)
}
except requests.exceptions.Timeout:
return {'success': False, 'error': '요청 시간 초과'}
except Exception as e:
return {'success': False, 'error': str(e)}
def save_audio_chunk(self, frames: list) -> bytes:
"""오디오 프레임을 WAV 바이트로 변환"""
import io
import struct
audio_data = b''.join(frames)
# WAV 헤더 생성
buffer = io.BytesIO()
with wave.open(buffer, 'wb') as wf:
wf.setnchannels(self.channels)
wf.setsampwidth(2) # 16-bit
wf.setframerate(self.sample_rate)
wf.writeframes(audio_data)
return buffer.getvalue()
def recording_worker(self, p: pyaudio.PyAudio, stream):
"""백그라운드 녹음 워커"""
while self.is_recording:
try:
data = stream.read(self.chunk_size, exception_on_overflow=False)
with self.lock:
self.frames.append(data)
except Exception as e:
print(f"녹음 오류: {e}")
break
def transcription_worker(self, interval_seconds: int = 5):
"""백그라운드 전사 워커 - 지정된 간격으로 전사 실행"""
while self.is_recording:
time.sleep(interval_seconds)
if not self.is_recording:
break
with self.lock:
if len(self.frames) == 0:
continue
current_frames = self.frames.copy()
self.frames = []
# 오디오 데이터 변환
audio_bytes = self.save_audio_chunk(current_frames)
# 전사 실행
result = self.transcribe_audio(audio_bytes)
# 결과 출력
timestamp = datetime.now().strftime('%H:%M:%S')
if result['success']:
print(f"\n[{timestamp}] ⏱️ {result['latency_ms']}ms")
print(f" 📝 {result['text']}")
print(f" 📊 평균 지연: {round(self.total_latency / self.total_requests, 2)}ms")
else:
print(f"\n[{timestamp}] ❌ 오류: {result['error']}")
def start(self, duration_seconds: int = 60):
"""녹음 및 전사 시작"""
print(f"🎙️ HolySheep AI Whisper large-v3 실시간 전사 시작")
print(f" 모델: {self.model}")
print(f" 샘플레이트: {self.sample_rate}Hz")
print(f" 예상 소요 시간: {duration_seconds}초")
print("-" * 50)
# PyAudio 초기화
p = pyaudio.PyAudio()
try:
# 오디오 스트림 열기
stream = p.open(
format=self.sample_format,
channels=self.channels,
rate=self.sample_rate,
input=True,
frames_per_buffer=self.chunk_size
)
self.is_recording = True
self.frames = []
# 워커 스레드 시작
recording_thread = threading.Thread(
target=self.recording_worker,
args=(p, stream)
)
transcription_thread = threading.Thread(
target=self.transcription_worker,
args=(5,) # 5초마다 전사
)
recording_thread.start()
transcription_thread.start()
print("🎤 녹음 중... Ctrl+C로 종료\n")
# 메인 스레드에서 duration 동안 대기
start_time = time.time()
while time.time() - start_time < duration_seconds:
time.sleep(1)
elapsed = int(time.time() - start_time)
print(f"\r⏱️ 경과 시간: {elapsed}s / {duration_seconds}s", end='', flush=True)
except KeyboardInterrupt:
print("\n\n⏹️ 사용자에 의해 중단됨")
finally:
self.is_recording = False
stream.stop_stream()
stream.close()
p.terminate()
print(f"\n\n📊 최종 통계")
print(f" 총 전사 요청: {self.total_requests}회")
if self.total_requests > 0:
print(f" 평균 응답 지연: {round(self.total_latency / self.total_requests, 2)}ms")
def main():
# HolySheep AI API 키 설정
API_KEY = "YOUR_HOLYSHEEP_API_KEY" # hs-로 시작하는 키
if API_KEY == "YOUR_HOLYSHEEP_API_KEY":
print("⚠️ HolySheep AI API 키를 설정해주세요.")
print(" https://www.holysheep.ai/register 에서 가입하세요.")
return
# 클라이언트 생성 및 시작
client = HolySheepWhisperClient(API_KEY)
client.start(duration_seconds=60) # 60초 녹음
if __name__ == "__main__":
main()
실행 전에 반드시 필요한 패키지를 설치하세요:
pip install openai pyaudio requests
macOS의 경우 추가 설치
brew install portaudio
pip install pyaudio
Ubuntu/Debian의 경우
sudo apt-get install portaudio19-dev
pip install pyaudio
프로젝트 3: Node.js + Express 실시간 전사 API
Node.js 환경에서 HolySheep AI Whisper API를 호출하는 REST API 서버를 구축합니다.
// package.json dependencies
// npm install express multer node-fetch form-data cors dotenv
require('dotenv').config();
const express = require('express');
const multer = require('multer');
const cors = require('cors');
const FormData = require('form-data');
const fetch = require('node-fetch');
const path = require('path');
const app = express();
const PORT = process.env.PORT || 3000;
// HolySheep AI 설정
const HOLYSHEEP_BASE_URL = 'https://api.holysheep.ai/v1';
// 미들웨어
app.use(cors());
app.use(express.json());
app.use(express.static('public'));
// 파일 업로드 설정
const storage = multer.memoryStorage();
const upload = multer({
storage: storage,
limits: { fileSize: 25 * 1024 * 1024 }, // 25MB 제한
fileFilter: (req, file, cb) => {
const allowedTypes = ['audio/wav', 'audio/mpeg', 'audio/mp3', 'audio/webm', 'audio/ogg', 'audio/m4a'];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error('지원되지 않는 오디오 형식입니다.'));
}
}
});
// 통계 저장
const stats = {
totalRequests: 0,
totalLatency: 0,
requestHistory: []
};
/**
* HolySheep AI Whisper API 호출
*/
async function transcribeWithHolySheep(audioBuffer, filename) {
const startTime = Date.now();
try {
const formData = new FormData();
formData.append('file', audioBuffer, {
filename: filename,
contentType: 'audio/webm'
});
formData.append('model', 'whisper-large-v3');
formData.append('response_format', 'verbose_json');
formData.append('language', 'ko');
formData.append('temperature', '0');
formData.append('timestamp_granularities[]', 'word');
const apiKey = process.env.HOLYSHEEP_API_KEY || 'YOUR_HOLYSHEEP_API_KEY';
const response = await fetch(${HOLYSHEEP_BASE_URL}/audio/transcriptions, {
method: 'POST',
headers: {
'Authorization': Bearer ${apiKey}
},
body: formData
});
const latency = Date.now() - startTime;
if (!response.ok) {
const errorText = await response.text();
throw new Error(API 오류: ${response.status} - ${errorText});
}
const result = await response.json();
// 통계 업데이트
stats.totalRequests++;
stats.totalLatency += latency;
stats.requestHistory.push({
timestamp: new Date().toISOString(),
latency,
textLength: result.text?.length || 0
});
return {
success: true,
text: result.text,
language: result.language,
duration: result.duration,
words: result.words,
latency_ms: latency,
avg_latency_ms: Math.round(stats.totalLatency / stats.totalRequests)
};
} catch (error) {
return {
success: false,
error: error.message,
latency_ms: Date.now() - startTime
};
}
}
/**
* POST /api/transcribe
* 오디오 파일 실시간 전사
*/
app.post('/api/transcribe', upload.single('audio'), async (req, res) => {
if (!req.file) {
return res.status(400).json({
success: false,
error: '오디오 파일이 제공되지 않았습니다.'
});
}
console.log(📥 파일 수신: ${req.file.originalname} (${req.file.size} bytes));
const result = await transcribeWithHolySheep(
req.file.buffer,
req.file.originalname
);
if (result.success) {
console.log(✅ 전사 완료: ${result.latency_ms}ms | ${result.text.substring(0, 50)}...);
} else {
console.log(❌ 전사 실패: ${result.error});
}
res.json(result);
});
/**
* POST /api/transcribe-stream
* 청크 단위 스트리밍 전사 (실시간용)
*/
app.post('/api/transcribe-stream', async (req, res) => {
const chunks = [];
req.on('data', chunk => chunks.push(chunk));
req.on('end', async () => {
const audioBuffer = Buffer.concat(chunks);
const result = await transcribeWithHolySheep(audioBuffer, 'stream.webm');
res.json(result);
});
req.on('error', (err) => {
res.status(500).json({ success: false, error: err.message });
});
});
/**
* GET /api/stats
* API 사용 통계 조회
*/
app.get('/api/stats', (req, res) => {
const avgLatency = stats.totalRequests > 0
? Math.round(stats.totalLatency / stats.totalRequests)
: 0;
res.json({
totalRequests: stats.totalRequests,
avgLatency_ms: avgLatency,
recentRequests: stats.requestHistory.slice(-10)
});
});
/**
* GET /api/health
* 헬스 체크
*/
app.get('/api/health', (req, res) => {
res.json({
status: 'healthy',
service: 'HolySheep Whisper API',
timestamp: new Date().toISOString()
});
});
// 오류 처리 미들웨어
app.use((error, req, res, next) => {
if (error instanceof multer.MulterError) {
if (error.code === 'LIMIT_FILE_SIZE') {
return res.status(400).json({
success: false,
error: '파일 크기가 25MB를 초과했습니다.'
});
}
}
res.status(500).json({ success: false, error: error.message });
});
// 서버 시작
app.listen(PORT, () => {
console.log(`
╔══════════════════════════════════════════════════════╗
║ 🎙️ HolySheep AI Whisper large-v3 API Server ║
╠══════════════════════════════════════════════════════╣
║ 서버 실행 중: http://localhost:${PORT} ║
║ 전사 엔드포인트: POST /api/transcribe ║
║ 헬스 체크: GET /api/health ║
║ 통계 조회: GET /api/stats ║
╚══════════════════════════════════════════════════════╝
`);
if (!process.env.HOLYSHEEP_API_KEY || process.env.HOLYSHEEP_API_KEY === 'YOUR_HOLYSHEEP_API_KEY') {
console.warn('⚠️ HolySheep API 키가 설정되지 않았습니다.');
console.warn(' .env 파일에 HOLYSHEEP_API_KEY를 설정하세요.');
console.warn(' https://www.holysheep.ai/register 에서 가입하세요.');
}
});
프론트엔드 HTML 파일도 함께 제공합니다:
<!-- public/index.html -->
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HolySheep Whisper API - 실시간 전사 데모</title>
<style>
body {
font-family: system-ui, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background: #0a0a0a;
color: #fff;
}
.header {
text-align: center;
margin-bottom: 30px;
}
.header h1 { color: #4ade80; }
.api-section {
background: #1a1a1a;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.api-section label {
display: block;
margin-bottom: 8px;
color: #888;
}
.api-section input {
width: 100%;
padding: 12px;
background: #0f0f0f;
border: 1px solid #333;
color: #fff;
border-radius: 6px;
}
.controls {
display: flex;
gap: 10px;
margin: 20px 0;
}
button {
padding: 12px 24px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: 600;
}
.record-btn { background: #4ade80; color: #000; }
.stop-btn { background: #ef4444; color: #fff; }
.transcript {
background: #1a1a1a;
padding: 20px;
border-radius: 8px;
min-height: 300px;
max-height: 500px;
overflow-y: auto;
}
.result-item {
padding: 12px;
margin-bottom: 10px;
background: #252525;
border-radius: 6px;
border-left: 3px solid #4ade80;
}
.result-item.error { border-left-color: #ef4444; }
.timestamp { color: #888; font-size: 0.85rem