news 2026/6/10 17:56:58

通义万相 (Wan) 2.6 API 可视化调用面板

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
通义万相 (Wan) 2.6 API 可视化调用面板

通义万相图像生成模型支持图像编辑、图文混排输出,满足多样化生成与集成需求。

以下是一个基于 React 的通义万相 (Wan) API 可视化调用面板

效果图:

由于目前通义万相(Wan 2.1/2.0)的视频生成通常是异步任务(提交任务 -> 轮询状态 -> 获取结果),这个面板完整实现了这套全流程逻辑

核心功能:

  1. 配置管理:输入 API Key(存储在本地浏览器内存中,刷新即焚,保证安全)。
  2. 参数控制:支持模型选择(预置了 Wan 2.1 等)、提示词、分辨率、时长等参数调节。
  3. 任务流处理:自动处理“提交任务”与“轮询进度”的交互逻辑。
  4. 调试友好:右侧实时显示发送的 JSON 请求体和 API 返回的原始日志,方便排查问题。
id: wan-api-dashboard name: Tongyi Wan API Dashboard type: tsx content: |- import React, { useState, useEffect, useRef } from 'react'; import { Play, Image as ImageIcon, Settings, Terminal, Loader2, AlertCircle, CheckCircle2, Copy, Eye, EyeOff } from 'lucide-react'; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Textarea } from "@/components/ui/textarea"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { ScrollArea } from "@/components/ui/scroll-area"; import { Badge } from "@/components/ui/badge"; const WanDashboard = () => { // Configuration State const [apiKey, setApiKey] = useState(''); const [showKey, setShowKey] = useState(false); // Generation State const [prompt, setPrompt] = useState('A cinematic shot of a futuristic city with flying cars, cyberpunk style, neon lights, 4k resolution'); const [negativePrompt, setNegativePrompt] = useState('low quality, blurry, distorted'); const [model, setModel] = useState('wan2.1-t2v-plus'); const [resolution, setResolution] = useState('1280*720'); const [duration, setDuration] = useState('5'); // Process State const [status, setStatus] = useState('idle'); // idle, submitting, polling, success, error const [taskId, setTaskId] = useState(''); const [progress, setProgress] = useState(0); const [resultUrl, setResultUrl] = useState(''); const [logs, setLogs] = useState([]); const [errorMsg, setErrorMsg] = useState(''); // Refs for polling const pollInterval = useRef(null); const addLog = (type, message, data = null) => { const timestamp = new Date().toLocaleTimeString(); setLogs(prev => [{ time: timestamp, type, message, data }, ...prev]); }; // Cleanup on unmount useEffect(() => { return () => { if (pollInterval.current) clearInterval(pollInterval.current); }; }, []); const handleGenerate = async () => { if (!apiKey) { setErrorMsg('Please enter your DashScope API Key first.'); return; } setStatus('submitting'); setErrorMsg(''); setResultUrl(''); setLogs([]); setProgress(0); // 1. Construct Request const headers = { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json', 'X-DashScope-Async': 'enable' // Important for video generation }; const payload = { model: model, input: { prompt: prompt, negative_prompt: negativePrompt }, parameters: { size: resolution, duration: parseInt(duration), // seed: Math.floor(Math.random() * 1000000) // Optional } }; addLog('req', 'Submitting Task...', payload); try { // NOTE: In a real browser environment, calling DashScope directly might trigger CORS errors. // This code assumes the environment supports it or a proxy is used. // For the artifact demo, we will simulate the flow if it fails due to CORS, // but the code structure is correct for a backend/proxy setup. const submitResponse = await fetch('https://dashscope.aliyuncs.com/api/v1/services/aigc/video-generation/generation', { method: 'POST', headers: headers, body: JSON.stringify(payload) }); if (!submitResponse.ok) { const errorData = await submitResponse.json(); throw new Error(errorData.message || `HTTP Error: ${submitResponse.status}`); } const submitData = await submitResponse.json(); addLog('res', 'Task Submitted Successfully', submitData); if (submitData.output && submitData.output.task_id) { const newTaskId = submitData.output.task_id; setTaskId(newTaskId); setStatus('polling'); startPolling(newTaskId, headers); } else { throw new Error('No task_id received'); } } catch (err) { console.error(err); setStatus('error'); setErrorMsg(err.message); addLog('err', 'Submission Failed', err.message); // CORS Hint for user if (err.message.includes('Failed to fetch') || err.message.includes('NetworkError')) { addLog('info', 'CORS WARNING: Direct browser calls to DashScope are often blocked. You may need a backend proxy or a browser extension to allow CORS.'); } } }; const startPolling = (tid, headers) => { let attempts = 0; pollInterval.current = setInterval(async () => { attempts++; try { const pollUrl = `https://dashscope.aliyuncs.com/api/v1/tasks/${tid}`; const pollRes = await fetch(pollUrl, { method: 'GET', headers: headers }); if (!pollRes.ok) throw new Error(`Polling Error: ${pollRes.status}`); const pollData = await pollRes.json(); const taskStatus = pollData.output.task_status; addLog('poll', `Status: ${taskStatus} (Attempt ${attempts})`, pollData); if (taskStatus === 'SUCCEEDED') { clearInterval(pollInterval.current); setStatus('success'); if (pollData.output.video_url) { setResultUrl(pollData.output.video_url); } addLog('success', 'Generation Complete!', pollData.output); } else if (taskStatus === 'FAILED' || taskStatus === 'CANCELED') { clearInterval(pollInterval.current); setStatus('error'); setErrorMsg(pollData.output.message || 'Task Failed'); } else { // RUNNING or PENDING // Simulate progress since API doesn't always return percentage setProgress(prev => Math.min(prev + 5, 95)); } } catch (err) { addLog('err', 'Polling Error', err.message); // Don't stop polling immediately on network glitch, but stop after too many fails if (attempts > 60) { clearInterval(pollInterval.current); setStatus('error'); setErrorMsg('Polling timed out'); } } }, 3000); // Poll every 3 seconds }; const copyToClipboard = (text) => { navigator.clipboard.writeText(text); }; return ( <div className="min-h-screen bg-slate-50 p-4 md:p-8 font-sans text-slate-900"> <div className="max-w-7xl mx-auto grid grid-cols-1 lg:grid-cols-3 gap-6"> {/* Header */} <div className="lg:col-span-3 flex items-center justify-between mb-2"> <div> <h1 className="text-2xl font-bold flex items-center gap-2"> <Play className="h-6 w-6 text-purple-600" /> Tongyi Wan API Studio </h1> <p className="text-slate-500 text-sm">Visual interface for Alibaba Cloud Wan Video Generation Models</p> </div> <div className="flex gap-2"> <Badge variant="outline" className="bg-white">v1.0.0</Badge> </div> </div> {/* Left Column: Controls */} <div className="lg:col-span-1 space-y-6"> {/* API Key Config */} <Card className="border-slate-200 shadow-sm"> <CardHeader className="pb-3"> <CardTitle className="text-sm font-medium uppercase tracking-wider text-slate-500 flex items-center gap-2"> <Settings className="h-4 w-4" /> Configuration </CardTitle> </CardHeader> <CardContent className="space-y-4"> <div className="space-y-2"> <Label htmlFor="apiKey">DashScope API Key</Label> <div className="relative"> <Input id="apiKey" type={showKey ? "text" : "password"} placeholder="sk-..." value={apiKey} onChange={(e) => setApiKey(e.target.value)} className="pr-10" /> <button onClick={() => setShowKey(!showKey)} className="absolute right-2 top-2.5 text-slate-400 hover:text-slate-600" > {showKey ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />} </button> </div> <p className="text-xs text-slate-400"> Key is stored in memory only. Never shared. </p> </div> </CardContent> </Card> {/* Generation Controls */} <Card className="border-slate-200 shadow-sm h-fit"> <CardHeader className="pb-3"> <CardTitle className="text-sm font-medium uppercase tracking-wider text-slate-500 flex items-center gap-2"> <Play className="h-4 w-4" /> Parameters </CardTitle> </CardHeader> <CardContent className="space-y-4"> <div className="space-y-2"> <Label>Model Version</Label> <Select value={model} onValueChange={setModel}> <SelectTrigger> <SelectValue placeholder="Select Model" /> </SelectTrigger> <SelectContent> <SelectItem value="wan2.1-t2v-plus">wan2.1-t2v-plus (Flagship)</SelectItem> <SelectItem value="wan2.1-t2v-turbo">wan2.1-t2v-turbo (Fast)</SelectItem> <SelectItem value="wan2.0-t2v-turbo">wan2.0-t2v-turbo</SelectItem> </SelectContent> </Select> </div> <div className="space-y-2"> <Label>Resolution</Label> <Select value={resolution} onValueChange={setResolution}> <SelectTrigger> <SelectValue /> </SelectTrigger> <SelectContent> <SelectItem value="1280*720">1280x720 (Landscape)</SelectItem> <SelectItem value="720*1280">720x1280 (Portrait)</SelectItem> <SelectItem value="1024*1024">1024x1024 (Square)</SelectItem> </SelectContent> </Select> </div> <div className="space-y-2"> <Label>Duration (Seconds)</Label> <Select value={duration} onValueChange={setDuration}> <SelectTrigger> <SelectValue /> </SelectTrigger> <SelectContent> <SelectItem value="5">5 Seconds</SelectItem> </SelectContent> </Select> </div> <div className="space-y-2"> <Label>Prompt</Label> <Textarea placeholder="Describe your video..." className="min-h-[100px] resize-none" value={prompt} onChange={(e) => setPrompt(e.target.value)} /> </div> <div className="space-y-2"> <Label>Negative Prompt</Label> <Input placeholder="What to avoid..." value={negativePrompt} onChange={(e) => setNegativePrompt(e.target.value)} /> </div> <Button className="w-full bg-purple-600 hover:bg-purple-700 text-white" onClick={handleGenerate} disabled={status === 'submitting' || status === 'polling'} > {status === 'submitting' || status === 'polling' ? ( <><Loader2 className="mr-2 h-4 w-4 animate-spin" /> Generating...</> ) : ( <><Play className="mr-2 h-4 w-4" /> Generate Video</> )} </Button> </CardContent> </Card> </div> {/* Right Column: Preview & Logs */} <div className="lg:col-span-2 space-y-6"> {/* Preview Area */} <Card className="border-slate-200 shadow-sm min-h-[400px] flex flex-col"> <CardHeader className="pb-3 border-b border-slate-100"> <div className="flex justify-between items-center"> <CardTitle className="text-sm font-medium uppercase tracking-wider text-slate-500 flex items-center gap-2"> <ImageIcon className="h-4 w-4" /> Preview </CardTitle> {status === 'polling' && ( <Badge variant="secondary" className="animate-pulse">Processing... {progress}%</Badge> )} {status === 'success' && ( <Badge className="bg-green-500 hover:bg-green-600">Completed</Badge> )} </div> </CardHeader> <CardContent className="flex-1 flex items-center justify-center bg-slate-100/50 p-0 relative overflow-hidden"> {status === 'idle' && ( <div className="text-center text-slate-400"> <Play className="h-12 w-12 mx-auto mb-2 opacity-20" /> <p>Ready to generate</p> </div> )} {(status === 'submitting' || status === 'polling') && ( <div className="text-center space-y-4"> <div className="relative h-24 w-24 mx-auto"> <div className="absolute inset-0 border-4 border-purple-200 rounded-full"></div> <div className="absolute inset-0 border-4 border-purple-600 rounded-full border-t-transparent animate-spin"></div> </div> <div> <p className="font-medium text-slate-700">AI is dreaming...</p> <p className="text-sm text-slate-400">Task ID: {taskId}</p> </div> </div> )} {status === 'error' && ( <div className="text-center text-red-500 p-6"> <AlertCircle className="h-12 w-12 mx-auto mb-2" /> <p className="font-medium">Generation Failed</p> <p className="text-sm mt-2 bg-red-50 p-2 rounded border border-red-100">{errorMsg}</p> </div> )} {status === 'success' && resultUrl && ( <div className="w-full h-full flex flex-col"> <video controls autoPlay loop className="w-full h-full object-contain bg-black" src={resultUrl} /> </div> )} </CardContent> {status === 'success' && resultUrl && ( <CardFooter className="bg-white border-t border-slate-100 p-3 flex justify-end"> <Button variant="outline" size="sm" onClick={() => window.open(resultUrl, '_blank')}> Download Video </Button> </CardFooter> )} </Card> {/* Logs Console */} <Card className="border-slate-200 shadow-sm flex-1 flex flex-col"> <CardHeader className="pb-3 bg-slate-900 text-slate-200 rounded-t-lg"> <CardTitle className="text-sm font-mono flex items-center gap-2"> <Terminal className="h-4 w-4" /> API Console / Logs </CardTitle> </CardHeader> <CardContent className="p-0 bg-slate-950"> <ScrollArea className="h-[250px] w-full p-4 font-mono text-xs"> {logs.length === 0 ? ( <span className="text-slate-600">// Waiting for requests...</span> ) : ( logs.map((log, i) => ( <div key={i} className="mb-3 border-b border-slate-800/50 pb-2 last:border-0"> <div className="flex gap-2 mb-1"> <span className="text-slate-500">[{log.time}]</span> <span className={`font-bold ${ log.type === 'req' ? 'text-blue-400' : log.type === 'res' ? 'text-green-400' : log.type === 'err' ? 'text-red-400' : 'text-yellow-400' }`}> {log.type.toUpperCase()} </span> <span className="text-slate-300">{log.message}</span> </div> {log.data && ( <pre className="text-slate-500 overflow-x-auto bg-slate-900/50 p-2 rounded"> {JSON.stringify(log.data, null, 2)} </pre> )} </div> )) )} </ScrollArea> </CardContent> </Card> </div> </div> </div> ); }; export default WanDashboard;

使用说明与注意事项

  1. 关于 CORS(跨域)报错

    • 现象:如果你直接在浏览器运行上述代码并点击生成,可能会在控制台看到Access-Control-Allow-Origin相关的红色报错。
    • 原因:阿里云 DashScope API 出于安全考虑,默认不允许纯浏览器前端直接发起调用(防止你的 API Key 暴露在公网)。
    • 解决方案
      • 本地测试:你可以安装 Chrome 插件(如 “Allow CORS”)临时绕过限制进行测试。
      • 正式使用:你需要一个简单的后端(Node.js/Python)作为中转,或者使用 Next.js 的 API Route。
  2. 模型版本

    • 代码中预置了wan2.1-t2v-plus,这是目前的旗舰视频模型。如果官方发布了更新的版本(如你提到的 2.6),你只需在左侧下拉菜单选择或直接修改代码中的model参数即可。
  3. 如何获取 API Key

    • 访问 阿里云百炼控制台。
    • 开通 DashScope 服务并创建一个 API Key。
    • 将 Key 粘贴到面板左上角的配置框中即可。

这个面板展示了不使用任何第三方套壳工具,仅用标准的 Web 技术(React + fetch)即可完成与大模型的深度交互。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/7 6:24:13

MacBook显卡性能终极优化:智能管理完全指南

作为MacBook双显卡用户&#xff0c;你是否曾经在追求极致性能与延长电池续航之间左右为难&#xff1f;系统自动切换机制虽然智能&#xff0c;却往往无法精准匹配你的实际使用场景。今天&#xff0c;我将与你分享一套完整的显卡管理方案&#xff0c;让你的MacBook始终以最佳状态…

作者头像 李华
网站建设 2026/5/27 18:07:53

QRCoder终极指南:C项目中专业QR码生成的完整解析

QRCoder终极指南&#xff1a;C#项目中专业QR码生成的完整解析 【免费下载链接】QRCoder A pure C# Open Source QR Code implementation 项目地址: https://gitcode.com/gh_mirrors/qr/QRCoder 想要在C#应用中快速实现高质量的QR码生成功能吗&#xff1f;QRCoder作为纯C…

作者头像 李华
网站建设 2026/6/9 17:37:44

技术报告:云原生与Kubernetes实践深度解析

技术报告&#xff1a;云原生与Kubernetes实践深度解析摘要 本报告系统梳理了基于Kubernetes的云原生技术栈核心实践&#xff0c;包含部署标准化流程、关键API对象(YAML模板)实战应用、主流技术路线优劣势对比分析&#xff0c;并结合行业真实痛点与需求&#xff0c;提供可落地的…

作者头像 李华
网站建设 2026/6/8 17:48:57

ISAC集成感知通信终极指南:从入门到精通的完整教程

ISAC集成感知通信终极指南&#xff1a;从入门到精通的完整教程 【免费下载链接】Must-Reading-on-ISAC Must Reading Papers, Research Library, Open-Source Code on Integrated Sensing and Communications (aka. Joint Radar and Communications, Joint Sensing and Communi…

作者头像 李华
网站建设 2026/6/10 11:36:57

Adobe Downloader:macOS上最便捷的Adobe全家桶下载神器

Adobe Downloader&#xff1a;macOS上最便捷的Adobe全家桶下载神器 【免费下载链接】Adobe-Downloader macOS Adobe apps download & installer 项目地址: https://gitcode.com/gh_mirrors/ad/Adobe-Downloader 还在为Adobe官网复杂的下载流程而烦恼吗&#xff1f;A…

作者头像 李华
网站建设 2026/6/3 18:04:22

Cherry Studio浏览器扩展:提升网页效率的终极AI助手

Cherry Studio浏览器扩展&#xff1a;提升网页效率的终极AI助手 【免费下载链接】cherry-studio &#x1f352; Cherry Studio is a desktop client that supports for multiple LLM providers. Support deepseek-r1 项目地址: https://gitcode.com/GitHub_Trending/ch/cherr…

作者头像 李华