> ## Documentation Index
> Fetch the complete documentation index at: https://ppio.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 视频生成通用接口

export const UnifiedAPI = () => {
  if (typeof document === "undefined") {
    return null;
  }
  const [config, setConfig] = useState(window.ppinfraRemoteData?.videoUnifyAPIConfig?.data || null);
  const [chosenIndex, setChosenIndex] = useState(0);
  const [isOpen, setIsOpen] = useState(false);
  const [isLoading, setIsLoading] = useState(false);
  const [searchTerm, setSearchTerm] = useState("");
  useEffect(() => {
    if (!window.ppinfraRemoteData?.videoUnifyAPIConfig?.data) {
      setIsLoading(true);
      fetch('https://api.ppio.com/v3/admin/video-unify-api/config').then(response => {
        if (!response.ok) {
          throw new Error("Network response was not ok");
        }
        return response.json();
      }).then(data => {
        setConfig(data.configs);
      }).catch(error => {
        console.error('Failed to fetch config:', error);
      }).finally(() => {
        setIsLoading(false);
      });
    }
  }, []);
  const data = useMemo(() => {
    return config?.[chosenIndex];
  }, [config, chosenIndex]);
  useEffect(() => {
    const handleClickOutside = event => {
      if (isOpen && !event.target.closest(".unified-api-selector-container")) {
        setIsOpen(false);
        setSearchTerm("");
      }
    };
    document.addEventListener("mousedown", handleClickOutside);
    return () => {
      document.removeEventListener("mousedown", handleClickOutside);
    };
  }, [isOpen]);
  const filteredConfig = useMemo(() => {
    if (!config || !searchTerm.trim()) {
      return config || [];
    }
    const term = searchTerm.toLowerCase();
    return config.filter(item => {
      const model = (item.model || '').toLowerCase();
      const description = (item.description || '').toLowerCase();
      return model.includes(term) || description.includes(term);
    });
  }, [config, searchTerm]);
  const modelSelector = useMemo(() => {
    if (!config || config.length <= 1) {
      return null;
    }
    const selectedModel = config[chosenIndex]?.model || '-';
    return <div className="unified-api-selector-container">
        <label className="unified-api-selector-label">选择要使用的模型</label>
        <div className="unified-api-selector-wrapper">
          <button type="button" onClick={() => setIsOpen(!isOpen)} className="unified-api-selector-button">
            <span>{selectedModel}</span>
            <svg width="16" height="16" viewBox="0 0 20 20" fill="none" className={`unified-api-selector-arrow ${isOpen ? "open" : ""}`}>
              <path stroke="#6b7280" strokeLinecap="round" strokeLinejoin="round" strokeWidth="1.5" d="m6 8 4 4 4-4" />
            </svg>
          </button>
          {isOpen && <div className="unified-api-selector-dropdown">
              <div className="unified-api-search-container">
                <input type="text" placeholder="搜索模型..." value={searchTerm} onChange={e => setSearchTerm(e.target.value)} className="unified-api-search-input" onClick={e => e.stopPropagation()} />
              </div>
              <div className="unified-api-options-container">
                {filteredConfig.length > 0 ? filteredConfig.map((item, filteredIndex) => {
      const originalIndex = config.findIndex(configItem => configItem === item);
      return <div key={originalIndex} onClick={() => {
        setChosenIndex(originalIndex);
        setIsOpen(false);
        setSearchTerm("");
      }} className={`unified-api-selector-option ${originalIndex === chosenIndex ? "selected" : ""}`}>
                        {item.model || '-'}
                      </div>;
    }) : <div className="unified-api-no-results">
                    没有找到匹配的模型
                  </div>}
              </div>
            </div>}
        </div>
      </div>;
  }, [config, chosenIndex, isOpen, searchTerm, filteredConfig]);
  const headerFields = useMemo(() => {
    return <>
        <h2>请求头</h2>
        <ParamField header="Content-Type" type="string" required={true}>
          枚举值: <span className="unified-api-inline-label">application/json</span>
        </ParamField>
        <ParamField header="Authorization" type="string" required={true}>
          Bearer 身份验证格式: Bearer {`{{API 秘钥}}`}。
        </ParamField>
      </>;
  }, []);
  const requestBodyFields = useMemo(() => {
    if (!data?.json_schema) {
      return null;
    }
    const schema = JSON.parse(data.json_schema);
    const getFields = () => {
      return Object.entries(schema.properties).filter(([key, value]) => key !== "model").map(([key, value]) => ({
        name: key,
        ...value
      }));
    };
    const getFieldInfo = fieldName => {
      const field = schema.properties[fieldName];
      if (!field) return {
        type: "string",
        required: false
      };
      return {
        type: field.type || "string",
        required: schema.required?.includes(fieldName) || false,
        description: field.description,
        enum: field.enum,
        default: field.default,
        minLength: field.minLength,
        maxLength: field.maxLength,
        minimum: field.minimum,
        maximum: field.maximum,
        maxItems: field.maxItems,
        items: field.items
      };
    };
    const renderFieldConstraints = fieldInfo => {
      return <>
          {fieldInfo.maxItems !== undefined && <p>
              最大支持项数:{" "}
              <span className="unified-api-inline-label">
                {fieldInfo.maxItems}
              </span>
            </p>}
          {(fieldInfo.minLength || fieldInfo.maxLength) && <p>
              {fieldInfo.minLength !== undefined && <>
                  最小长度:{" "}
                  <span className="unified-api-inline-label">
                    {fieldInfo.minLength}
                  </span>
                </>}
              {fieldInfo.minLength !== undefined && fieldInfo.maxLength && "，"}
              {fieldInfo.maxLength && <>
                  最大长度:{" "}
                  <span className="unified-api-inline-label">
                    {fieldInfo.maxLength}
                  </span>
                </>}
              。
            </p>}
          {(fieldInfo.minimum !== undefined || fieldInfo.maximum !== undefined) && <p>
              {fieldInfo.minimum !== undefined && <>
                  最小值:{" "}
                  <span className="unified-api-inline-label">
                    {fieldInfo.minimum}
                  </span>
                </>}
              {fieldInfo.minimum !== undefined && fieldInfo.maximum !== undefined && "，"}
              {fieldInfo.maximum !== undefined && <>
                  最大值:{" "}
                  <span className="unified-api-inline-label">
                    {fieldInfo.maximum}
                  </span>
                </>}
              。
            </p>}
          {(fieldInfo.enum || fieldInfo.default !== undefined) && <p>
              {fieldInfo.enum && <>
                  枚举值:{" "}
                  {fieldInfo.enum.map((value, index) => <span key={index}>
                      <span className="unified-api-inline-label">
                        {value}
                      </span>
                      {index < fieldInfo.enum.length - 1 && ", "}
                    </span>)}
                </>}
              {fieldInfo.enum && fieldInfo.default !== undefined && "。 "}
              {fieldInfo.default !== undefined && <>
                  默认值:{" "}
                  <span className="unified-api-inline-label">
                    {typeof fieldInfo.default === 'boolean' ? fieldInfo.default ? 'true' : 'false' : fieldInfo.default}
                  </span>
                </>}
              。
            </p>}
        </>;
    };
    return <>
        {data?.model && <ParamField body="model" type="string" required={true}>
            支持的模型：
            <span className="unified-api-inline-label">{data.model}</span>
          </ParamField>}
        {getFields().map(field => {
      const fieldInfo = getFieldInfo(field.name);
      return <ParamField key={field.name} body={field.name} type={fieldInfo.type} required={fieldInfo.required} default={fieldInfo.default}>
              <p>{fieldInfo.description}</p>
              {renderFieldConstraints(fieldInfo)}
              {fieldInfo.items && fieldInfo.items.properties && <Expandable title="properties" defaultOpen={false}>
                  {Object.entries(fieldInfo.items.properties).map(([propName, propInfo]) => <ParamField key={propName} body={propName} type={propInfo.type} required={fieldInfo.items.required?.includes(propName) || false}>
                      <p>{propInfo.description}</p>
                      {renderFieldConstraints(propInfo)}
                    </ParamField>)}
                </Expandable>}
            </ParamField>;
    })}
      </>;
  }, [data]);
  const LoadingSpinner = useMemo(() => <div className="unified-api-loading-container">
      <div className="unified-api-loading-spinner"></div>
      <p className="unified-api-loading-text">加载中...</p>
    </div>, []);
  return <div className="unified-api-container">
      <p>
        该接口用于统一整合各厂商的视频生成功能，抽取通用的请求与响应字段，规范参数和数据格式，简化调用流程，提高接入与使用效率。
      </p>
      {modelSelector}
      {data?.description && <>
          <p>模型信息：</p>
          <p className="unified-api-description">{data.description}</p>
        </>}
      {headerFields}
      <h2>请求体</h2>
      {!isLoading && <ParamField body="callback_url" type="string">
          Webhook 回调 URL。视频生成完成后，系统会通过 POST 请求调用该地址，回调数据格式
          
          <p className="unified-api-code">{`{
              event_type: "ASYNC_TASK_RESULT",
              payload: TaskResultResponse
             }`}
          </p>
          
          TaskResultResponse 详见 <a href="/models/reference-get-async-task-result">查询任务结果 API</a> 的返回内容。
          <br />
          <br />
          示例值：https://your-callback-url.com/callback
        </ParamField>}
      {isLoading ? LoadingSpinner : requestBodyFields}
      <h2>返回结果</h2>
      <ResponseField name="task_id" type="string" required={true}>
        异步任务的 task_id。您应该使用该 task_id 请求{" "}
        <a href="/models/reference-get-async-task-result">
          查询任务结果 API
        </a>{" "}
        以获取生成结果
      </ResponseField>
    </div>;
};

<UnifiedAPI />
