小马爱学
131.57MB · 2025-12-17
? + ? + ? + ? = ? 创作飞轮
一句话:多模态 AIGC 把创作链路从“手工串联”升级为“自动流水线”,人类创意负责方向,机器负责体力活。
推理重且多样:图像生成、视频合成对显存/计算要求高,浏览器端难以独立完成。
实时交互 vs. 成本:用户希望“秒回”,但大模型贵。
跨模态一致性:文案和图像风格不一致,视频与旁白节奏错位。
数据流(从左到右):
用户输入 → 前端 Prompt 编排与模版化 → 边缘路由(AB、配额、缓存) → 模态服务(文本/图像/音频/视频) → 资产存储与 CDN → 前端预览/编辑 → 一键发布
前端(浏览器)
边缘(Edge Functions/Workers)
后端(GPU 集群/模型服务)
小图标地图:
以下示例聚焦“浏览器编排 + 边缘路由 + 后端推理”的最小可用框架。接口用占位符,你可以替换为自有服务。
// src/pipeline.js
// 核心思想:将一次创作拆成可并行/可重试的子任务,并在 UI 中流式展示
export async function createMultimodalProject({ topic, style, durationSec = 30 }) {
const anchor = await fetchJSON('/edge/anchor', { topic, style });
// 并行启动文案和视觉草图
const [scriptJob, storyboardJob] = await Promise.all([
postJSON('/edge/jobs/text', { anchor, length: Math.ceil(durationSec / 5) }),
postJSON('/edge/jobs/image-storyboard', { anchor, frames: 6 })
]);
// 流式订阅结果
const scriptStream = streamEvents(`/edge/jobs/${scriptJob.id}/events`);
const storyboardStream = streamEvents(`/edge/jobs/${storyboardJob.id}/events`);
return { anchor, scriptStream, storyboardStream };
}
async function fetchJSON(url, body) {
const res = await fetch(url, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify(body) });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
async function postJSON(url, payload) {
return fetchJSON(url, payload);
}
function streamEvents(url, onEvent) {
const es = new EventSource(url);
const listeners = new Set();
es.onmessage = (e) => {
const data = JSON.parse(e.data);
listeners.forEach(fn => fn(data));
};
es.onerror = () => { /* 可加重连 */ };
return {
subscribe: (fn) => (listeners.add(fn), () => listeners.delete(fn)),
close: () => es.close()
};
}
// src/timeline.js
// 将图像序列 + 文案字幕 + TTS 片段合成可预览的时间轴(非最终导出)
export function buildTimeline({ images, captions, ttsClips, bpm = 90 }) {
const timeline = [];
const beat = 60_000 / bpm;
let t = 0;
for (let i = 0; i < images.length; i++) {
const img = images[i];
const cap = captions[i] || '';
const voice = ttsClips[i];
timeline.push({
type: 'frame',
start: t,
end: t + 4 * beat,
image: img.url,
caption: cap,
voice: voice?.url
});
t += 4 * beat;
}
return timeline;
}
// public/preview-worker.js
self.onmessage = async (e) => {
const { canvas, timeline, width, height } = e.data;
const ctx = canvas.getContext('2d');
const start = performance.now();
let nextIndex = 0;
const images = new Map();
function load(src) {
return new Promise((resolve) => {
if (images.has(src)) return resolve(images.get(src));
const img = new Image();
img.onload = () => (images.set(src, img), resolve(img));
img.src = src;
});
}
function drawCaption(text) {
ctx.font = '24px system-ui';
ctx.fillStyle = 'rgba(0,0,0,0.5)';
ctx.fillRect(0, height - 60, width, 60);
ctx.fillStyle = '#fff';
ctx.fillText(text, 24, height - 24);
}
const render = async () => {
const now = performance.now() - start;
const item = timeline[nextIndex];
if (!item) return requestAnimationFrame(render);
if (now >= item.start && now < item.end) {
const img = await load(item.image);
ctx.drawImage(img, 0, 0, width, height);
if (item.caption) drawCaption(item.caption);
} else if (now >= item.end) {
nextIndex++;
}
requestAnimationFrame(render);
};
render();
};
// src/preview.js
export function startPreview(timeline, canvas) {
const worker = new Worker('/preview-worker.js', { type: 'module' });
const offscreen = canvas.transferControlToOffscreen();
worker.postMessage({ canvas: offscreen, timeline, width: canvas.width, height: canvas.height }, [offscreen]);
return () => worker.terminate();
}
// edge/route.js (示意:Cloudflare Workers / Vercel Edge Functions 风格)
export default async function handler(req) {
const url = new URL(req.url);
if (url.pathname === '/edge/anchor') {
const { topic, style } = await req.json();
const anchor = await buildAnchor(topic, style);
return json(anchor);
}
if (url.pathname.startsWith('/edge/jobs/')) {
// 事件流转发到后端任务系统
return proxySSE(req, process.env.JOB_BUS_URL);
}
if (url.pathname === '/edge/jobs/text') {
rateLimit(req, { key: userKey(req), rpm: 30 });
const payload = await req.json();
// 命中缓存直接返回
const cacheKey = hash(payload);
const cached = await EDGE_KV.get(cacheKey, 'json');
if (cached) return json(cached);
const job = await submitJob('text', payload);
await EDGE_KV.put(cacheKey, JSON.stringify(job), { expirationTtl: 60 });
return json(job);
}
// 其他路由...
return new Response('Not Found', { status: 404 });
}
function json(data) { return new Response(JSON.stringify(data), { headers: { 'Content-Type': 'application/json' } }); }
// server/jobs/text.js
// 先用小模型草稿,后用大模型精修,并保持结构化输出
import { eventBus } from './bus.js';
export async function runTextJob(job) {
const { anchor, length } = job.payload;
const draft = await smallLM(anchor, length, { temperature: 0.7 });
eventBus.emit(job.id, { phase: 'draft', content: draft });
const refined = await largeLM({ outline: draft.outline, style: anchor.style, constraints: anchor.constraints });
const script = enforceSchema(refined, {
type: 'object',
properties: { segments: { type: 'array', items: { type: 'object', properties: { caption: { type: 'string' }, durationMs: { type: 'number' } }, required: ['caption', 'durationMs'] } } },
required: ['segments']
});
eventBus.emit(job.id, { phase: 'final', content: script });
return script;
}
创作从来不是把灵感关在服务器机房,而是把灵感调度到用户眼前。
愿你的 Web 创作工作室像一台精密乐团:提示词是指挥棒,模型是乐手,时间轴是节拍器,内容在浏览器里,现场开演。????