@aivue/360-spin 1.0.2 → 2.0.0

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/dist/index.mjs CHANGED
@@ -1,100 +1,103 @@
1
- import { ref as p, computed as y, onUnmounted as H, defineComponent as q, onMounted as N, createElementBlock as k, openBlock as F, normalizeStyle as O, normalizeClass as L, unref as r, createCommentVNode as C, withDirectives as $, createElementVNode as _, toDisplayString as z, vShow as x } from "vue";
2
- function V(t, d) {
3
- const a = p(!1), c = p(!0), i = p(0), o = p(null), l = p([]), v = p(!1), A = p(0), M = p(0), u = y(() => t.mode && t.mode !== "auto" ? t.mode : Array.isArray(t.animatedImage) ? "frames" : "gif"), I = y(() => u.value === "gif" && typeof t.animatedImage == "string" ? t.animatedImage : ""), b = y(() => {
4
- if (u.value === "frames" && Array.isArray(t.animatedImage)) {
5
- const n = t.animatedImage, s = i.value % n.length;
6
- return n[s];
1
+ var W = Object.defineProperty;
2
+ var Q = (u, a, e) => a in u ? W(u, a, { enumerable: !0, configurable: !0, writable: !0, value: e }) : u[a] = e;
3
+ var O = (u, a, e) => Q(u, typeof a != "symbol" ? a + "" : a, e);
4
+ import { ref as f, computed as F, onUnmounted as Y, defineComponent as j, onMounted as Z, createElementBlock as _, openBlock as y, normalizeStyle as X, normalizeClass as G, unref as k, createCommentVNode as C, withDirectives as U, createElementVNode as r, toDisplayString as D, vShow as q, vModelText as ee, withModifiers as R, vModelSelect as N, createVNode as ae, Fragment as te, renderList as re } from "vue";
5
+ function oe(u, a) {
6
+ const e = f(!1), l = f(!0), n = f(0), i = f(null), s = f([]), v = f(!1), c = f(0), m = f(0), p = F(() => u.mode && u.mode !== "auto" ? u.mode : Array.isArray(u.animatedImage) ? "frames" : "gif"), S = F(() => p.value === "gif" && typeof u.animatedImage == "string" ? u.animatedImage : ""), B = F(() => {
7
+ if (p.value === "frames" && Array.isArray(u.animatedImage)) {
8
+ const d = u.animatedImage, w = n.value % d.length;
9
+ return d[w];
7
10
  }
8
11
  return "";
9
- }), T = y(() => Array.isArray(t.animatedImage) ? t.animatedImage : []), m = y(() => T.value.length);
10
- async function B() {
11
- c.value = !0;
12
+ }), T = F(() => Array.isArray(u.animatedImage) ? u.animatedImage : []), h = F(() => T.value.length);
13
+ async function L() {
14
+ l.value = !0;
12
15
  try {
13
- if (u.value === "gif")
14
- await S(I.value);
15
- else if (u.value === "frames") {
16
- const n = T.value.map((f) => S(f)), s = await Promise.all(n);
17
- l.value = s;
16
+ if (p.value === "gif")
17
+ await $(S.value);
18
+ else if (p.value === "frames") {
19
+ const d = T.value.map((b) => $(b)), w = await Promise.all(d);
20
+ s.value = w;
18
21
  }
19
- await S(t.staticImage), c.value = !1, d("loaded");
20
- } catch (n) {
21
- c.value = !1, d("error", n);
22
- }
23
- }
24
- function S(n, s = 5e3) {
25
- return new Promise((f, h) => {
26
- const g = new Image(), e = setTimeout(() => {
27
- h(new Error(`Image load timeout: ${n}`));
28
- }, s);
29
- g.onload = () => {
30
- clearTimeout(e), f(g);
31
- }, g.onerror = () => {
32
- clearTimeout(e), h(new Error(`Failed to load image: ${n}`));
33
- }, g.src = n;
22
+ await $(u.staticImage), l.value = !1, a("loaded");
23
+ } catch (d) {
24
+ l.value = !1, a("error", d);
25
+ }
26
+ }
27
+ function $(d, w = 5e3) {
28
+ return new Promise((b, I) => {
29
+ const A = new Image(), o = setTimeout(() => {
30
+ I(new Error(`Image load timeout: ${d}`));
31
+ }, w);
32
+ A.onload = () => {
33
+ clearTimeout(o), b(A);
34
+ }, A.onerror = () => {
35
+ clearTimeout(o), I(new Error(`Failed to load image: ${d}`));
36
+ }, A.src = d;
34
37
  });
35
38
  }
36
- function X() {
37
- a.value || c.value || (a.value = !0, d("animation-start"), u.value === "frames" && P());
39
+ function x() {
40
+ e.value || l.value || (e.value = !0, a("animation-start"), p.value === "frames" && z());
38
41
  }
39
- function D() {
40
- a.value && (a.value = !1, d("animation-end"), o.value !== null && (cancelAnimationFrame(o.value), o.value = null), u.value === "frames" && (i.value = 0));
42
+ function E() {
43
+ e.value && (e.value = !1, a("animation-end"), i.value !== null && (cancelAnimationFrame(i.value), i.value = null), p.value === "frames" && (n.value = 0));
41
44
  }
42
- function P() {
43
- if (m.value === 0) return;
44
- const n = 1e3 / (t.frameRate || 30);
45
- let s = Date.now();
46
- function f() {
47
- const h = Date.now();
48
- if (h - s >= n && (t.direction === "clockwise" ? i.value = (i.value + 1) % m.value : i.value = i.value === 0 ? m.value - 1 : i.value - 1, d("frame-change", i.value), s = h, !t.loop && i.value === 0)) {
49
- D();
45
+ function z() {
46
+ if (h.value === 0) return;
47
+ const d = 1e3 / (u.frameRate || 30);
48
+ let w = Date.now();
49
+ function b() {
50
+ const I = Date.now();
51
+ if (I - w >= d && (u.direction === "clockwise" ? n.value = (n.value + 1) % h.value : n.value = n.value === 0 ? h.value - 1 : n.value - 1, a("frame-change", n.value), w = I, !u.loop && n.value === 0)) {
52
+ E();
50
53
  return;
51
54
  }
52
- a.value && (o.value = requestAnimationFrame(f));
55
+ e.value && (i.value = requestAnimationFrame(b));
53
56
  }
54
- o.value = requestAnimationFrame(f);
57
+ i.value = requestAnimationFrame(b);
55
58
  }
56
- function U(n) {
57
- u.value !== "frames" || m.value === 0 || (v.value = !0, M.value = i.value, n instanceof TouchEvent ? A.value = n.touches[0].clientX : A.value = n.clientX, a.value && D());
59
+ function V(d) {
60
+ p.value !== "frames" || h.value === 0 || (v.value = !0, m.value = n.value, d instanceof TouchEvent ? c.value = d.touches[0].clientX : c.value = d.clientX, e.value && E());
58
61
  }
59
- function E(n) {
60
- if (!v.value || u.value !== "frames") return;
61
- n.preventDefault();
62
- let s;
63
- n instanceof TouchEvent ? s = n.touches[0].clientX : s = n.clientX;
64
- const f = s - A.value, h = t.dragSensitivity || 10, g = Math.floor(f / h);
65
- let e = M.value + g;
66
- for (; e < 0; ) e += m.value;
67
- for (; e >= m.value; ) e -= m.value;
68
- e !== i.value && (i.value = e, d("frame-change", i.value));
62
+ function M(d) {
63
+ if (!v.value || p.value !== "frames") return;
64
+ d.preventDefault();
65
+ let w;
66
+ d instanceof TouchEvent ? w = d.touches[0].clientX : w = d.clientX;
67
+ const b = w - c.value, I = u.dragSensitivity || 10, A = Math.floor(b / I);
68
+ let o = m.value + A;
69
+ for (; o < 0; ) o += h.value;
70
+ for (; o >= h.value; ) o -= h.value;
71
+ o !== n.value && (n.value = o, a("frame-change", n.value));
69
72
  }
70
- function R(n) {
73
+ function K(d) {
71
74
  v.value = !1;
72
75
  }
73
- return H(() => {
74
- o.value !== null && cancelAnimationFrame(o.value);
76
+ return Y(() => {
77
+ i.value !== null && cancelAnimationFrame(i.value);
75
78
  }), {
76
- isAnimating: a,
77
- isLoading: c,
78
- currentFrameIndex: i,
79
- currentMode: u,
80
- animatedImageUrl: I,
81
- currentFrameUrl: b,
82
- totalFrames: m,
83
- startAnimation: X,
84
- stopAnimation: D,
85
- preloadImages: B,
86
- handleDragStart: U,
87
- handleDragMove: E,
88
- handleDragEnd: R
79
+ isAnimating: e,
80
+ isLoading: l,
81
+ currentFrameIndex: n,
82
+ currentMode: p,
83
+ animatedImageUrl: S,
84
+ currentFrameUrl: B,
85
+ totalFrames: h,
86
+ startAnimation: x,
87
+ stopAnimation: E,
88
+ preloadImages: L,
89
+ handleDragStart: V,
90
+ handleDragMove: M,
91
+ handleDragEnd: K
89
92
  };
90
93
  }
91
- const G = {
94
+ const ne = {
92
95
  key: 0,
93
96
  class: "ai-360-spin__loading"
94
- }, J = { class: "ai-360-spin__loading-text" }, K = ["src", "alt"], Q = ["src", "alt"], W = ["src", "alt"], Y = {
97
+ }, ie = { class: "ai-360-spin__loading-text" }, se = ["src", "alt"], le = ["src", "alt"], ue = ["src", "alt"], ce = {
95
98
  key: 3,
96
99
  class: "ai-360-spin__hint"
97
- }, Z = /* @__PURE__ */ q({
100
+ }, de = /* @__PURE__ */ j({
98
101
  __name: "Ai360Spin",
99
102
  props: {
100
103
  staticImage: {},
@@ -118,132 +121,635 @@ const G = {
118
121
  dragSensitivity: { default: 10 }
119
122
  },
120
123
  emits: ["animation-start", "animation-end", "loaded", "error", "frame-change"],
121
- setup(t, { emit: d }) {
122
- const a = t, c = d, i = p(null), {
123
- isAnimating: o,
124
- isLoading: l,
124
+ setup(u, { emit: a }) {
125
+ const e = u, l = a, n = f(null), {
126
+ isAnimating: i,
127
+ isLoading: s,
125
128
  currentMode: v,
126
- animatedImageUrl: A,
127
- currentFrameUrl: M,
128
- startAnimation: u,
129
- stopAnimation: I,
130
- preloadImages: b,
129
+ animatedImageUrl: c,
130
+ currentFrameUrl: m,
131
+ startAnimation: p,
132
+ stopAnimation: S,
133
+ preloadImages: B,
131
134
  handleDragStart: T,
132
- handleDragMove: m,
133
- handleDragEnd: B
134
- } = V(a, c), S = y(() => ({
135
- width: typeof a.width == "number" ? `${a.width}px` : a.width,
136
- height: typeof a.height == "number" ? `${a.height}px` : a.height
137
- })), X = y(() => a.trigger === "hover" && !o.value), D = y(() => "Hover to spin");
138
- function P() {
139
- a.trigger === "hover" && u();
140
- }
141
- function U() {
142
- a.trigger === "hover" && I();
135
+ handleDragMove: h,
136
+ handleDragEnd: L
137
+ } = oe(e, l), $ = F(() => ({
138
+ width: typeof e.width == "number" ? `${e.width}px` : e.width,
139
+ height: typeof e.height == "number" ? `${e.height}px` : e.height
140
+ })), x = F(() => e.trigger === "hover" && !i.value), E = F(() => "Hover to spin");
141
+ function z() {
142
+ e.trigger === "hover" && p();
143
143
  }
144
- function E() {
145
- a.trigger === "click" && (o.value ? I() : u());
144
+ function V() {
145
+ e.trigger === "hover" && S();
146
146
  }
147
- function R(e) {
148
- a.enableDragSpin && v.value === "frames" ? T(e) : a.trigger === "click" && E();
147
+ function M() {
148
+ e.trigger === "click" && (i.value ? S() : p());
149
149
  }
150
- function n(e) {
151
- a.enableDragSpin && v.value === "frames" && m(e);
150
+ function K(o) {
151
+ e.enableDragSpin && v.value === "frames" ? T(o) : e.trigger === "click" && M();
152
152
  }
153
- function s(e) {
154
- a.enableDragSpin && v.value === "frames" && B(e);
153
+ function d(o) {
154
+ e.enableDragSpin && v.value === "frames" && h(o);
155
155
  }
156
- function f() {
157
- console.log("[Ai360Spin] Static image loaded, clearing loading state"), l.value && (l.value = !1), console.log("[Ai360Spin] isLoading after static load:", l.value);
156
+ function w(o) {
157
+ e.enableDragSpin && v.value === "frames" && L(o);
158
158
  }
159
- function h() {
160
- c("loaded");
159
+ function b() {
160
+ console.log("[Ai360Spin] Static image loaded, clearing loading state"), s.value && (s.value = !1), console.log("[Ai360Spin] isLoading after static load:", s.value);
161
161
  }
162
- function g(e) {
163
- var w;
164
- console.error("Image failed to load:", (w = e.target) == null ? void 0 : w.src), l.value && (l.value = !1), c("error", new Error("Failed to load image"));
162
+ function I() {
163
+ l("loaded");
165
164
  }
166
- return N(async () => {
167
- if (console.log("[Ai360Spin] onMounted - preload:", a.preload, "isLoading:", l.value), a.preload)
165
+ function A(o) {
166
+ var t;
167
+ console.error("Image failed to load:", (t = o.target) == null ? void 0 : t.src), s.value && (s.value = !1), l("error", new Error("Failed to load image"));
168
+ }
169
+ return Z(async () => {
170
+ if (console.log("[Ai360Spin] onMounted - preload:", e.preload, "isLoading:", s.value), e.preload)
168
171
  try {
169
- await b();
170
- } catch (e) {
171
- console.error("[Ai360Spin] Preload failed:", e), l.value = !1;
172
+ await B();
173
+ } catch (o) {
174
+ console.error("[Ai360Spin] Preload failed:", o), s.value = !1;
172
175
  }
173
176
  else
174
- console.log("[Ai360Spin] Preload disabled, clearing loading state"), l.value = !1, console.log("[Ai360Spin] isLoading after clear:", l.value);
175
- a.trigger === "auto" && u();
176
- }), (e, w) => (F(), k("div", {
177
+ console.log("[Ai360Spin] Preload disabled, clearing loading state"), s.value = !1, console.log("[Ai360Spin] isLoading after clear:", s.value);
178
+ e.trigger === "auto" && p();
179
+ }), (o, t) => (y(), _("div", {
177
180
  ref_key: "containerRef",
178
- ref: i,
179
- class: L(["ai-360-spin", e.containerClass, { "ai-360-spin--animating": r(o), "ai-360-spin--loading": r(l) }]),
180
- style: O(S.value),
181
- onMouseenter: P,
182
- onMouseleave: U,
183
- onClick: E,
184
- onTouchstart: R,
185
- onTouchmove: n,
186
- onTouchend: s
181
+ ref: n,
182
+ class: G(["ai-360-spin", o.containerClass, { "ai-360-spin--animating": k(i), "ai-360-spin--loading": k(s) }]),
183
+ style: X($.value),
184
+ onMouseenter: z,
185
+ onMouseleave: V,
186
+ onClick: M,
187
+ onTouchstart: K,
188
+ onTouchmove: d,
189
+ onTouchend: w
187
190
  }, [
188
- r(l) && e.showLoading ? (F(), k("div", G, [
189
- w[0] || (w[0] = _("div", { class: "ai-360-spin__spinner" }, null, -1)),
190
- _("p", J, z(e.loadingText), 1)
191
+ k(s) && o.showLoading ? (y(), _("div", ne, [
192
+ t[0] || (t[0] = r("div", { class: "ai-360-spin__spinner" }, null, -1)),
193
+ r("p", ie, D(o.loadingText), 1)
191
194
  ])) : C("", !0),
192
- $(_("img", {
193
- src: e.staticImage,
194
- alt: e.alt,
195
- class: L(["ai-360-spin__image", "ai-360-spin__image--static", e.imageClass]),
196
- onLoad: f,
197
- onError: g
198
- }, null, 42, K), [
199
- [x, !r(o) && !r(l)]
195
+ U(r("img", {
196
+ src: o.staticImage,
197
+ alt: o.alt,
198
+ class: G(["ai-360-spin__image", "ai-360-spin__image--static", o.imageClass]),
199
+ onLoad: b,
200
+ onError: A
201
+ }, null, 42, se), [
202
+ [q, !k(i) && !k(s)]
200
203
  ]),
201
- r(v) === "gif" && !r(l) ? $((F(), k("img", {
204
+ k(v) === "gif" && !k(s) ? U((y(), _("img", {
202
205
  key: 1,
203
- src: r(A),
204
- alt: e.alt,
205
- class: L(["ai-360-spin__image", "ai-360-spin__image--animated", e.imageClass]),
206
- onLoad: h,
207
- onError: g
208
- }, null, 42, Q)), [
209
- [x, r(o)]
206
+ src: k(c),
207
+ alt: o.alt,
208
+ class: G(["ai-360-spin__image", "ai-360-spin__image--animated", o.imageClass]),
209
+ onLoad: I,
210
+ onError: A
211
+ }, null, 42, le)), [
212
+ [q, k(i)]
210
213
  ]) : C("", !0),
211
- r(v) === "frames" && !r(l) ? $((F(), k("img", {
214
+ k(v) === "frames" && !k(s) ? U((y(), _("img", {
212
215
  key: 2,
213
- src: r(M),
214
- alt: e.alt,
215
- class: L(["ai-360-spin__image", "ai-360-spin__image--frame", e.imageClass])
216
- }, null, 10, W)), [
217
- [x, r(o)]
216
+ src: k(m),
217
+ alt: o.alt,
218
+ class: G(["ai-360-spin__image", "ai-360-spin__image--frame", o.imageClass])
219
+ }, null, 10, ue)), [
220
+ [q, k(i)]
218
221
  ]) : C("", !0),
219
- !r(o) && !r(l) && X.value ? (F(), k("div", Y, [
220
- w[1] || (w[1] = _("svg", {
222
+ !k(i) && !k(s) && x.value ? (y(), _("div", ce, [
223
+ t[1] || (t[1] = r("svg", {
221
224
  class: "ai-360-spin__hint-icon",
222
225
  viewBox: "0 0 24 24",
223
226
  fill: "none",
224
227
  stroke: "currentColor"
225
228
  }, [
226
- _("path", { d: "M12 2L2 7l10 5 10-5-10-5z" }),
227
- _("path", { d: "M2 17l10 5 10-5" }),
228
- _("path", { d: "M2 12l10 5 10-5" })
229
+ r("path", { d: "M12 2L2 7l10 5 10-5-10-5z" }),
230
+ r("path", { d: "M2 17l10 5 10-5" }),
231
+ r("path", { d: "M2 12l10 5 10-5" })
229
232
  ], -1)),
230
- _("span", null, z(D.value), 1)
233
+ r("span", null, D(E.value), 1)
231
234
  ])) : C("", !0)
232
235
  ], 38));
233
236
  }
234
- }), j = (t, d) => {
235
- const a = t.__vccOpts || t;
236
- for (const [c, i] of d)
237
- a[c] = i;
238
- return a;
239
- }, ee = /* @__PURE__ */ j(Z, [["__scopeId", "data-v-915b2a52"]]), ne = {
240
- install(t) {
241
- t.component("Ai360Spin", ee);
237
+ }), H = (u, a) => {
238
+ const e = u.__vccOpts || u;
239
+ for (const [l, n] of a)
240
+ e[l] = n;
241
+ return e;
242
+ }, J = /* @__PURE__ */ H(de, [["__scopeId", "data-v-915b2a52"]]);
243
+ class ge {
244
+ constructor(a, e) {
245
+ O(this, "config");
246
+ O(this, "onProgress");
247
+ this.config = {
248
+ provider: a.provider || "openai",
249
+ apiKey: a.apiKey,
250
+ frameCount: a.frameCount || 36,
251
+ backgroundColor: a.backgroundColor || "white",
252
+ customBackgroundColor: a.customBackgroundColor || "#ffffff",
253
+ quality: a.quality || 80,
254
+ imageSize: a.imageSize || "1024x1024",
255
+ model: a.model || (a.provider === "openai" ? "dall-e-3" : "stable-diffusion-xl-1024-v1-0"),
256
+ useVisionAnalysis: a.useVisionAnalysis !== !1,
257
+ promptTemplate: a.promptTemplate || ""
258
+ }, this.onProgress = e;
259
+ }
260
+ /**
261
+ * Generate 360-degree frames from a single product image
262
+ */
263
+ async generate(a) {
264
+ const e = Date.now(), l = [];
265
+ try {
266
+ const n = typeof a == "string" ? a : await this.fileToBase64(a);
267
+ this.updateProgress(0, "Analyzing product image...");
268
+ let i = "";
269
+ this.config.useVisionAnalysis && this.config.provider === "openai" ? i = await this.analyzeProduct(n) : i = "Product image", this.updateProgress(10, "Product analyzed. Generating frames...");
270
+ const s = 360 / this.config.frameCount;
271
+ for (let c = 0; c < this.config.frameCount; c++) {
272
+ const m = Math.round(c * s), p = 10 + Math.round(c / this.config.frameCount * 85);
273
+ this.updateProgress(p, `Generating frame ${c + 1}/${this.config.frameCount} (${m}°)...`, l);
274
+ const S = await this.generateFrame(i, m, c);
275
+ l.push(S);
276
+ }
277
+ this.updateProgress(100, "Generation complete!", l);
278
+ const v = Date.now() - e;
279
+ return {
280
+ frames: l,
281
+ productDescription: i,
282
+ metadata: {
283
+ provider: this.config.provider,
284
+ frameCount: this.config.frameCount,
285
+ generationTime: v,
286
+ model: this.config.model
287
+ }
288
+ };
289
+ } catch (n) {
290
+ throw console.error("AI 360 Generation error:", n), n;
291
+ }
292
+ }
293
+ /**
294
+ * Analyze product image using GPT-4 Vision
295
+ */
296
+ async analyzeProduct(a) {
297
+ var n, i;
298
+ const e = await fetch("https://api.openai.com/v1/chat/completions", {
299
+ method: "POST",
300
+ headers: {
301
+ "Content-Type": "application/json",
302
+ Authorization: `Bearer ${this.config.apiKey}`
303
+ },
304
+ body: JSON.stringify({
305
+ model: "gpt-4o",
306
+ messages: [
307
+ {
308
+ role: "user",
309
+ content: [
310
+ {
311
+ type: "text",
312
+ text: "Analyze this product image and provide a detailed description including: product type, color, material, key features, and style. Be concise but specific. This will be used to generate 360-degree views."
313
+ },
314
+ {
315
+ type: "image_url",
316
+ image_url: { url: a }
317
+ }
318
+ ]
319
+ }
320
+ ],
321
+ max_tokens: 300
322
+ })
323
+ });
324
+ if (!e.ok)
325
+ throw new Error(`Vision API error: ${e.statusText}`);
326
+ return ((i = (n = (await e.json()).choices[0]) == null ? void 0 : n.message) == null ? void 0 : i.content) || "Product";
327
+ }
328
+ /**
329
+ * Generate a single frame at a specific angle
330
+ */
331
+ async generateFrame(a, e, l) {
332
+ return this.config.provider === "openai" ? this.generateFrameOpenAI(a, e, l) : this.generateFrameStability(a, e, l);
333
+ }
334
+ /**
335
+ * Generate frame using OpenAI DALL-E
336
+ */
337
+ async generateFrameOpenAI(a, e, l) {
338
+ var m;
339
+ const n = this.getBackgroundColorValue(), i = this.config.promptTemplate || `Create a high-quality product photograph of: ${a}
340
+ View angle: ${e} degrees rotation (0° is front view, rotating clockwise around vertical axis)
341
+ Background: ${n}
342
+ Style: Professional product photography, studio lighting, high detail, sharp focus, centered composition
343
+ The product should be clearly visible and well-lit from this ${e}° angle.`, s = await fetch("https://api.openai.com/v1/images/generations", {
344
+ method: "POST",
345
+ headers: {
346
+ "Content-Type": "application/json",
347
+ Authorization: `Bearer ${this.config.apiKey}`
348
+ },
349
+ body: JSON.stringify({
350
+ model: this.config.model,
351
+ prompt: i.substring(0, 4e3),
352
+ // DALL-E has prompt limits
353
+ n: 1,
354
+ size: this.config.imageSize,
355
+ quality: "hd",
356
+ style: "natural"
357
+ })
358
+ });
359
+ if (!s.ok)
360
+ throw new Error(`OpenAI API error: ${s.statusText}`);
361
+ const c = (m = (await s.json()).data[0]) == null ? void 0 : m.url;
362
+ if (!c)
363
+ throw new Error("No image URL returned from OpenAI");
364
+ return this.urlToBase64(c);
365
+ }
366
+ /**
367
+ * Generate frame using Stability AI
368
+ */
369
+ async generateFrameStability(a, e, l) {
370
+ var m;
371
+ const n = this.getBackgroundColorValue(), i = this.config.promptTemplate || `Professional product photograph of ${a}, viewed from ${e} degrees angle, ${n} background, studio lighting, high quality, detailed, centered`, s = await fetch("https://api.stability.ai/v1/generation/stable-diffusion-xl-1024-v1-0/text-to-image", {
372
+ method: "POST",
373
+ headers: {
374
+ "Content-Type": "application/json",
375
+ Authorization: `Bearer ${this.config.apiKey}`,
376
+ Accept: "application/json"
377
+ },
378
+ body: JSON.stringify({
379
+ text_prompts: [
380
+ {
381
+ text: i,
382
+ weight: 1
383
+ }
384
+ ],
385
+ cfg_scale: 7,
386
+ height: 1024,
387
+ width: 1024,
388
+ samples: 1,
389
+ steps: 30
390
+ })
391
+ });
392
+ if (!s.ok)
393
+ throw new Error(`Stability AI error: ${s.statusText}`);
394
+ const c = (m = (await s.json()).artifacts[0]) == null ? void 0 : m.base64;
395
+ if (!c)
396
+ throw new Error("No image returned from Stability AI");
397
+ return `data:image/png;base64,${c}`;
398
+ }
399
+ /**
400
+ * Get background color value based on config
401
+ */
402
+ getBackgroundColorValue() {
403
+ switch (this.config.backgroundColor) {
404
+ case "white":
405
+ return "pure white";
406
+ case "transparent":
407
+ return "transparent/alpha channel";
408
+ case "black":
409
+ return "pure black";
410
+ case "custom":
411
+ return this.config.customBackgroundColor;
412
+ default:
413
+ return "white";
414
+ }
415
+ }
416
+ /**
417
+ * Convert File to base64
418
+ */
419
+ fileToBase64(a) {
420
+ return new Promise((e, l) => {
421
+ const n = new FileReader();
422
+ n.onload = () => e(n.result), n.onerror = l, n.readAsDataURL(a);
423
+ });
424
+ }
425
+ /**
426
+ * Convert URL to base64
427
+ */
428
+ async urlToBase64(a) {
429
+ try {
430
+ const l = await (await fetch(a)).blob();
431
+ return new Promise((n, i) => {
432
+ const s = new FileReader();
433
+ s.onload = () => n(s.result), s.onerror = i, s.readAsDataURL(l);
434
+ });
435
+ } catch (e) {
436
+ return console.error("Error converting URL to base64:", e), a;
437
+ }
438
+ }
439
+ /**
440
+ * Update progress callback
441
+ */
442
+ updateProgress(a, e, l = []) {
443
+ this.onProgress && this.onProgress({
444
+ currentFrame: l.length,
445
+ totalFrames: this.config.frameCount,
446
+ percentage: a,
447
+ status: e,
448
+ generatedFrames: l
449
+ });
450
+ }
451
+ }
452
+ const me = { class: "ai-360-generator" }, pe = {
453
+ key: 0,
454
+ class: "ai-360-generator__api-key"
455
+ }, fe = { for: "api-key-input" }, ve = ["placeholder"], he = {
456
+ key: 1,
457
+ class: "ai-360-generator__upload"
458
+ }, _e = {
459
+ key: 0,
460
+ class: "ai-360-generator__dropzone-content"
461
+ }, ye = {
462
+ key: 1,
463
+ class: "ai-360-generator__preview"
464
+ }, we = ["src"], ke = {
465
+ key: 0,
466
+ class: "ai-360-generator__options"
467
+ }, be = { class: "ai-360-generator__option" }, Ae = { class: "ai-360-generator__option" }, Se = { class: "ai-360-generator__option" }, Ie = {
468
+ key: 2,
469
+ class: "ai-360-generator__progress"
470
+ }, Ce = { class: "ai-360-generator__progress-bar" }, Fe = { class: "ai-360-generator__progress-text" }, Te = { class: "ai-360-generator__progress-detail" }, $e = {
471
+ key: 3,
472
+ class: "ai-360-generator__result"
473
+ }, Pe = { class: "ai-360-generator__viewer" }, De = {
474
+ key: 0,
475
+ class: "ai-360-generator__frame-grid"
476
+ }, Be = ["src", "alt"], Ee = {
477
+ key: 4,
478
+ class: "ai-360-generator__error"
479
+ }, Me = /* @__PURE__ */ j({
480
+ __name: "Ai360Generator",
481
+ props: {
482
+ provider: { default: "openai" },
483
+ apiKey: { default: "" },
484
+ autoSaveApiKey: { type: Boolean, default: !0 },
485
+ showFramePreview: { type: Boolean, default: !0 }
486
+ },
487
+ emits: ["frames-generated", "generation-start", "generation-complete", "generation-error"],
488
+ setup(u, { emit: a }) {
489
+ const e = u, l = a, n = f(e.apiKey || localStorage.getItem(`ai_360_api_key_${e.provider}`) || ""), i = f(null), s = f(!1), v = f(!1), c = f([]), m = f(null), p = f(null), S = f(36), B = f("white"), T = f(80), h = f({
490
+ currentFrame: 0,
491
+ totalFrames: 0,
492
+ percentage: 0,
493
+ status: "",
494
+ generatedFrames: []
495
+ }), L = F(() => n.value.length > 0), $ = F(() => e.provider === "openai" ? "OpenAI" : "Stability AI");
496
+ function x() {
497
+ e.autoSaveApiKey && localStorage.setItem(`ai_360_api_key_${e.provider}`, n.value);
498
+ }
499
+ function E() {
500
+ var o;
501
+ (o = p.value) == null || o.click();
502
+ }
503
+ function z(o) {
504
+ var P;
505
+ const g = (P = o.target.files) == null ? void 0 : P[0];
506
+ g && M(g);
507
+ }
508
+ function V(o) {
509
+ var g;
510
+ s.value = !1;
511
+ const t = (g = o.dataTransfer) == null ? void 0 : g.files[0];
512
+ t && t.type.startsWith("image/") && M(t);
513
+ }
514
+ function M(o) {
515
+ const t = new FileReader();
516
+ t.onload = (g) => {
517
+ var P;
518
+ i.value = (P = g.target) == null ? void 0 : P.result;
519
+ }, t.readAsDataURL(o);
520
+ }
521
+ function K() {
522
+ i.value = null, p.value && (p.value.value = "");
523
+ }
524
+ async function d() {
525
+ if (!(!i.value || !n.value)) {
526
+ v.value = !0, m.value = null, c.value = [], l("generation-start");
527
+ try {
528
+ const t = await new ge(
529
+ {
530
+ provider: e.provider,
531
+ apiKey: n.value,
532
+ frameCount: S.value,
533
+ backgroundColor: B.value,
534
+ quality: T.value,
535
+ useVisionAnalysis: !0
536
+ },
537
+ (g) => {
538
+ h.value = g;
539
+ }
540
+ ).generate(i.value);
541
+ c.value = t.frames, l("generation-complete", t.frames);
542
+ } catch (o) {
543
+ const t = o instanceof Error ? o.message : "Generation failed";
544
+ m.value = t, l("generation-error", o instanceof Error ? o : new Error(t));
545
+ } finally {
546
+ v.value = !1;
547
+ }
548
+ }
549
+ }
550
+ function w() {
551
+ c.value.forEach((o, t) => {
552
+ const g = document.createElement("a");
553
+ g.href = o, g.download = `360-frame-${String(t + 1).padStart(3, "0")}.png`, g.click();
554
+ });
555
+ }
556
+ function b() {
557
+ c.value = [], i.value = null, m.value = null, h.value = {
558
+ currentFrame: 0,
559
+ totalFrames: 0,
560
+ percentage: 0,
561
+ status: "",
562
+ generatedFrames: []
563
+ };
564
+ }
565
+ function I() {
566
+ l("frames-generated", c.value);
567
+ }
568
+ function A() {
569
+ m.value = null;
570
+ }
571
+ return (o, t) => (y(), _("div", me, [
572
+ L.value ? C("", !0) : (y(), _("div", pe, [
573
+ r("label", fe, D($.value) + " API Key:", 1),
574
+ U(r("input", {
575
+ id: "api-key-input",
576
+ "onUpdate:modelValue": t[0] || (t[0] = (g) => n.value = g),
577
+ type: "password",
578
+ placeholder: `Enter your ${$.value} API key`,
579
+ class: "ai-360-generator__input"
580
+ }, null, 8, ve), [
581
+ [ee, n.value]
582
+ ]),
583
+ r("button", {
584
+ onClick: x,
585
+ class: "ai-360-generator__button"
586
+ }, " Save API Key ")
587
+ ])),
588
+ L.value && !v.value && c.value.length === 0 ? (y(), _("div", he, [
589
+ r("div", {
590
+ class: G(["ai-360-generator__dropzone", { "ai-360-generator__dropzone--dragging": s.value }]),
591
+ onDrop: R(V, ["prevent"]),
592
+ onDragover: t[1] || (t[1] = R((g) => s.value = !0, ["prevent"])),
593
+ onDragleave: t[2] || (t[2] = R((g) => s.value = !1, ["prevent"])),
594
+ onClick: E
595
+ }, [
596
+ r("input", {
597
+ ref_key: "fileInputRef",
598
+ ref: p,
599
+ type: "file",
600
+ accept: "image/*",
601
+ style: { display: "none" },
602
+ onChange: z
603
+ }, null, 544),
604
+ i.value ? (y(), _("div", ye, [
605
+ r("img", {
606
+ src: i.value,
607
+ alt: "Uploaded product",
608
+ class: "ai-360-generator__preview-image"
609
+ }, null, 8, we),
610
+ r("button", {
611
+ onClick: R(K, ["stop"]),
612
+ class: "ai-360-generator__clear-button"
613
+ }, " ✕ ")
614
+ ])) : (y(), _("div", _e, t[6] || (t[6] = [
615
+ r("svg", {
616
+ class: "ai-360-generator__upload-icon",
617
+ viewBox: "0 0 24 24",
618
+ fill: "none",
619
+ stroke: "currentColor"
620
+ }, [
621
+ r("path", {
622
+ d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4M17 8l-5-5-5 5M12 3v12",
623
+ "stroke-width": "2",
624
+ "stroke-linecap": "round",
625
+ "stroke-linejoin": "round"
626
+ })
627
+ ], -1),
628
+ r("p", { class: "ai-360-generator__dropzone-text" }, " Drop an image here or click to upload ", -1),
629
+ r("p", { class: "ai-360-generator__dropzone-hint" }, " Upload a product image to generate 360° views ", -1)
630
+ ])))
631
+ ], 34),
632
+ i.value ? (y(), _("div", ke, [
633
+ r("div", be, [
634
+ t[8] || (t[8] = r("label", null, "Frame Count:", -1)),
635
+ U(r("select", {
636
+ "onUpdate:modelValue": t[3] || (t[3] = (g) => S.value = g),
637
+ class: "ai-360-generator__select"
638
+ }, t[7] || (t[7] = [
639
+ r("option", { value: 12 }, "12 frames (Fast)", -1),
640
+ r("option", { value: 24 }, "24 frames (Balanced)", -1),
641
+ r("option", { value: 36 }, "36 frames (Smooth)", -1),
642
+ r("option", { value: 72 }, "72 frames (Ultra Smooth)", -1)
643
+ ]), 512), [
644
+ [
645
+ N,
646
+ S.value,
647
+ void 0,
648
+ { number: !0 }
649
+ ]
650
+ ])
651
+ ]),
652
+ r("div", Ae, [
653
+ t[10] || (t[10] = r("label", null, "Background:", -1)),
654
+ U(r("select", {
655
+ "onUpdate:modelValue": t[4] || (t[4] = (g) => B.value = g),
656
+ class: "ai-360-generator__select"
657
+ }, t[9] || (t[9] = [
658
+ r("option", { value: "white" }, "White", -1),
659
+ r("option", { value: "transparent" }, "Transparent", -1),
660
+ r("option", { value: "black" }, "Black", -1)
661
+ ]), 512), [
662
+ [N, B.value]
663
+ ])
664
+ ]),
665
+ r("div", Se, [
666
+ t[12] || (t[12] = r("label", null, "Quality:", -1)),
667
+ U(r("select", {
668
+ "onUpdate:modelValue": t[5] || (t[5] = (g) => T.value = g),
669
+ class: "ai-360-generator__select"
670
+ }, t[11] || (t[11] = [
671
+ r("option", { value: 60 }, "Standard", -1),
672
+ r("option", { value: 80 }, "High", -1),
673
+ r("option", { value: 100 }, "Ultra", -1)
674
+ ]), 512), [
675
+ [
676
+ N,
677
+ T.value,
678
+ void 0,
679
+ { number: !0 }
680
+ ]
681
+ ])
682
+ ]),
683
+ r("button", {
684
+ onClick: d,
685
+ class: "ai-360-generator__generate-button"
686
+ }, " 🤖 Generate 360° View ")
687
+ ])) : C("", !0)
688
+ ])) : C("", !0),
689
+ v.value ? (y(), _("div", Ie, [
690
+ r("div", Ce, [
691
+ r("div", {
692
+ class: "ai-360-generator__progress-fill",
693
+ style: X({ width: h.value.percentage + "%" })
694
+ }, null, 4)
695
+ ]),
696
+ r("p", Fe, D(h.value.status), 1),
697
+ r("p", Te, " Frame " + D(h.value.currentFrame) + " / " + D(h.value.totalFrames) + " (" + D(Math.round(h.value.percentage)) + "%) ", 1)
698
+ ])) : C("", !0),
699
+ c.value.length > 0 && !v.value ? (y(), _("div", $e, [
700
+ t[13] || (t[13] = r("h3", { class: "ai-360-generator__result-title" }, "✅ 360° View Generated!", -1)),
701
+ r("div", Pe, [
702
+ ae(J, {
703
+ "static-image": c.value[0],
704
+ "animated-image": c.value,
705
+ mode: "frames",
706
+ trigger: "hover",
707
+ "enable-drag-spin": !0,
708
+ "frame-rate": 30
709
+ }, null, 8, ["static-image", "animated-image"])
710
+ ]),
711
+ r("div", { class: "ai-360-generator__actions" }, [
712
+ r("button", {
713
+ onClick: w,
714
+ class: "ai-360-generator__button"
715
+ }, " 📥 Download Frames "),
716
+ r("button", {
717
+ onClick: b,
718
+ class: "ai-360-generator__button ai-360-generator__button--secondary"
719
+ }, " 🔄 Generate Another "),
720
+ r("button", {
721
+ onClick: I,
722
+ class: "ai-360-generator__button ai-360-generator__button--primary"
723
+ }, " ✓ Use These Frames ")
724
+ ]),
725
+ o.showFramePreview ? (y(), _("div", De, [
726
+ (y(!0), _(te, null, re(c.value.slice(0, 12), (g, P) => (y(), _("img", {
727
+ key: P,
728
+ src: g,
729
+ alt: `Frame ${P + 1}`,
730
+ class: "ai-360-generator__frame-thumbnail"
731
+ }, null, 8, Be))), 128))
732
+ ])) : C("", !0)
733
+ ])) : C("", !0),
734
+ m.value ? (y(), _("div", Ee, [
735
+ r("p", null, "❌ " + D(m.value), 1),
736
+ r("button", {
737
+ onClick: A,
738
+ class: "ai-360-generator__button"
739
+ }, " Dismiss ")
740
+ ])) : C("", !0)
741
+ ]));
742
+ }
743
+ }), Ue = /* @__PURE__ */ H(Me, [["__scopeId", "data-v-fc0644b2"]]), ze = {
744
+ install(u) {
745
+ u.component("Ai360Spin", J), u.component("Ai360Generator", Ue);
242
746
  }
243
747
  };
244
748
  export {
245
- ee as Ai360Spin,
246
- ne as default,
247
- V as use360Spin
749
+ ge as AI360Generator,
750
+ Ue as Ai360Generator,
751
+ J as Ai360Spin,
752
+ ze as default,
753
+ oe as use360Spin
248
754
  };
249
755
  //# sourceMappingURL=index.mjs.map