skillfree 0.1.3 โ†’ 0.1.5

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "skillfree",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "๐Ÿฆž ไธ€ไธช API๏ผŒๆปก่ถณๆ‰€ๆœ‰้พ™่™พๆŠ€่ƒฝ้œ€ๆฑ‚",
5
5
  "main": "bin/skillfree.js",
6
6
  "bin": {
@@ -1,100 +1,338 @@
1
- const { post, postStream, BASE_URL } = require('../lib/client')
1
+ const { post, postStream, get, request, getApiKey, BASE_URL } = require('../lib/client')
2
2
  const fs = require('fs')
3
3
  const path = require('path')
4
4
 
5
+ // โ”€โ”€โ”€ ๅทฅๅ…ท๏ผšไธ‹่ฝฝๆ–‡ไปถๅนถไฟๅญ˜ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
6
+ async function downloadAndSave(url, output) {
7
+ const resp = await fetch(url)
8
+ if (!resp.ok) throw new Error(`ไธ‹่ฝฝๅคฑ่ดฅ: ${resp.status}`)
9
+ fs.writeFileSync(output, Buffer.from(await resp.arrayBuffer()))
10
+ console.log(`โœ… ๅทฒไฟๅญ˜ๅˆฐ ${output}`)
11
+ }
12
+
13
+ // โ”€โ”€โ”€ ๅทฅๅ…ท๏ผšๅฐ่ฃ… PCM ไธบ WAV โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
14
+ function pcmToWav(pcmBytes, sampleRate = 24000, channels = 1, bitsPerSample = 16) {
15
+ const dataSize = pcmBytes.length
16
+ const wav = Buffer.alloc(44 + dataSize)
17
+ wav.write('RIFF', 0); wav.writeUInt32LE(36 + dataSize, 4); wav.write('WAVE', 8)
18
+ wav.write('fmt ', 12); wav.writeUInt32LE(16, 16); wav.writeUInt16LE(1, 20)
19
+ wav.writeUInt16LE(channels, 22); wav.writeUInt32LE(sampleRate, 24)
20
+ wav.writeUInt32LE(sampleRate * channels * bitsPerSample / 8, 28)
21
+ wav.writeUInt16LE(channels * bitsPerSample / 8, 32); wav.writeUInt16LE(bitsPerSample, 34)
22
+ wav.write('data', 36); wav.writeUInt32LE(dataSize, 40)
23
+ pcmBytes.copy(wav, 44)
24
+ return wav
25
+ }
26
+
5
27
  async function pilot(flags) {
6
28
  const type = flags.type || 'chat'
7
29
  const prompt = flags.prompt || flags.text || ''
8
30
  const output = flags.output || null
31
+ const model = flags.model || null
9
32
 
10
- // ๆž„้€ ่ฏทๆฑ‚ไฝ“
11
- const body = { type, inputs: {} }
12
-
13
- switch (type) {
14
- case 'chat':
15
- body.model = flags.model
16
- body.inputs.messages = [{ role: 'user', content: prompt }]
17
- break
18
- case 'image':
19
- body.model = flags.model
20
- body.inputs.prompt = prompt
21
- if (flags.size) body.inputs.size = flags.size
22
- break
23
- case 'tts':
24
- body.model = flags.model
25
- body.inputs.text = prompt
26
- body.inputs.input = prompt
27
- body.inputs.voice = flags.voice || 'nova'
28
- break
29
- case 'stt':
30
- if (flags.file) {
31
- body.inputs.audio_data = fs.readFileSync(path.resolve(flags.file)).toString('base64')
32
- body.inputs.filename = path.basename(flags.file)
33
+ // โ”€โ”€ CHAT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
34
+ if (type === 'chat') {
35
+ if (!output) {
36
+ // ๆตๅผ่พ“ๅ‡บ
37
+ const res = await postStream('/chat/completions', {
38
+ model: model || 'DeepSeek-V3.2-Fast',
39
+ messages: [{ role: 'user', content: prompt }],
40
+ stream: true,
41
+ })
42
+ const reader = res.body.getReader()
43
+ const decoder = new TextDecoder()
44
+ let buf = ''
45
+ process.stdout.write('\n')
46
+ while (true) {
47
+ const { done, value } = await reader.read()
48
+ if (done) break
49
+ buf += decoder.decode(value, { stream: true })
50
+ const lines = buf.split('\n')
51
+ buf = lines.pop()
52
+ for (const line of lines) {
53
+ if (line.startsWith('data: ')) {
54
+ const chunk = line.slice(6)
55
+ if (chunk === '[DONE]') { process.stdout.write('\n'); return }
56
+ try {
57
+ const d = JSON.parse(chunk)
58
+ const text = d.choices?.[0]?.delta?.content
59
+ if (text) process.stdout.write(text)
60
+ } catch {}
61
+ }
62
+ }
33
63
  }
34
- break
35
- case 'video':
36
- body.model = flags.model
37
- body.inputs.prompt = prompt
38
- body.inputs.duration = flags.duration ? parseInt(flags.duration) : 5
39
- break
40
- case 'music':
41
- body.model = flags.model
42
- body.inputs.prompt = prompt
43
- break
44
- default:
45
- body.inputs.prompt = prompt
64
+ return
65
+ }
66
+ const result = await post('/chat/completions', {
67
+ model: model || 'DeepSeek-V3.2-Fast',
68
+ messages: [{ role: 'user', content: prompt }],
69
+ })
70
+ const text = result.choices?.[0]?.message?.content || JSON.stringify(result, null, 2)
71
+ if (output) { fs.writeFileSync(output, text); console.log(`โœ… ๅทฒไฟๅญ˜ๅˆฐ ${output}`) }
72
+ else console.log(text)
73
+ return
46
74
  }
47
75
 
48
- // ๆตๅผ่พ“ๅ‡บ๏ผˆchat ้ป˜่ฎคๆตๅผ๏ผ‰
49
- if (type === 'chat' && !output) {
50
- const res = await postStream('/chat/completions', {
51
- model: flags.model || 'DeepSeek-V3.2-Fast',
52
- messages: [{ role: 'user', content: prompt }],
53
- stream: true,
76
+ // โ”€โ”€ IMAGE โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
77
+ if (type === 'image') {
78
+ const imageModel = model || 'gemini-3.1-flash-image-preview'
79
+
80
+ // qwen-image-edit-plus๏ผšๅ›พ็‰‡็ผ–่พ‘๏ผŒ่ตฐ /v1/images/edits๏ผŒmultipart/form-data
81
+ if (imageModel.startsWith('qwen-image')) {
82
+ if (!flags.file) throw new Error('qwen-image-edit ้œ€่ฆ --file ๆŒ‡ๅฎšๅŽŸๅง‹ๅ›พ็‰‡่ทฏๅพ„')
83
+ const imgBuf = fs.readFileSync(path.resolve(flags.file))
84
+ const form = new FormData()
85
+ form.append('model', imageModel)
86
+ form.append('prompt', prompt)
87
+ form.append('size', flags.size || '1024x1024')
88
+ form.append('image', new Blob([imgBuf], { type: 'image/png' }), path.basename(flags.file))
89
+ const res = await request('/images/edits', { method: 'POST', body: form })
90
+ const data = await res.json()
91
+ if (data.error) throw new Error(data.error.message || JSON.stringify(data.error))
92
+ const url = data.data?.[0]?.url
93
+ if (!url) throw new Error('ๆœช่ฟ”ๅ›žๅ›พๅƒURL: ' + JSON.stringify(data).slice(0, 200))
94
+ if (output) await downloadAndSave(url, output)
95
+ else console.log('ๅ›พๅƒ URL:', url)
96
+ return
97
+ }
98
+
99
+ // doubao-seedream-5.0-lite / ๅ…ถไป–ๆ ‡ๅ‡†ๅ›พๅƒๆจกๅž‹
100
+ const res = await post('/images/generations', {
101
+ model: imageModel,
102
+ prompt,
103
+ n: 1,
104
+ size: flags.size || '1024x1024',
54
105
  })
106
+ if (res.error) throw new Error(res.error.message || JSON.stringify(res.error))
107
+ const url = res.data?.[0]?.url || res.data?.[0]?.b64_json
108
+ if (!url) throw new Error('ๆœช่ฟ”ๅ›žๅ›พๅƒๆ•ฐๆฎ: ' + JSON.stringify(res).slice(0, 200))
109
+ if (output) {
110
+ if (url.startsWith('http')) await downloadAndSave(url, output)
111
+ else { fs.writeFileSync(output, Buffer.from(url, 'base64')); console.log(`โœ… ๅทฒไฟๅญ˜ๅˆฐ ${output}`) }
112
+ } else {
113
+ console.log('ๅ›พๅƒ URL:', url)
114
+ }
115
+ return
116
+ }
117
+
118
+ // โ”€โ”€ TTS โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
119
+ if (type === 'tts') {
120
+ const ttsModel = model || 'speech-2.6-hd'
121
+ const text = prompt
122
+
123
+ if (ttsModel === 'speech-2.8-hd' || ttsModel === 'minimax-clone-lastversion') {
124
+ // ่ตฐ /v1/responses๏ผŒ่ฟ”ๅ›ž hex ้Ÿณ้ข‘
125
+ const res = await request('/responses', {
126
+ method: 'POST',
127
+ body: JSON.stringify({
128
+ model: ttsModel,
129
+ input: text,
130
+ stream: false,
131
+ voice_setting: {
132
+ voice_id: flags.voice || 'female-shaonv',
133
+ speed: 1, vol: 1, pitch: 0, emotion: 'fluent',
134
+ },
135
+ audio_setting: { sample_rate: 32000, bitrate: 128000, format: 'mp3', channel: 1 },
136
+ output_format: 'hex',
137
+ }),
138
+ })
139
+ const data = await res.json()
140
+ if (!data.data?.audio) throw new Error(JSON.stringify(data).slice(0, 200))
141
+ const audioBuf = Buffer.from(data.data.audio, 'hex')
142
+ if (output) { fs.writeFileSync(output, audioBuf); console.log(`โœ… ๅทฒไฟๅญ˜ๅˆฐ ${output}๏ผˆ${audioBuf.length} bytes๏ผ‰`) }
143
+ else console.log(`โœ… TTS ๆˆๅŠŸ๏ผŒๆ—ถ้•ฟ็บฆ ${(data.extra_info?.audio_length / 1000).toFixed(1)} ็ง’`)
55
144
 
56
- const reader = res.body.getReader()
57
- const decoder = new TextDecoder()
58
- let buf = ''
59
- process.stdout.write('\n')
60
- while (true) {
61
- const { done, value } = await reader.read()
62
- if (done) break
63
- buf += decoder.decode(value, { stream: true })
64
- const lines = buf.split('\n')
65
- buf = lines.pop()
66
- for (const line of lines) {
67
- if (line.startsWith('data: ')) {
68
- const chunk = line.slice(6)
69
- if (chunk === '[DONE]') { process.stdout.write('\n'); return }
70
- try {
71
- const d = JSON.parse(chunk)
72
- const text = d.choices?.[0]?.delta?.content
73
- if (text) process.stdout.write(text)
74
- } catch {}
145
+ } else if (ttsModel === 'gemini-2.5-pro-preview-tts' || ttsModel === 'gemini-2.5-flash-preview-tts') {
146
+ // Gemini TTS๏ผš่ตฐ /v1beta/models/:id:generateContent๏ผŒ่ฎค่ฏๆ—  Bearer
147
+ const apiKey = getApiKey()
148
+ const res = await fetch(BASE_URL.replace('skillfree.tech', 'dmxapi.cn') + '/v1beta/models/' + ttsModel + ':generateContent', {
149
+ method: 'POST',
150
+ headers: { 'Authorization': apiKey, 'Content-Type': 'application/json' },
151
+ body: JSON.stringify({
152
+ contents: [{ parts: [{ text }] }],
153
+ generationConfig: {
154
+ responseModalities: ['AUDIO'],
155
+ speechConfig: {
156
+ voiceConfig: { prebuiltVoiceConfig: { voiceName: flags.voice || 'Kore' } }
157
+ }
158
+ }
159
+ }),
160
+ })
161
+ const data = await res.json()
162
+ if (data.error) throw new Error(data.error.message || JSON.stringify(data.error))
163
+ const pcm = Buffer.from(data.candidates[0].content.parts[0].inlineData.data, 'base64')
164
+ const ext = (output || '').endsWith('.mp3') ? 'mp3' : 'wav'
165
+ const finalBuf = ext === 'wav' ? pcmToWav(pcm) : pcm
166
+ if (output) { fs.writeFileSync(output, finalBuf); console.log(`โœ… ๅทฒไฟๅญ˜ๅˆฐ ${output}๏ผˆ${finalBuf.length} bytes๏ผ‰`) }
167
+ else console.log(`โœ… Gemini TTS ๆˆๅŠŸ๏ผŒๆ—ถ้•ฟ็บฆ ${(pcm.length / (24000 * 2)).toFixed(1)} ็ง’`)
168
+
169
+ } else {
170
+ // ๆ ‡ๅ‡† OpenAI ๅ…ผๅฎน๏ผˆspeech-2.6-hd ็ญ‰๏ผ‰
171
+ const res = await request('/audio/speech', {
172
+ method: 'POST',
173
+ body: JSON.stringify({
174
+ model: ttsModel,
175
+ input: text,
176
+ voice: flags.voice || 'female-shaonv',
177
+ }),
178
+ })
179
+ if (res.status !== 200) {
180
+ const err = await res.json()
181
+ throw new Error(err.error?.message || JSON.stringify(err))
182
+ }
183
+ const buf = Buffer.from(await res.arrayBuffer())
184
+ if (output) { fs.writeFileSync(output, buf); console.log(`โœ… ๅทฒไฟๅญ˜ๅˆฐ ${output}๏ผˆ${buf.length} bytes๏ผ‰`) }
185
+ else console.log(`โœ… TTS ๆˆๅŠŸ๏ผŒ${buf.length} bytes`)
186
+ }
187
+ return
188
+ }
189
+
190
+ // โ”€โ”€ MUSIC โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
191
+ if (type === 'music') {
192
+ const musicModel = model || 'chirp-v5'
193
+
194
+ if (musicModel === 'chirp-v5') {
195
+ // Suno ๅผ‚ๆญฅๆŽฅๅฃ
196
+ const res = await request('/suno/submit/music', {
197
+ method: 'POST',
198
+ headers: { 'Accept': 'application/json' },
199
+ body: JSON.stringify({
200
+ gpt_description_prompt: prompt,
201
+ make_instrumental: true,
202
+ mv: 'chirp-v5',
203
+ notify_hook: '',
204
+ }),
205
+ })
206
+ const submitData = await res.json()
207
+ if (submitData.code !== 'success') throw new Error(submitData.message || JSON.stringify(submitData))
208
+ const taskId = submitData.data
209
+ console.log(`๐ŸŽต Suno ไปปๅŠกๅทฒๆไบค๏ผŒtask_id: ${taskId}๏ผŒ็ญ‰ๅพ…็”Ÿๆˆ๏ผˆ็บฆ 60-90 ็ง’๏ผ‰...`)
210
+
211
+ // ่ฝฎ่ฏข็ป“ๆžœ
212
+ for (let i = 0; i < 15; i++) {
213
+ await new Promise(r => setTimeout(r, 10000))
214
+ const poll = await request('/suno/fetch/' + taskId, { method: 'GET' })
215
+ const result = await poll.json()
216
+ if (result.code !== 'success') throw new Error(result.message || JSON.stringify(result))
217
+ const status = result.data?.status
218
+ const progress = result.data?.progress || '0%'
219
+ process.stdout.write(`\r็Šถๆ€: ${status} ่ฟ›ๅบฆ: ${progress} `)
220
+ if (status === 'SUCCESS') {
221
+ const songs = result.data?.data || []
222
+ process.stdout.write('\n')
223
+ console.log(`โœ… ็”ŸๆˆๆˆๅŠŸ๏ผๅ…ฑ ${songs.length} ้ฆ–`)
224
+ songs.forEach((s, i) => {
225
+ console.log(` ๆญŒๆ›ฒ${i+1}: ${s.title} (${Math.floor(s.duration/60)}m${Math.floor(s.duration%60)}s)`)
226
+ console.log(` ้Ÿณ้ข‘: ${s.audio_url}`)
227
+ })
228
+ // ๅฆ‚ๆžœๆŒ‡ๅฎš output๏ผŒไธ‹่ฝฝ็ฌฌไธ€้ฆ–
229
+ if (output && songs[0]?.audio_url) await downloadAndSave(songs[0].audio_url, output)
230
+ return
75
231
  }
232
+ if (status === 'FAILED') throw new Error('Suno ไปปๅŠกๅคฑ่ดฅ: ' + JSON.stringify(result.data))
76
233
  }
234
+ throw new Error('Suno ็”Ÿๆˆ่ถ…ๆ—ถ๏ผˆ150s๏ผ‰๏ผŒ่ฏทๆ‰‹ๅŠจๆŸฅ่ฏข task_id: ' + taskId)
235
+
236
+ } else {
237
+ // music-2.5๏ผˆMiniMax๏ผ‰๏ผŒ่ตฐ /v1/responses๏ผŒ้œ€่ฆ lyrics
238
+ const res = await request('/responses', {
239
+ method: 'POST',
240
+ body: JSON.stringify({
241
+ model: 'music-2.5',
242
+ input: prompt,
243
+ lyrics: flags.lyrics || `[verse]\n${prompt}`,
244
+ audio_setting: { sample_rate: 44100, bitrate: 256000, format: 'mp3' },
245
+ output_format: 'url',
246
+ stream: false,
247
+ }),
248
+ })
249
+ const data = await res.json()
250
+ if (data.error) throw new Error(data.error.message || JSON.stringify(data.error))
251
+ const audioUrl = data.output?.[0]?.content?.[0]?.audio
252
+ if (!audioUrl) throw new Error('ๆœช่ฟ”ๅ›ž้Ÿณ้ข‘: ' + JSON.stringify(data).slice(0, 200))
253
+ const duration = ((data.extra_info?.music_duration || 0) / 1000).toFixed(1)
254
+ console.log(`โœ… music-2.5 ็”ŸๆˆๆˆๅŠŸ๏ผๆ—ถ้•ฟ็บฆ ${duration} ็ง’`)
255
+ console.log('้Ÿณ้ข‘ URL:', audioUrl)
256
+ if (output) await downloadAndSave(audioUrl, output)
77
257
  }
258
+ }
259
+
260
+ // โ”€โ”€ OCR โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
261
+ if (type === 'ocr') {
262
+ let input = prompt
263
+ // ๅฆ‚ๆžœๆ˜ฏๆœฌๅœฐๆ–‡ไปถ่ทฏๅพ„๏ผŒ่ฏปๅ–ไธบ base64
264
+ if (flags.file && fs.existsSync(path.resolve(flags.file))) {
265
+ input = fs.readFileSync(path.resolve(flags.file)).toString('base64')
266
+ }
267
+ const res = await request('/responses', {
268
+ method: 'POST',
269
+ body: JSON.stringify({
270
+ model: model || 'hehe-tywd',
271
+ input,
272
+ parse_mode: 'auto',
273
+ dpi: 144,
274
+ page_start: 0,
275
+ page_count: 1000,
276
+ markdown_details: 1,
277
+ page_details: 0,
278
+ table_flavor: 'html',
279
+ }),
280
+ })
281
+ const data = await res.json()
282
+ if (data.code !== 200) throw new Error(data.message || JSON.stringify(data).slice(0, 200))
283
+ const markdown = data.result?.markdown || ''
284
+ if (output) { fs.writeFileSync(output, markdown); console.log(`โœ… OCR ็ป“ๆžœๅทฒไฟๅญ˜ๅˆฐ ${output}`) }
285
+ else console.log(markdown)
78
286
  return
79
287
  }
80
288
 
81
- // ้žๆตๅผ
82
- const result = await post('/pilot', body)
289
+ // โ”€โ”€ STT โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
290
+ if (type === 'stt') {
291
+ if (!flags.file) throw new Error('--file ๆ˜ฏๅฟ…้œ€็š„๏ผˆ้Ÿณ้ข‘ๆ–‡ไปถ่ทฏๅพ„๏ผ‰')
292
+ const audioBase64 = fs.readFileSync(path.resolve(flags.file)).toString('base64')
293
+ const result = await post('/v1/audio/transcriptions', {
294
+ model: 'whisper-1',
295
+ file: audioBase64,
296
+ filename: path.basename(flags.file),
297
+ })
298
+ const text = result.text || JSON.stringify(result, null, 2)
299
+ if (output) { fs.writeFileSync(output, text); console.log(`โœ… ๅทฒไฟๅญ˜ๅˆฐ ${output}`) }
300
+ else console.log(text)
301
+ return
302
+ }
83
303
 
84
- // ๅฆ‚ๆžœๆœ‰ๅช’ไฝ“ URL ไธ”ๆŒ‡ๅฎšไบ†่พ“ๅ‡บๆ–‡ไปถ๏ผŒไธ‹่ฝฝไฟๅญ˜
85
- const mediaUrl = result.image_url || result.video_url || result.audio_url
86
- || result.data?.[0]?.url || result.data?.[0]
87
- if (output && mediaUrl) {
88
- const resp = await fetch(typeof mediaUrl === 'string' ? mediaUrl : mediaUrl.url)
89
- fs.writeFileSync(output, Buffer.from(await resp.arrayBuffer()))
90
- console.log(`โœ… ๅทฒไฟๅญ˜ๅˆฐ ${output}`)
304
+ // โ”€โ”€ EMBEDDING โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
305
+ if (type === 'embedding') {
306
+ // doubao-embedding-vision๏ผš่ตฐ /v1/responses๏ผˆๅคšๆจกๆ€๏ผ‰๏ผŒinput ไธบๆ•ฐ็ป„
307
+ // ็ฎ€ๅ•ๆ–‡ๆœฌ embedding ไนŸๅฏ็”จ
308
+ const inputData = flags.file
309
+ ? [{ type: 'image_url', image_url: { url: 'data:image/png;base64,' + fs.readFileSync(path.resolve(flags.file)).toString('base64') } }]
310
+ : [{ type: 'text', text: prompt }]
311
+ const res = await request('/embeddings', {
312
+ method: 'POST',
313
+ body: JSON.stringify({
314
+ model: model || 'doubao-embedding-vision-251215',
315
+ input: inputData,
316
+ encoding_format: 'float',
317
+ dimensions: 1024,
318
+ sparse_embedding: { type: 'disabled' },
319
+ }),
320
+ })
321
+ const data = await res.json()
322
+ if (data.error) throw new Error(data.error.message || JSON.stringify(data.error))
323
+ const embedding = data.data?.[0]?.embedding
324
+ if (!embedding) throw new Error('ๆœช่ฟ”ๅ›žๅ‘้‡: ' + JSON.stringify(data).slice(0, 200))
325
+ console.log(`โœ… Embedding ๆˆๅŠŸ๏ผ็ปดๅบฆ: ${embedding.length}`)
326
+ if (output) {
327
+ fs.writeFileSync(output, JSON.stringify(embedding))
328
+ console.log(`ๅทฒไฟๅญ˜ๅˆฐ ${output}`)
329
+ } else {
330
+ console.log('ๅ‰5็ปด:', embedding.slice(0, 5))
331
+ }
91
332
  return
92
333
  }
93
334
 
94
- // ๆ–‡ๆœฌ่พ“ๅ‡บ
95
- const text = result.choices?.[0]?.message?.content
96
- || result.text || result.transcript || JSON.stringify(result, null, 2)
97
- console.log(text)
335
+ throw new Error(`ไธๆ”ฏๆŒ็š„็ฑปๅž‹: ${type}๏ผŒๅฏ้€‰: chat | image | tts | stt | music | ocr | video | embedding`)
98
336
  }
99
337
 
100
338
  module.exports = { pilot }
Binary file