File size: 3,808 Bytes
ec70da3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
import { pickToken, setRequestToken } from './auth.js';
import { solvePowChallengeForUpload } from './pow.js';
import { apiHeaders, getHeaders, proxiedFetch } from './headers.js';

const BASE_URL = 'https://chat.deepseek.com';

export async function uploadFile(fileBuffer, filename, mimeType, token) {
  const powResponse = await solvePowChallengeForUpload(token);

  const boundary = `----FormBoundary${Date.now()}`;
  const header = Buffer.from(
    `--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${filename}"\r\nContent-Type: ${mimeType}\r\n\r\n`
  );
  const footer = Buffer.from(`\r\n--${boundary}--\r\n`);
  const body = Buffer.concat([header, fileBuffer, footer]);

  const res = await proxiedFetch(`${BASE_URL}/api/v0/file/upload_file`, {
    method: 'POST',
    headers: {
      ...await apiHeaders(token, {
        'x-ds-pow-response': powResponse,
        'x-file-size': body.length.toString(),
        'x-model-type': 'vision',
        'content-type': `multipart/form-data; boundary=${boundary}`,
      }),
    },
    body,
  });

  const json = await res.json();
  const fileId = json.data?.biz_data?.id;
  if (!fileId) throw new Error(`File upload failed: ${JSON.stringify(json)}`);

  return fileId;
}

export async function waitForFileReady(fileId, token, maxAttempts = 30, intervalMs = 2000) {
  for (let i = 0; i < maxAttempts; i++) {
    const res = await proxiedFetch(`${BASE_URL}/api/v0/file/fetch_files?file_ids=${fileId}`, {
      headers: await getHeaders(token),
    });
    const json = await res.json();
    const files = json.data?.biz_data?.files;
    if (files && files.length > 0) {
      const status = files[0].status?.toUpperCase();
      if (status === 'SUCCESS' || status === 'PROCESSED' || status === 'READY' || status === 'DONE') {
        return files[0];
      }
      if (status === 'FAILED') {
        throw new Error(`File processing failed: ${files[0].error_code || 'unknown'}`);
      }
    }

    await new Promise(r => setTimeout(r, intervalMs));
  }

  throw new Error(`File not ready after ${maxAttempts * intervalMs / 1000}s`);
}

export async function uploadImageFromFile(fileBuffer, filename, mimeType, token) {
  const fileId = await uploadFile(fileBuffer, filename, mimeType, token);
  await waitForFileReady(fileId, token);
  return fileId;
}

const MIME_MAP = {
  'jpg': 'image/jpeg',
  'jpeg': 'image/jpeg',
  'png': 'image/png',
  'gif': 'image/gif',
  'webp': 'image/webp',
  'svg': 'image/svg+xml',
  'bmp': 'image/bmp',
};

function extFromDataUrl(dataUrl) {
  const match = dataUrl.match(/^data:(image\/\w+);/);
  if (!match) return { ext: 'png', mime: 'image/png' };
  const mime = match[1];
  const ext = mime.split('/')[1] || 'png';
  return { ext: ext === 'jpeg' ? 'jpg' : ext, mime };
}

export async function resolveImageToRefId(imageUrl, token) {
  let buffer, filename, mimeType;

  if (imageUrl.startsWith('data:')) {
    const { ext, mime } = extFromDataUrl(imageUrl);
    const base64 = imageUrl.split(',')[1];
    if (!base64) throw new Error('Invalid data URL: no base64 data');
    buffer = Buffer.from(base64, 'base64');
    filename = `image.${ext}`;
    mimeType = mime;
  } else {
    const res = await proxiedFetch(imageUrl);
    if (!res.ok) throw new Error(`Failed to download image: ${res.status}`);
    const contentType = res.headers.get('content-type') || 'image/png';
    buffer = Buffer.from(await res.arrayBuffer());
    const urlPath = new URL(imageUrl).pathname;
    const ext = urlPath.split('.').pop()?.toLowerCase() || 'png';
    const mappedMime = MIME_MAP[ext] || contentType;
    filename = `image.${mappedMime.split('/')[1] === 'jpeg' ? 'jpg' : (mappedMime.split('/')[1] || 'png')}`;
    mimeType = mappedMime;
  }

  return uploadImageFromFile(buffer, filename, mimeType, token);
}