yesilcam-studios 0.1.0 → 0.3.1

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/src/wait.js ADDED
@@ -0,0 +1,167 @@
1
+ // Uzun işler için ortak yoklama. Her "tür" bir durum ucunu okuyup ortak biçime çevirir:
2
+ // { kind, id, status, done, ok, needs_action?, progress, stage, url, poster, asset_id, error, tokens_charged, refunded }
3
+ const J = (v) => { if (v && typeof v === 'object') return v; try { return JSON.parse(v || '{}') } catch { return {} } }
4
+ const num = (v) => (v == null || v === '' ? null : Number(v))
5
+ const pick = (o, ...keys) => { for (const k of keys) { const v = k.split('.').reduce((a, p) => (a == null ? a : a[p]), o); if (v != null && v !== '') return v } return null }
6
+ const split2 = (id) => { const [a, b] = String(id).split(':'); return [a, b] }
7
+
8
+ const KINDS = {
9
+ job: {
10
+ label: 'Film Studio / Image Studio / Fashion job',
11
+ fetch: (c, id) => c.call('aistudio-job-status', { job_id: Number(id) }).then((r) => r.job),
12
+ norm: (j) => {
13
+ const ip = J(j?.input_params)
14
+ const awaiting = j?.status === 'waiting' && (ip.awaiting_approval == 1 || ip.awaiting_approval === true) && Boolean(ip.source_image_url)
15
+ return { status: awaiting ? 'awaiting_approval' : j?.status, done: awaiting || ['succeeded', 'failed', 'cancelled', 'canceled', 'refunded', 'done'].includes(j?.status),
16
+ ok: j?.status === 'succeeded' || j?.status === 'done', needs_action: awaiting ? 'Approve or reject the first frame: yesilcam_first_frame (approve|reject|regen)' : null,
17
+ progress: num(j?.render_progress), url: pick(j, 'result_asset.spaces_url'), poster: pick(j, 'result_asset.thumb_url'), asset_id: pick(j, 'result_asset.id', 'result_asset_id'),
18
+ error: j?.error_code || null, tokens_charged: num(j?.tokens_charged), refunded: Boolean(j?.refunded_at) }
19
+ },
20
+ },
21
+ influencer_job: {
22
+ label: 'AI Influencer video or create job',
23
+ fetch: (c, id) => c.call('ys-influencer-job', { job_id: Number(id) }).then((r) => r.job),
24
+ norm: (j) => ({ status: j?.status, done: ['ready', 'failed'].includes(j?.status), ok: j?.status === 'ready', progress: num(j?.progress), stage: j?.stage || null,
25
+ url: pick(j, 'result.url'), poster: pick(j, 'result.poster'), asset_id: pick(j, 'result.asset_id'), error: j?.error_code || null, tokens_charged: num(j?.tokens_charged), refunded: Boolean(j?.refunded) }),
26
+ },
27
+ influencer: {
28
+ label: 'AI Influencer character set',
29
+ fetch: (c, id) => c.call('ys-influencer-get', { influencer_id: Number(id) }).then((r) => r.influencer),
30
+ norm: (i) => ({ status: i?.status, done: ['ready', 'failed'].includes(i?.status), ok: i?.status === 'ready', progress: num(pick(i, 'job.progress')), stage: pick(i, 'job.stage'),
31
+ url: i?.cover_url || null, error: pick(i, 'job.error_code'), refunded: Boolean(pick(i, 'job.refunded')) }),
32
+ },
33
+ edit: {
34
+ label: 'Long video (edit studio)',
35
+ fetch: (c, id) => c.call('ys-influencer-edit', { op: 'get', edit_id: Number(id) }).then((r) => r.edit),
36
+ norm: (e) => ({ status: e?.status, done: !['planning', 'framing', 'producing'].includes(e?.status), ok: ['draft', 'ready', 'partial'].includes(e?.status),
37
+ needs_action: e?.status === 'paused' ? 'Paused (balance). Top up, then: yesilcam_longvideo_resume' : (e?.status === 'draft' ? 'Plan ready. Review/edit the storyboard, then quote and produce.' : null),
38
+ progress: num(pick(e, 'job.progress', 'plan_job.progress', 'frames_job.progress')), url: pick(e, 'result.url'), poster: e?.cover || null, error: pick(e, 'job.error_code', 'plan_job.error_code') }),
39
+ },
40
+ anim: {
41
+ label: 'Animation Studio project',
42
+ fetch: (c, id) => c.call('aistudio-anim-get', { project_id: Number(id) }),
43
+ norm: (p) => ({ status: p?.status, done: ['ready', 'failed'].includes(p?.status), ok: p?.status === 'ready', progress: num(p?.progress), stage: p?.stage || null, poster: p?.cover || null, error: p?.error_code || null, tokens_charged: num(p?.tokens_charged) }),
44
+ },
45
+ anim_render: {
46
+ label: 'Animation render (id = projectId:renderId)',
47
+ fetch: async (c, id) => { const [p, r] = split2(id); const x = await c.call('aistudio-anim-renders', { project_id: Number(p) }); return (x.renders || []).find((k) => String(k.id) === String(r)) || null },
48
+ norm: (r) => ({ status: r?.status, done: ['done', 'failed'].includes(r?.status), ok: r?.status === 'done', progress: num(r?.progress), url: r?.url || null, error: r?.error || null, tokens_charged: num(r?.tokens_charged) }),
49
+ },
50
+ motion: {
51
+ label: 'Animation › Custom (image + camera path)',
52
+ fetch: (c, id) => c.call('aistudio-motion-get', { job_id: Number(id) }).then((r) => r.job),
53
+ norm: (j) => ({ status: j?.status, done: ['ready', 'failed'].includes(j?.status), ok: j?.status === 'ready', progress: num(j?.progress), stage: j?.stage || null,
54
+ url: pick(j, 'result.url'), poster: pick(j, 'result.poster'), asset_id: pick(j, 'result.asset_id'), error: j?.error_code || null, refunded: Boolean(j?.refunded) }),
55
+ },
56
+ inkmation: {
57
+ label: 'Inkmation project (script + voice)',
58
+ fetch: (c, id) => c.call('aistudio-inkmation-get', { project_id: Number(id) }),
59
+ norm: (p) => ({ status: p?.status, done: p?.status === 'failed' || (p?.status === 'ready' && p?.audio_status !== 'voicing'), ok: p?.status === 'ready', stage: p?.audio_status ? `audio: ${p.audio_status}` : null, error: p?.error_code || p?.audio_error || null }),
60
+ },
61
+ inkmation_render: {
62
+ label: 'Inkmation render (id = projectId:renderId)',
63
+ fetch: async (c, id) => { const [p, r] = split2(id); const x = await c.call('aistudio-inkmation-renders', { project_id: Number(p) }); return (x.renders || []).find((k) => String(k.id) === String(r)) || null },
64
+ norm: (r) => ({ status: r?.status, done: ['done', 'failed'].includes(r?.status), ok: r?.status === 'done', progress: num(r?.progress), url: r?.url || null, error: r?.error || null }),
65
+ },
66
+ design: {
67
+ label: 'Design Studio design (generate / add detail)',
68
+ fetch: (c, id) => c.call('ys-design-get', { design_id: Number(id) }).then((r) => r.design),
69
+ norm: (d) => ({ status: d?.status, done: d?.status !== 'working', ok: d?.status === 'ready', progress: num(pick(d, 'job.progress')), stage: pick(d, 'job.stage'), poster: d?.thumb_url || null, error: pick(d, 'job.error_code'), tokens_charged: num(pick(d, 'job.tokens_charged')) }),
70
+ },
71
+ design_export: {
72
+ label: 'Design Studio export',
73
+ fetch: (c, id) => c.call('ys-design-export-get', { export_id: Number(id) }).then((r) => r.export),
74
+ norm: (x) => ({ status: x?.status, done: ['ready', 'failed'].includes(x?.status), ok: x?.status === 'ready', url: x?.url || null, asset_id: x?.asset_id || null, error: x?.error_code || null, tokens_charged: num(x?.tokens_charged) }),
75
+ },
76
+ website: {
77
+ label: 'Website Studio site',
78
+ fetch: (c, id) => c.call('ys-website-get', { site_id: Number(id) }).then((r) => r.site),
79
+ norm: (s) => ({ status: s?.status, done: ['ready', 'failed'].includes(s?.status), ok: s?.status === 'ready', progress: num(s?.progress), url: s?.public_url || null, poster: s?.cover_url || null, zip_url: s?.zip_url || null, error: s?.error_code || null }),
80
+ },
81
+ promo: {
82
+ label: 'Website Promo video',
83
+ fetch: (c, id) => c.call('sitefilm-get', { project_id: Number(id) }).then((r) => r.project || r),
84
+ norm: (p) => ({ status: p?.status, done: ['review', 'ready', 'failed'].includes(p?.status), ok: ['review', 'ready'].includes(p?.status),
85
+ needs_action: p?.status === 'review' ? 'Script ready for review. Edit (promo save-script) or produce (yesilcam_promo_produce).' : null,
86
+ url: pick(p, 'result.video_url'), poster: pick(p, 'result.poster_url'), error: p?.error_code || null }),
87
+ },
88
+ assemble: {
89
+ label: 'Assemble / Disassemble video',
90
+ fetch: (c, id) => c.call('aistudio-assemble-get', { project_id: Number(id) }).then((r) => r.project),
91
+ norm: (p) => ({ status: p?.status, done: ['ready', 'failed'].includes(p?.status), ok: p?.status === 'ready', progress: num(p?.progress), stage: p?.stage || null,
92
+ url: pick(p, 'result.video_url'), poster: pick(p, 'result.poster_url'), asset_id: pick(p, 'result.asset_id'), error: p?.error_code || null }),
93
+ },
94
+ enhance: {
95
+ label: 'Product photo enhance',
96
+ fetch: (c, id) => c.call('aistudio-assemble-enhance-get', { job_id: Number(id) }),
97
+ norm: (j) => ({ status: j?.status, done: ['ready', 'failed'].includes(j?.status), ok: j?.status === 'ready', progress: num(j?.progress), url: pick(j, 'result.url'), asset_id: pick(j, 'result.asset_id'), error: j?.error_code || null }),
98
+ },
99
+ maket_design: {
100
+ label: 'Maket furnishing / landscape design',
101
+ fetch: (c, id) => c.call('maket-design-get', { jobId: String(id) }),
102
+ norm: (j) => ({ status: j?.status, done: ['ready', 'failed'].includes(j?.status), ok: j?.status === 'ready', progress: num(j?.progress), error: j?.error || null, tokens_charged: num(j?.charged) }),
103
+ },
104
+ maket_render: {
105
+ label: 'Maket photoreal render',
106
+ fetch: (c, id) => c.call('maket-render-get', { jobId: String(id) }),
107
+ norm: (j) => ({ status: j?.status, done: ['ready', 'failed'].includes(j?.status), ok: j?.status === 'ready', progress: num(j?.progress), url: pick(j, 'result.url'), error: j?.error || null, tokens_charged: num(j?.charged) }),
108
+ },
109
+ flow: {
110
+ label: 'Film Studio flow / Story to Film',
111
+ fetch: (c, id) => c.call('aistudio-flow-get', { flow_id: Number(id) }),
112
+ norm: (r) => { const f = r?.flow || {}; return { status: f.status, done: !['directing', 'casting', 'storyboarding', 'producing', 'assembling'].includes(f.status), ok: ['ready', 'completed'].includes(f.status),
113
+ needs_action: f.status === 'ready' ? 'Storyboard ready. Estimate and produce: yesilcam_flow_produce' : null,
114
+ url: pick(r, 'final_asset.spaces_url'), poster: pick(r, 'final_asset.thumb_url'), asset_id: f.final_asset_id || null, error: f.error_code || null } },
115
+ },
116
+ bible: {
117
+ label: 'Character Bible',
118
+ fetch: (c, id) => c.call('aistudio-character-bible-get', { character_id: Number(id) }),
119
+ norm: (b) => ({ status: b?.status, done: b?.status !== 'generating', ok: b?.status === 'ready', poster: pick(b, 'character.thumb_url'), error: b?.bible_error || null }),
120
+ },
121
+ auto_run: {
122
+ label: 'Automation run',
123
+ fetch: (c, id) => c.call('ys-influencer-auto', { op: 'run', run_id: Number(id) }).then((r) => r.run),
124
+ norm: (r) => ({ status: r?.status, done: ['delivered', 'failed', 'skipped', 'canceled', 'review'].includes(r?.status), ok: ['delivered', 'review'].includes(r?.status),
125
+ needs_action: r?.status === 'review' ? 'Waiting for your approval: yesilcam_automation_review action=approve|skip' : null, url: pick(r, 'result.url'), poster: r?.cover || null, error: r?.error_code || null }),
126
+ },
127
+ social_post: {
128
+ label: 'Social media post',
129
+ fetch: (c, id) => c.call('ys-social', { op: 'post', post_id: Number(id) }).then((r) => r.post || r),
130
+ norm: (p) => ({ status: p?.status, done: ['published', 'failed', 'canceled'].includes(p?.status), ok: p?.status === 'published', url: p?.permalink || p?.url || null, error: p?.error_code || null }),
131
+ },
132
+ cinema_ai: {
133
+ label: 'Cinema Studio presenter / subtitles',
134
+ fetch: (c, id) => c.call('aistudio-cinema-ai-job', { job_id: String(id) }),
135
+ norm: (j) => ({ status: j?.status, done: ['done', 'failed'].includes(j?.status), ok: j?.status === 'done', url: pick(j, 'item.url'), asset_id: pick(j, 'item.asset_id'), error: j?.error_code || null, tokens_charged: num(j?.tokens_charged), refunded: Boolean(j?.refunded) }),
136
+ },
137
+ }
138
+ export const WAIT_KINDS = Object.keys(KINDS)
139
+ export const waitKindHelp = () => Object.entries(KINDS).map(([k, v]) => `${k}: ${v.label}`).join('; ')
140
+
141
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
142
+
143
+ /** Tek okuma. */
144
+ export async function checkOnce(c, kind, id) {
145
+ const K = KINDS[kind]; if (!K) throw new Error(`unknown kind: ${kind}`)
146
+ const raw = await K.fetch(c, id)
147
+ if (raw == null) return { kind, id: String(id), status: 'not_found', done: true, ok: false }
148
+ return { kind, id: String(id), ...K.norm(raw) }
149
+ }
150
+
151
+ /** Bitene (ya da işlem gerekene / süre dolana) kadar bekler. onTick(state) ilerleme için. */
152
+ export async function waitFor(c, kind, id, { intervalMs = 4000, timeoutMs = 20 * 60 * 1000, onTick, sleepImpl = sleep } = {}) {
153
+ const started = Date.now()
154
+ let errors = 0
155
+ for (;;) {
156
+ let st
157
+ try { st = await checkOnce(c, kind, id); errors = 0 }
158
+ catch (e) { if (++errors >= 4) throw e; await sleepImpl(intervalMs * errors); continue }
159
+ onTick?.(st)
160
+ if (st.done) return st
161
+ if (Date.now() - started > timeoutMs) return { ...st, timed_out: true, hint: `Still running. Check later: yesilcam wait ${kind} ${id}` }
162
+ await sleepImpl(intervalMs)
163
+ }
164
+ }
165
+
166
+ // Geriye uyum (0.2): jobs.js yerine
167
+ export async function waitForJob(c, jobId, opts = {}) { const s = await waitFor(c, 'job', jobId, opts); return s }
package/src/jobs.js DELETED
@@ -1,45 +0,0 @@
1
- // İş oluşturma ve bekleme. Sözleşme: aistudio_jobs.status ∈ queued|processing|succeeded|failed.
2
- export const TIERS = ['draft', 'standard', 'cinema', 'cinemapro']
3
- export const ASPECTS = ['1:1', '16:9', '9:16', '3:4', '4:3']
4
-
5
- export function imageJob({ prompt, tier = 'draft', aspect = '16:9', framing }) {
6
- const p = { job_type: 'text2image', prompt, quality_tier: tier, aspect_ratio: aspect }
7
- if (framing) p.framing = framing
8
- return p
9
- }
10
- export function videoJob({ prompt = '', tier = 'draft', aspect = '16:9', sourceAssetId, duration, resolution, generateAudio }) {
11
- const p = { job_type: sourceAssetId ? 'image2video' : 'text2video', prompt, quality_tier: tier, aspect_ratio: aspect }
12
- if (sourceAssetId) p.source_asset_id = Number(sourceAssetId)
13
- if (duration) p.duration = Number(duration)
14
- if (resolution) p.resolution = resolution
15
- if (generateAudio) p.generate_audio = true
16
- return p
17
- }
18
- export function tryonJob({ garmentAssetId, personAssetId, characterId, prompt = '' }) {
19
- const p = { job_type: 'tryon', garment_asset_id: Number(garmentAssetId), prompt, quality_tier: 'standard' }
20
- if (characterId) p.character_id = Number(characterId)
21
- else if (personAssetId) p.source_asset_id = Number(personAssetId)
22
- return p
23
- }
24
-
25
- const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
26
- const isDone = (s) => ['succeeded', 'failed', 'cancelled', 'refunded'].includes(String(s))
27
-
28
- /** İş bitene kadar bekler (varsayılan 4 sn aralık, 20 dk). onTick(job) ilerleme için. */
29
- export async function waitForJob(client, jobId, { intervalMs = 4000, timeoutMs = 20 * 60 * 1000, onTick, sleepImpl = sleep } = {}) {
30
- const started = Date.now()
31
- for (;;) {
32
- const r = await client.jobStatus(Number(jobId))
33
- const job = r?.job
34
- onTick?.(job)
35
- if (job && isDone(job.status)) return job
36
- if (Date.now() - started > timeoutMs) throw new Error('job_wait_timeout')
37
- await sleepImpl(intervalMs)
38
- }
39
- }
40
- export function describeJob(job) {
41
- if (!job) return null
42
- return { id: String(job.id), type: job.job_type, status: job.status, tier: job.quality_tier, progress: job.render_progress != null ? Number(job.render_progress) : null,
43
- tokens_charged: job.tokens_charged != null ? Number(job.tokens_charged) : null, error: job.error_code || null,
44
- result: job.result_asset ? { asset_id: String(job.result_asset.id), kind: job.result_asset.kind, url: job.result_asset.spaces_url, thumb: job.result_asset.thumb_url } : null }
45
- }