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) => {
|
||||
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"));
|
||||
|
||||
if (target) target.classList.add("drag-over");
|
||||
const items = this.#rankPanel!.querySelectorAll(".rank-item:not(.dragging)");
|
||||
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) => {
|
||||
|
|
|
|||
|
|
@ -525,6 +525,7 @@ export class FolkChoiceSpider extends FolkShape {
|
|||
#selectedOptionId = "";
|
||||
#drawerOpen = false;
|
||||
#settingsOpen = false;
|
||||
#isSliding = false;
|
||||
|
||||
// DOM refs
|
||||
#wrapperEl: HTMLElement | null = null;
|
||||
|
|
@ -748,7 +749,7 @@ export class FolkChoiceSpider extends FolkShape {
|
|||
#render() {
|
||||
this.#renderOptionTabs();
|
||||
this.#renderChart();
|
||||
this.#renderSliders();
|
||||
if (!this.#isSliding) this.#renderSliders();
|
||||
this.#renderLegend();
|
||||
this.#renderSummary();
|
||||
if (this.#drawerOpen) this.#renderDrawer();
|
||||
|
|
@ -889,7 +890,9 @@ export class FolkChoiceSpider extends FolkShape {
|
|||
this.#slidersEl.querySelectorAll(".slider-input").forEach((slider) => {
|
||||
const input = slider as HTMLInputElement;
|
||||
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) => {
|
||||
e.stopPropagation();
|
||||
const critId = input.dataset.crit!;
|
||||
|
|
|
|||
|
|
@ -459,7 +459,7 @@ export class FolkVideoGen extends FolkShape {
|
|||
this.#error = null;
|
||||
this.#progress = 0;
|
||||
if (this.#generateBtn) this.#generateBtn.disabled = true;
|
||||
this.#renderLoading();
|
||||
this.#renderLoading("Submitting...");
|
||||
|
||||
try {
|
||||
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();
|
||||
|
||||
// Poll for job completion (up to 5 minutes)
|
||||
// Poll for job completion (up to 10 minutes)
|
||||
const jobId = submitData.job_id;
|
||||
if (!jobId) throw new Error("No job ID returned");
|
||||
|
||||
const deadline = Date.now() + 300_000;
|
||||
let elapsed = 0;
|
||||
const deadline = Date.now() + 600_000;
|
||||
let statusMsg = "Submitting to queue...";
|
||||
|
||||
while (Date.now() < deadline) {
|
||||
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}`);
|
||||
if (!pollRes.ok) continue;
|
||||
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") {
|
||||
const video: GeneratedVideo = {
|
||||
id: crypto.randomUUID(),
|
||||
|
|
@ -530,16 +542,16 @@ export class FolkVideoGen extends FolkShape {
|
|||
}
|
||||
}
|
||||
|
||||
#renderLoading() {
|
||||
#renderLoading(message = "Generating video...") {
|
||||
if (!this.#videoArea) return;
|
||||
this.#videoArea.innerHTML = `
|
||||
<div class="loading">
|
||||
<div class="spinner"></div>
|
||||
<span>Generating video...</span>
|
||||
<span>${this.#escapeHtml(message)}</span>
|
||||
<div class="progress-bar">
|
||||
<div class="progress-fill" style="width: ${this.#progress}%"></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>
|
||||
`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1060,6 +1060,8 @@ interface VideoGenJob {
|
|||
error?: string;
|
||||
createdAt: number;
|
||||
completedAt?: number;
|
||||
queuePosition?: number;
|
||||
falStatus?: string;
|
||||
}
|
||||
|
||||
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 };
|
||||
|
||||
// Poll for completion (up to 5 min)
|
||||
const deadline = Date.now() + 300_000;
|
||||
// Poll for completion (up to 10 min)
|
||||
const deadline = Date.now() + 600_000;
|
||||
let responseUrl = "";
|
||||
let completed = false;
|
||||
|
||||
|
|
@ -1115,8 +1117,10 @@ async function processVideoGenJob(job: VideoGenJob) {
|
|||
{ headers: falHeaders },
|
||||
);
|
||||
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}`);
|
||||
job.falStatus = statusData.status;
|
||||
job.queuePosition = statusData.queue_position;
|
||||
if (statusData.response_url) responseUrl = statusData.response_url;
|
||||
if (statusData.status === "COMPLETED") { completed = true; break; }
|
||||
if (statusData.status === "FAILED") {
|
||||
|
|
@ -1667,6 +1671,7 @@ app.get("/api/video-gen/:jobId", async (c) => {
|
|||
|
||||
const response: Record<string, any> = {
|
||||
job_id: job.id, status: job.status, created_at: job.createdAt,
|
||||
fal_status: job.falStatus, queue_position: job.queuePosition,
|
||||
};
|
||||
if (job.status === "complete") {
|
||||
response.url = job.resultUrl;
|
||||
|
|
|
|||
|
|
@ -1143,7 +1143,7 @@ export async function revokeSpaceInvite(id: string, spaceSlug: string): Promise<
|
|||
export interface StoredNotification {
|
||||
id: string;
|
||||
userDid: string;
|
||||
category: 'space' | 'module' | 'system' | 'social';
|
||||
category: 'space' | 'module' | 'system' | 'social' | 'payment';
|
||||
eventType: string;
|
||||
title: string;
|
||||
body: string | null;
|
||||
|
|
@ -1196,7 +1196,7 @@ async function rowToNotification(row: any): Promise<StoredNotification> {
|
|||
export async function createNotification(notif: {
|
||||
id: string;
|
||||
userDid: string;
|
||||
category: 'space' | 'module' | 'system' | 'social';
|
||||
category: 'space' | 'module' | 'system' | 'social' | 'payment';
|
||||
eventType: string;
|
||||
title: string;
|
||||
body?: string;
|
||||
|
|
|
|||
Loading…
Reference in New Issue