newtask.html 5.97 KB
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<title>新建视频任务 · MiniMax</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<header class="topbar">
  <h1>MiniMax 视频生成测试台</h1>
  <div class="spacer"></div>
  <label class="small">fal API Key</label>
  <input type="password" id="apiKeyInput" placeholder="key_id:key_secret" />
  <button class="secondary" id="saveKeyBtn">保存</button>
</header>

<div class="layout">
  <aside class="sidebar">
    <div class="toolbar">
      <input type="search" id="searchInput" placeholder="搜索 prompt..." />
      <select id="statusFilter">
        <option value="">全部状态</option>
        <option value="Processing">生成中</option>
        <option value="succeeded">成功</option>
        <option value="failed">失败</option>
        <option value="cancelled">取消</option>
      </select>
      <select id="modelFilter">
        <option value="">全部模型</option>
        <option value="MiniMax-H3">MiniMax-H3</option>
        <option value="MiniMax-H3-Max">MiniMax-H3-Max</option>
      </select>
      <select id="sortSelect">
        <option value="created_desc">创建时间 ↓</option>
        <option value="created_asc">创建时间 ↑</option>
        <option value="elapsed_asc">耗时 ↑</option>
        <option value="elapsed_desc">耗时 ↓</option>
      </select>
      <button id="newTaskBtn">+ 新建任务</button>
    </div>
    <div class="task-list" id="taskList"></div>
  </aside>

  <main class="main">
    <div class="card">
      <div class="field">
        <label>模型</label>
        <select id="model">
          <option value="MiniMax-H3-Max">MiniMax-H3-Max(速度优先,480P/768P,5-15s)</option>
          <option value="MiniMax-H3">MiniMax-H3(画质更高,480P/768P/2K/4K,4-15s)</option>
        </select>
      </div>

      <div class="field">
        <label>Prompt</label>
        <textarea id="prompt" placeholder="描述画面主体、动作、镜头运镜、氛围、风格;建议写清楚景别(远景/近景/特写)与光线质感。最多 7000 字符。"></textarea>
        <div class="hint">参考 <a href="https://platform.minimax.cn/docs/guides/video-prompt" target="_blank">Prompt 撰写指南</a>:技术参数(比例/时长/分辨率)+ 叙事细节 + 镜头/声音方向,用自然语言描述,无需特殊标签。经 fal.ai 转发,暂不支持首帧/尾帧图片。</div>
      </div>

      <div class="row-3">
        <div class="field">
          <label>分辨率</label>
          <select id="resolution"></select>
        </div>
        <div class="field">
          <label>时长(秒)</label>
          <input type="number" id="duration" />
        </div>
        <div class="field">
          <label>画面比例</label>
          <select id="ratio">
            <option value="16:9" selected>16:9</option>
            <option value="21:9">21:9</option>
            <option value="4:3">4:3</option>
            <option value="1:1">1:1</option>
            <option value="3:4">3:4</option>
            <option value="9:16">9:16</option>
          </select>
        </div>
      </div>

      <div id="errorBox" class="error-box" hidden></div>

      <div class="form-actions">
        <button id="submitBtn">提交任务</button>
        <button class="secondary" onclick="location.href='index.html'">取消</button>
      </div>
    </div>
  </main>
</div>

<script src="app.js"></script>
<script>
const apiKeyInput = document.getElementById("apiKeyInput");
apiKeyInput.value = getApiKey();
document.getElementById("saveKeyBtn").onclick = (e) => {
  setApiKey(apiKeyInput.value);
  const btn = e.currentTarget;
  const old = btn.textContent;
  btn.textContent = "已保存 ✓";
  setTimeout(() => (btn.textContent = old), 1200);
};

initSidebar(null);

const modelSel = document.getElementById("model");
const resSel = document.getElementById("resolution");
const durInput = document.getElementById("duration");

function syncModelLimits() {
  const limits = MODEL_LIMITS[modelSel.value];
  resSel.innerHTML = limits.resolutions.map((r) => `<option value="${r}">${r}</option>`).join("");
  durInput.min = limits.duration[0];
  durInput.max = limits.duration[1];
  durInput.value = limits.duration[0];
}
modelSel.addEventListener("change", syncModelLimits);
syncModelLimits();

document.getElementById("submitBtn").addEventListener("click", async () => {
  const errorBox = document.getElementById("errorBox");
  errorBox.hidden = true;

  const prompt = document.getElementById("prompt").value.trim();
  if (!prompt) {
    errorBox.hidden = false;
    errorBox.textContent = "请填写 Prompt";
    return;
  }
  const limits = MODEL_LIMITS[modelSel.value];
  const duration = Number(durInput.value);
  if (duration < limits.duration[0] || duration > limits.duration[1]) {
    errorBox.hidden = false;
    errorBox.textContent = `时长需在 ${limits.duration[0]}-${limits.duration[1]} 秒之间`;
    return;
  }

  const task = {
    id: uid(),
    model: modelSel.value,
    falModel: limits.falModel,
    prompt,
    resolution: resSel.value,
    duration,
    ratio: document.getElementById("ratio").value,
    status: "Processing",
    requestId: null,
    statusUrl: null,
    resultUrl: null,
    videoUrl: null,
    error: null,
    createdAt: Date.now(),
    submittedAt: null,
    completedAt: null,
    lastRaw: null,
  };

  const btn = document.getElementById("submitBtn");
  btn.disabled = true;
  btn.textContent = "提交中...";
  try {
    const res = await submitVideoTask(task);
    task.requestId = res.request_id;
    task.statusUrl = res.status_url;
    task.resultUrl = res.response_url;
    task.submittedAt = Date.now();
    task.lastRaw = res;
    upsertTask(task);
    location.href = `taskdetail.html?id=${task.id}`;
  } catch (err) {
    errorBox.hidden = false;
    errorBox.textContent = err.message + (err.body ? "\n\n" + JSON.stringify(err.body, null, 2) : "");
    btn.disabled = false;
    btn.textContent = "提交任务";
  }
});
</script>
</body>
</html>