Merge branch 'dev'
CI/CD / deploy (push) Successful in 2m22s
Details
CI/CD / deploy (push) Successful in 2m22s
Details
This commit is contained in:
commit
905481eb84
|
|
@ -839,13 +839,15 @@ export class FolkChoiceRank extends FolkShape {
|
||||||
|
|
||||||
const onMove = (me: PointerEvent) => {
|
const onMove = (me: PointerEvent) => {
|
||||||
me.stopPropagation();
|
me.stopPropagation();
|
||||||
const target = this.#rankPanel!.querySelector(
|
|
||||||
`.rank-item:not(.dragging):hover`
|
|
||||||
) as HTMLElement;
|
|
||||||
// Clear previous drag-over states
|
|
||||||
this.#rankPanel!.querySelectorAll(".drag-over").forEach((d) => d.classList.remove("drag-over"));
|
this.#rankPanel!.querySelectorAll(".drag-over").forEach((d) => d.classList.remove("drag-over"));
|
||||||
|
const items = this.#rankPanel!.querySelectorAll(".rank-item:not(.dragging)");
|
||||||
if (target) target.classList.add("drag-over");
|
for (const item of items) {
|
||||||
|
const rect = (item as HTMLElement).getBoundingClientRect();
|
||||||
|
if (me.clientY >= rect.top && me.clientY <= rect.bottom) {
|
||||||
|
item.classList.add("drag-over");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onUp = (ue: PointerEvent) => {
|
const onUp = (ue: PointerEvent) => {
|
||||||
|
|
|
||||||
|
|
@ -525,6 +525,7 @@ export class FolkChoiceSpider extends FolkShape {
|
||||||
#selectedOptionId = "";
|
#selectedOptionId = "";
|
||||||
#drawerOpen = false;
|
#drawerOpen = false;
|
||||||
#settingsOpen = false;
|
#settingsOpen = false;
|
||||||
|
#isSliding = false;
|
||||||
|
|
||||||
// DOM refs
|
// DOM refs
|
||||||
#wrapperEl: HTMLElement | null = null;
|
#wrapperEl: HTMLElement | null = null;
|
||||||
|
|
@ -748,7 +749,7 @@ export class FolkChoiceSpider extends FolkShape {
|
||||||
#render() {
|
#render() {
|
||||||
this.#renderOptionTabs();
|
this.#renderOptionTabs();
|
||||||
this.#renderChart();
|
this.#renderChart();
|
||||||
this.#renderSliders();
|
if (!this.#isSliding) this.#renderSliders();
|
||||||
this.#renderLegend();
|
this.#renderLegend();
|
||||||
this.#renderSummary();
|
this.#renderSummary();
|
||||||
if (this.#drawerOpen) this.#renderDrawer();
|
if (this.#drawerOpen) this.#renderDrawer();
|
||||||
|
|
@ -889,7 +890,9 @@ export class FolkChoiceSpider extends FolkShape {
|
||||||
this.#slidersEl.querySelectorAll(".slider-input").forEach((slider) => {
|
this.#slidersEl.querySelectorAll(".slider-input").forEach((slider) => {
|
||||||
const input = slider as HTMLInputElement;
|
const input = slider as HTMLInputElement;
|
||||||
input.addEventListener("click", (e) => e.stopPropagation());
|
input.addEventListener("click", (e) => e.stopPropagation());
|
||||||
input.addEventListener("pointerdown", (e) => e.stopPropagation());
|
input.addEventListener("pointerdown", (e) => { e.stopPropagation(); this.#isSliding = true; });
|
||||||
|
input.addEventListener("pointerup", () => { this.#isSliding = false; });
|
||||||
|
input.addEventListener("lostpointercapture", () => { this.#isSliding = false; });
|
||||||
input.addEventListener("input", (e) => {
|
input.addEventListener("input", (e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const critId = input.dataset.crit!;
|
const critId = input.dataset.crit!;
|
||||||
|
|
|
||||||
|
|
@ -459,7 +459,7 @@ export class FolkVideoGen extends FolkShape {
|
||||||
this.#error = null;
|
this.#error = null;
|
||||||
this.#progress = 0;
|
this.#progress = 0;
|
||||||
if (this.#generateBtn) this.#generateBtn.disabled = true;
|
if (this.#generateBtn) this.#generateBtn.disabled = true;
|
||||||
this.#renderLoading();
|
this.#renderLoading("Submitting...");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const endpoint = this.#mode === "i2v" ? "/api/video-gen/i2v" : "/api/video-gen/t2v";
|
const endpoint = this.#mode === "i2v" ? "/api/video-gen/i2v" : "/api/video-gen/t2v";
|
||||||
|
|
@ -481,23 +481,35 @@ export class FolkVideoGen extends FolkShape {
|
||||||
|
|
||||||
const submitData = await submitRes.json();
|
const submitData = await submitRes.json();
|
||||||
|
|
||||||
// Poll for job completion (up to 5 minutes)
|
// Poll for job completion (up to 10 minutes)
|
||||||
const jobId = submitData.job_id;
|
const jobId = submitData.job_id;
|
||||||
if (!jobId) throw new Error("No job ID returned");
|
if (!jobId) throw new Error("No job ID returned");
|
||||||
|
|
||||||
const deadline = Date.now() + 300_000;
|
const deadline = Date.now() + 600_000;
|
||||||
let elapsed = 0;
|
let statusMsg = "Submitting to queue...";
|
||||||
|
|
||||||
while (Date.now() < deadline) {
|
while (Date.now() < deadline) {
|
||||||
await new Promise((r) => setTimeout(r, 3000));
|
await new Promise((r) => setTimeout(r, 3000));
|
||||||
elapsed += 3;
|
|
||||||
this.#progress = Math.min(90, (elapsed / 120) * 100);
|
|
||||||
this.#renderLoading();
|
|
||||||
|
|
||||||
const pollRes = await fetch(`/api/video-gen/${jobId}`);
|
const pollRes = await fetch(`/api/video-gen/${jobId}`);
|
||||||
if (!pollRes.ok) continue;
|
if (!pollRes.ok) continue;
|
||||||
const pollData = await pollRes.json();
|
const pollData = await pollRes.json();
|
||||||
|
|
||||||
|
// Update progress from real fal.ai status
|
||||||
|
const falStatus = pollData.fal_status;
|
||||||
|
const queuePos = pollData.queue_position;
|
||||||
|
if (falStatus === "IN_QUEUE") {
|
||||||
|
this.#progress = 10;
|
||||||
|
statusMsg = queuePos != null ? `In queue (position ${queuePos})...` : "Waiting in queue...";
|
||||||
|
} else if (falStatus === "IN_PROGRESS") {
|
||||||
|
this.#progress = Math.min(85, this.#progress < 40 ? 40 : this.#progress + 2);
|
||||||
|
statusMsg = "Generating video...";
|
||||||
|
} else if (!falStatus && pollData.status === "processing") {
|
||||||
|
this.#progress = Math.min(20, this.#progress + 2);
|
||||||
|
statusMsg = "Starting generation...";
|
||||||
|
}
|
||||||
|
this.#renderLoading(statusMsg);
|
||||||
|
|
||||||
if (pollData.status === "complete") {
|
if (pollData.status === "complete") {
|
||||||
const video: GeneratedVideo = {
|
const video: GeneratedVideo = {
|
||||||
id: crypto.randomUUID(),
|
id: crypto.randomUUID(),
|
||||||
|
|
@ -530,16 +542,16 @@ export class FolkVideoGen extends FolkShape {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#renderLoading() {
|
#renderLoading(message = "Generating video...") {
|
||||||
if (!this.#videoArea) return;
|
if (!this.#videoArea) return;
|
||||||
this.#videoArea.innerHTML = `
|
this.#videoArea.innerHTML = `
|
||||||
<div class="loading">
|
<div class="loading">
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<span>Generating video...</span>
|
<span>${this.#escapeHtml(message)}</span>
|
||||||
<div class="progress-bar">
|
<div class="progress-bar">
|
||||||
<div class="progress-fill" style="width: ${this.#progress}%"></div>
|
<div class="progress-fill" style="width: ${this.#progress}%"></div>
|
||||||
</div>
|
</div>
|
||||||
<span style="font-size: 11px; color: #64748b;">This may take 30-60 seconds</span>
|
<span style="font-size: 11px; color: #64748b;">This may take a few minutes</span>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1060,6 +1060,8 @@ interface VideoGenJob {
|
||||||
error?: string;
|
error?: string;
|
||||||
createdAt: number;
|
createdAt: number;
|
||||||
completedAt?: number;
|
completedAt?: number;
|
||||||
|
queuePosition?: number;
|
||||||
|
falStatus?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const videoGenJobs = new Map<string, VideoGenJob>();
|
const videoGenJobs = new Map<string, VideoGenJob>();
|
||||||
|
|
@ -1103,8 +1105,8 @@ async function processVideoGenJob(job: VideoGenJob) {
|
||||||
|
|
||||||
const { request_id } = await submitRes.json() as { request_id: string };
|
const { request_id } = await submitRes.json() as { request_id: string };
|
||||||
|
|
||||||
// Poll for completion (up to 5 min)
|
// Poll for completion (up to 10 min)
|
||||||
const deadline = Date.now() + 300_000;
|
const deadline = Date.now() + 600_000;
|
||||||
let responseUrl = "";
|
let responseUrl = "";
|
||||||
let completed = false;
|
let completed = false;
|
||||||
|
|
||||||
|
|
@ -1115,8 +1117,10 @@ async function processVideoGenJob(job: VideoGenJob) {
|
||||||
{ headers: falHeaders },
|
{ headers: falHeaders },
|
||||||
);
|
);
|
||||||
if (!statusRes.ok) continue;
|
if (!statusRes.ok) continue;
|
||||||
const statusData = await statusRes.json() as { status: string; response_url?: string };
|
const statusData = await statusRes.json() as { status: string; response_url?: string; queue_position?: number };
|
||||||
console.log(`[video-gen] Poll ${job.id}: status=${statusData.status}`);
|
console.log(`[video-gen] Poll ${job.id}: status=${statusData.status}`);
|
||||||
|
job.falStatus = statusData.status;
|
||||||
|
job.queuePosition = statusData.queue_position;
|
||||||
if (statusData.response_url) responseUrl = statusData.response_url;
|
if (statusData.response_url) responseUrl = statusData.response_url;
|
||||||
if (statusData.status === "COMPLETED") { completed = true; break; }
|
if (statusData.status === "COMPLETED") { completed = true; break; }
|
||||||
if (statusData.status === "FAILED") {
|
if (statusData.status === "FAILED") {
|
||||||
|
|
@ -1667,6 +1671,7 @@ app.get("/api/video-gen/:jobId", async (c) => {
|
||||||
|
|
||||||
const response: Record<string, any> = {
|
const response: Record<string, any> = {
|
||||||
job_id: job.id, status: job.status, created_at: job.createdAt,
|
job_id: job.id, status: job.status, created_at: job.createdAt,
|
||||||
|
fal_status: job.falStatus, queue_position: job.queuePosition,
|
||||||
};
|
};
|
||||||
if (job.status === "complete") {
|
if (job.status === "complete") {
|
||||||
response.url = job.resultUrl;
|
response.url = job.resultUrl;
|
||||||
|
|
|
||||||
|
|
@ -1143,7 +1143,7 @@ export async function revokeSpaceInvite(id: string, spaceSlug: string): Promise<
|
||||||
export interface StoredNotification {
|
export interface StoredNotification {
|
||||||
id: string;
|
id: string;
|
||||||
userDid: string;
|
userDid: string;
|
||||||
category: 'space' | 'module' | 'system' | 'social';
|
category: 'space' | 'module' | 'system' | 'social' | 'payment';
|
||||||
eventType: string;
|
eventType: string;
|
||||||
title: string;
|
title: string;
|
||||||
body: string | null;
|
body: string | null;
|
||||||
|
|
@ -1196,7 +1196,7 @@ async function rowToNotification(row: any): Promise<StoredNotification> {
|
||||||
export async function createNotification(notif: {
|
export async function createNotification(notif: {
|
||||||
id: string;
|
id: string;
|
||||||
userDid: string;
|
userDid: string;
|
||||||
category: 'space' | 'module' | 'system' | 'social';
|
category: 'space' | 'module' | 'system' | 'social' | 'payment';
|
||||||
eventType: string;
|
eventType: string;
|
||||||
title: string;
|
title: string;
|
||||||
body?: string;
|
body?: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue