tuna-agent 0.1.126 → 0.1.128

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,6 +10,7 @@ export interface AnalyzeVideoResult {
10
10
  duration_sec: number;
11
11
  language: string;
12
12
  transcript: string;
13
+ video_style: string;
13
14
  segments: Array<{
14
15
  start: number;
15
16
  end: number;
@@ -105,6 +105,30 @@ Voiceover at this moment: "${voiceoverText || '(none)'}"` },
105
105
  const data = await res.json();
106
106
  return data.choices?.[0]?.message?.content?.trim() || '';
107
107
  }
108
+ async function visionExtractStyle(frames) {
109
+ if (!OPENAI_KEY || frames.length === 0)
110
+ return '';
111
+ try {
112
+ const content = [
113
+ { type: 'text', text: 'Analyze these frames from a video and extract a concise visual style description (1-2 sentences). Focus on: animation style (cartoon, realistic, anime, etc.), color palette, lighting, character design approach (anthropomorphized objects, real people, etc.), and overall aesthetic.\n\nReturn ONLY the style description, nothing else.' },
114
+ ];
115
+ for (const b64 of frames) {
116
+ content.push({ type: 'image_url', image_url: { url: `data:image/jpeg;base64,${b64}` } });
117
+ }
118
+ const res = await fetch('https://api.openai.com/v1/chat/completions', {
119
+ method: 'POST',
120
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${OPENAI_KEY}` },
121
+ body: JSON.stringify({ model: 'gpt-4o-mini', max_tokens: 200, messages: [{ role: 'user', content }] }),
122
+ });
123
+ if (!res.ok)
124
+ return '';
125
+ const data = await res.json();
126
+ return (data.choices?.[0]?.message?.content || '').trim().replace(/\*\*/g, '');
127
+ }
128
+ catch {
129
+ return '';
130
+ }
131
+ }
108
132
  export async function analyzeVideo(url, onProgress) {
109
133
  const progress = onProgress || (() => { });
110
134
  const tmpDir = path.join(os.tmpdir(), 'tuna-analyze-' + crypto.randomBytes(6).toString('hex'));
@@ -218,10 +242,16 @@ export async function analyzeVideo(url, onProgress) {
218
242
  sceneResults.push(...results.filter((r) => r !== null));
219
243
  }
220
244
  const scenes = sceneResults.sort((a, b) => a.scene_number - b.scene_number);
245
+ // Extract video style from 3 sample frames
246
+ progress('Đang phân tích video style...');
247
+ const styleSamples = frameBuffers.slice(0, 3).map(f => f.buf.toString('base64'));
248
+ const video_style = await visionExtractStyle(styleSamples);
249
+ console.log('[analyze_video] Video style:', video_style.substring(0, 100));
221
250
  return {
222
251
  duration_sec: Math.round(durationSec),
223
252
  language: transcript.language || 'unknown',
224
253
  transcript: transcript.text || '',
254
+ video_style,
225
255
  segments: segments.map((s) => ({ start: s.start, end: s.end, text: s.text })),
226
256
  scenes,
227
257
  };
@@ -226,10 +226,31 @@ export function runClaude(options) {
226
226
  continue;
227
227
  try {
228
228
  const parsed = JSON.parse(line);
229
- if (!firstStreamEventTime && parsed.type === 'stream_event') {
230
- firstStreamEventTime = Date.now();
229
+ if (parsed.type === 'stream_event') {
231
230
  const evt = parsed.event;
232
- console.log(`[claude-cli] ⏱ first stream_event (${evt?.type}): ${firstStreamEventTime - spawnTime}ms after spawn`);
231
+ if (!firstStreamEventTime) {
232
+ firstStreamEventTime = Date.now();
233
+ console.log(`[claude-cli] ⏱ first stream_event (${evt?.type}): ${firstStreamEventTime - spawnTime}ms after spawn`);
234
+ }
235
+ // Log phase transitions
236
+ if (evt?.type === 'content_block_start') {
237
+ const contentBlock = evt.content_block;
238
+ console.log(`[claude-cli] ⏱ content_block_start (${contentBlock?.type}): ${Date.now() - spawnTime}ms after spawn`);
239
+ }
240
+ if (evt?.type === 'content_block_delta') {
241
+ const delta = evt.delta;
242
+ if (delta?.type === 'thinking_delta' && !proc._loggedThinking) {
243
+ proc._loggedThinking = true;
244
+ console.log(`[claude-cli] ⏱ thinking started: ${Date.now() - spawnTime}ms after spawn`);
245
+ }
246
+ if (delta?.type === 'text_delta' && !proc._loggedTextStart) {
247
+ proc._loggedTextStart = true;
248
+ console.log(`[claude-cli] ⏱ text output started: ${Date.now() - spawnTime}ms after spawn`);
249
+ }
250
+ }
251
+ }
252
+ if (parsed.type === 'result') {
253
+ console.log(`[claude-cli] ⏱ result received: ${Date.now() - spawnTime}ms after spawn`);
233
254
  }
234
255
  options.onStreamLine(parsed);
235
256
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tuna-agent",
3
- "version": "0.1.126",
3
+ "version": "0.1.128",
4
4
  "description": "Tuna Agent - Run AI coding tasks on your machine",
5
5
  "bin": {
6
6
  "tuna-agent": "dist/cli/index.js"