mirror of
https://github.com/zhouxiaoka/autoclip.git
synced 2026-10-02 02:34:34 +08:00
feat: add 88API sponsor provider and setup guides (#235)
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
## [未发布]
|
||||
|
||||
### 新增
|
||||
- 新增赞助合作伙伴 88API:八语 README、首页与设置页推荐入口、独立 API Key、模型发现、兼容封面与 Whisper 转写;CLI / MCP 使用 `api88`。
|
||||
- 模型设置新增赞助合作伙伴「Infistar 无限星河」:接口地址已预设,填好 Key 后自动列出账号可用的模型;设置页提供专属注册链接(可领 $5 体验额度)和接入说明。CLI / MCP 支持 `--provider infistar` 与 `INFISTAR_API_KEY`。
|
||||
- 设置 → AI 模型重新整理为「AI 服务 / 字幕转写 / 封面 / 高级」四段:选一家服务、填好 Key,分析模型自动选好;画面识别和 AI 封面首次配置默认开启,画面识别注明适合游戏画面、口播较少的内容。供应商分组中的赞助伙伴改为「推荐」。
|
||||
- 首次打开应用会弹出「连接 AI 服务」引导,未连接时导入会提示先去连接;不再需要自己摸索设置页。
|
||||
|
||||
@@ -48,6 +48,18 @@ AutoClip uses AI to analyze video transcripts, find highlights, write titles, an
|
||||
## Special thanks ❤️
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="140">
|
||||
<a href="https://88api.ai/sign-up?aff=2PIc"><img src="docs/sponsors/88api-logo.jpg" alt="88API" width="88"></a><br>
|
||||
<a href="https://88api.ai/sign-up?aff=2PIc"><strong>88API</strong></a>
|
||||
</td>
|
||||
<td>
|
||||
Thank you to <strong>88API Token Platform</strong> for sponsoring AutoClip! It brings together GPT, Claude, Gemini, Grok, DeepSeek, Kimi and GLM for transcript analysis, highlight selection and title generation.<br>
|
||||
🎨 <strong>Media capabilities</strong>: The platform offers image, video and audio models, including GPT-Image, Seedance, Veo, MiniMax Hailuo H3, Kling, Whisper and TTS. AutoClip uses compatible analysis, cover-image and transcription APIs.<br>
|
||||
🏷️ <strong>Service and billing</strong>: The partner reports overseas corporate operation, live support, invoices and a 1:1 top-up ratio; platform terms apply.<br>
|
||||
🎁 <strong>New-user offer</strong>: Receive trial credit to test models through our <a href="https://88api.ai/sign-up?aff=2PIc">referral registration link</a>, subject to promotion terms. <a href="docs/88API_SETUP.en.md">Setup guide</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="140">
|
||||
<a href="https://www.infistar.cc/register?aff=XLK3BCM6&ref_source=link"><img src="docs/sponsors/infistar-logo.svg" alt="Infistar" width="88"></a><br>
|
||||
|
||||
@@ -48,6 +48,18 @@ AutoClip utiliza IA para analizar los subtítulos de un vídeo, encontrar moment
|
||||
## Agradecimientos ❤️
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="140">
|
||||
<a href="https://88api.ai/sign-up?aff=2PIc"><img src="docs/sponsors/88api-logo.jpg" alt="88API" width="88"></a><br>
|
||||
<a href="https://88api.ai/sign-up?aff=2PIc"><strong>88API</strong></a>
|
||||
</td>
|
||||
<td>
|
||||
¡Gracias a <strong>88API</strong> por patrocinar AutoClip! Reúne GPT, Claude, Gemini, Grok, DeepSeek, Kimi y GLM para analizar subtítulos, seleccionar momentos destacados y generar títulos.<br>
|
||||
🎨 <strong>Multimedia</strong>: Ofrece modelos de imagen, vídeo y audio como GPT-Image, Seedance, Veo, MiniMax Hailuo H3, Kling, Whisper y TTS. AutoClip utiliza API compatibles de análisis, portadas y transcripción.<br>
|
||||
🏷️ <strong>Servicio y facturación</strong>: Según el patrocinador, opera mediante una empresa extranjera y ofrece atención humana, facturas y recargas con proporción 1:1; se aplican las condiciones de la plataforma.<br>
|
||||
🎁 <strong>Nuevos usuarios</strong>: Obtén crédito de prueba para evaluar modelos mediante el <a href="https://88api.ai/sign-up?aff=2PIc">enlace de referido</a>, según las condiciones de la promoción. <a href="docs/88API_SETUP.en.md">Guía de configuración (inglés)</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="140">
|
||||
<a href="https://www.infistar.cc/register?aff=XLK3BCM6&ref_source=link"><img src="docs/sponsors/infistar-logo.svg" alt="Infistar" width="88"></a><br>
|
||||
|
||||
@@ -48,6 +48,18 @@ AutoClip utilise l’IA pour analyser les sous-titres, repérer les temps forts,
|
||||
## Remerciements ❤️
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="140">
|
||||
<a href="https://88api.ai/sign-up?aff=2PIc"><img src="docs/sponsors/88api-logo.jpg" alt="88API" width="88"></a><br>
|
||||
<a href="https://88api.ai/sign-up?aff=2PIc"><strong>88API</strong></a>
|
||||
</td>
|
||||
<td>
|
||||
Merci à <strong>88API</strong> de sponsoriser AutoClip ! La plateforme réunit GPT, Claude, Gemini, Grok, DeepSeek, Kimi et GLM pour analyser les transcriptions, sélectionner les temps forts et générer des titres.<br>
|
||||
🎨 <strong>Multimédia</strong> : Modèles d’image, de vidéo et d’audio, dont GPT-Image, Seedance, Veo, MiniMax Hailuo H3, Kling, Whisper et TTS. AutoClip utilise les API compatibles d’analyse, de couverture et de transcription.<br>
|
||||
🏷️ <strong>Service et facturation</strong> : Le partenaire indique être exploité par une société à l’étranger et proposer une assistance humaine, des factures et un ratio de recharge de 1:1 ; les conditions de la plateforme s’appliquent.<br>
|
||||
🎁 <strong>Nouveaux utilisateurs</strong> : Recevez un crédit d’essai pour tester les modèles via le <a href="https://88api.ai/sign-up?aff=2PIc">lien de parrainage</a>, selon les conditions de la promotion. <a href="docs/88API_SETUP.en.md">Guide de configuration (anglais)</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="140">
|
||||
<a href="https://www.infistar.cc/register?aff=XLK3BCM6&ref_source=link"><img src="docs/sponsors/infistar-logo.svg" alt="Infistar" width="88"></a><br>
|
||||
|
||||
@@ -48,6 +48,18 @@ AutoClip は AI で動画の字幕を分析し、見どころを抽出してタ
|
||||
## スポンサーへの感謝 ❤️
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="140">
|
||||
<a href="https://88api.ai/sign-up?aff=2PIc"><img src="docs/sponsors/88api-logo.jpg" alt="88API" width="88"></a><br>
|
||||
<a href="https://88api.ai/sign-up?aff=2PIc"><strong>88API</strong></a>
|
||||
</td>
|
||||
<td>
|
||||
<strong>88API Tokenプラットフォーム</strong> のご支援に感謝します!GPT、Claude、Gemini、Grok、DeepSeek、Kimi、GLM を集約し、字幕分析・見どころ選定・タイトル生成に活用できます。<br>
|
||||
🎨 <strong>マルチメディア</strong>:GPT-Image、Seedance、Veo、MiniMax Hailuo H3、Kling、Whisper、TTS などの画像・動画・音声モデルを提供。AutoClip は対応する分析・カバー画像生成・文字起こし API を利用できます。<br>
|
||||
🏷️ <strong>サービスと決済</strong>:提携先によると海外法人が運営し、有人サポート・請求書・1:1 のチャージ比率を提供。条件はプラットフォームをご確認ください。<br>
|
||||
🎁 <strong>新規登録特典</strong>:<a href="https://88api.ai/sign-up?aff=2PIc">専用紹介リンク</a>から登録するとモデル検証用の体験クレジットを受け取れます。適用条件はキャンペーンページをご確認ください。 <a href="docs/88API_SETUP.en.md">設定ガイド(英語)</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="140">
|
||||
<a href="https://www.infistar.cc/register?aff=XLK3BCM6&ref_source=link"><img src="docs/sponsors/infistar-logo.svg" alt="Infistar" width="88"></a><br>
|
||||
|
||||
@@ -48,6 +48,18 @@ AutoClip은 AI로 영상 자막을 분석하고 하이라이트를 찾아 제목
|
||||
## 후원에 감사드립니다 ❤️
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="140">
|
||||
<a href="https://88api.ai/sign-up?aff=2PIc"><img src="docs/sponsors/88api-logo.jpg" alt="88API" width="88"></a><br>
|
||||
<a href="https://88api.ai/sign-up?aff=2PIc"><strong>88API</strong></a>
|
||||
</td>
|
||||
<td>
|
||||
<strong>88API Token 플랫폼</strong>의 AutoClip 후원에 감사드립니다! GPT, Claude, Gemini, Grok, DeepSeek, Kimi, GLM을 통합하여 자막 분석, 하이라이트 선택, 제목 생성에 활용할 수 있습니다.<br>
|
||||
🎨 <strong>멀티미디어</strong>: GPT-Image, Seedance, Veo, MiniMax Hailuo H3, Kling, Whisper, TTS 등 이미지·영상·음성 모델을 제공합니다. AutoClip에서는 호환되는 분석·표지 생성·자막 전사 API를 사용합니다.<br>
|
||||
🏷️ <strong>서비스 및 결제</strong>: 파트너 안내에 따르면 해외 법인이 운영하며 상담원 지원, 정식 청구서, 1:1 충전 비율을 제공합니다. 세부 조건은 플랫폼을 확인하세요.<br>
|
||||
🎁 <strong>신규 가입 혜택</strong>: <a href="https://88api.ai/sign-up?aff=2PIc">전용 추천 링크</a>로 가입하면 모델 테스트용 체험 크레딧을 받을 수 있습니다. 이벤트 조건이 적용됩니다. <a href="docs/88API_SETUP.en.md">설정 가이드 (영어)</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="140">
|
||||
<a href="https://www.infistar.cc/register?aff=XLK3BCM6&ref_source=link"><img src="docs/sponsors/infistar-logo.svg" alt="Infistar" width="88"></a><br>
|
||||
|
||||
@@ -48,6 +48,18 @@ O AutoClip usa IA para analisar legendas, encontrar destaques, criar títulos e
|
||||
## Agradecimentos ❤️
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="140">
|
||||
<a href="https://88api.ai/sign-up?aff=2PIc"><img src="docs/sponsors/88api-logo.jpg" alt="88API" width="88"></a><br>
|
||||
<a href="https://88api.ai/sign-up?aff=2PIc"><strong>88API</strong></a>
|
||||
</td>
|
||||
<td>
|
||||
Agradecemos à <strong>88API</strong> por patrocinar o AutoClip! Reúne GPT, Claude, Gemini, Grok, DeepSeek, Kimi e GLM para analisar legendas, selecionar destaques e gerar títulos.<br>
|
||||
🎨 <strong>Multimídia</strong>: Modelos de imagem, vídeo e áudio, incluindo GPT-Image, Seedance, Veo, MiniMax Hailuo H3, Kling, Whisper e TTS. O AutoClip utiliza APIs compatíveis de análise, capas e transcrição.<br>
|
||||
🏷️ <strong>Serviço e faturamento</strong>: Segundo o parceiro, a operação é de uma empresa no exterior, com atendimento humano, faturas e proporção de recarga de 1:1; aplicam-se as condições da plataforma.<br>
|
||||
🎁 <strong>Novos usuários</strong>: Receba créditos de teste para avaliar modelos pelo <a href="https://88api.ai/sign-up?aff=2PIc">link de indicação</a>, conforme as condições da promoção. <a href="docs/88API_SETUP.en.md">Guia de configuração (inglês)</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="140">
|
||||
<a href="https://www.infistar.cc/register?aff=XLK3BCM6&ref_source=link"><img src="docs/sponsors/infistar-logo.svg" alt="Infistar" width="88"></a><br>
|
||||
|
||||
@@ -48,6 +48,18 @@ AutoClip с помощью ИИ анализирует субтитры, нах
|
||||
## Благодарим спонсоров ❤️
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="140">
|
||||
<a href="https://88api.ai/sign-up?aff=2PIc"><img src="docs/sponsors/88api-logo.jpg" alt="88API" width="88"></a><br>
|
||||
<a href="https://88api.ai/sign-up?aff=2PIc"><strong>88API</strong></a>
|
||||
</td>
|
||||
<td>
|
||||
Благодарим <strong>88API</strong> за поддержку AutoClip! Платформа объединяет GPT, Claude, Gemini, Grok, DeepSeek, Kimi и GLM для анализа субтитров, выбора ярких моментов и создания заголовков.<br>
|
||||
🎨 <strong>Мультимедиа</strong>: Модели изображений, видео и аудио, включая GPT-Image, Seedance, Veo, MiniMax Hailuo H3, Kling, Whisper и TTS. AutoClip использует совместимые API анализа, создания обложек и транскрипции.<br>
|
||||
🏷️ <strong>Сервис и оплата</strong>: По информации партнёра, сервис управляется зарубежной компанией и предлагает поддержку операторов, счета и пополнение в соотношении 1:1; действуют условия платформы.<br>
|
||||
🎁 <strong>Новым пользователям</strong>: Получите пробный кредит для проверки моделей по <a href="https://88api.ai/sign-up?aff=2PIc">реферальной ссылке</a> на условиях акции. <a href="docs/88API_SETUP.en.md">Инструкция по настройке (англ.)</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="140">
|
||||
<a href="https://www.infistar.cc/register?aff=XLK3BCM6&ref_source=link"><img src="docs/sponsors/infistar-logo.svg" alt="Infistar" width="88"></a><br>
|
||||
|
||||
@@ -48,6 +48,18 @@
|
||||
## 特别感谢 ❤️
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<td align="center" width="140">
|
||||
<a href="https://88api.ai/sign-up?aff=2PIc"><img src="docs/sponsors/88api-logo.jpg" alt="88API" width="88"></a><br>
|
||||
<a href="https://88api.ai/sign-up?aff=2PIc"><strong>88API</strong></a>
|
||||
</td>
|
||||
<td>
|
||||
感谢 <strong>88API Token聚合平台</strong> 赞助 AutoClip!聚合 GPT、Claude、Gemini、Grok、DeepSeek、Kimi、GLM 等语言与编程模型,适合字幕分析、高光筛选与标题生成。<br>
|
||||
🎨 <strong>多媒体能力</strong>:平台提供 GPT-Image、Gemini、Grok 等图片模型,Seedance、Veo、MiniMax Hailuo H3、Kling、Grok 等视频模型,以及 Whisper、TTS 等语音能力;AutoClip 可接入兼容的分析、封面生图与字幕转写接口。<br>
|
||||
🏷️ <strong>服务与结算</strong>:据合作方介绍,由海外企业运营,提供人工客服、正规发票,充值比例 1:1;具体模型、服务与结算条件以平台页面为准。<br>
|
||||
🎁 <strong>新用户福利</strong>:通过 <a href="https://88api.ai/sign-up?aff=2PIc">专属推广链接注册</a>可获体验额度,用于测试模型能力,领取条件以活动页面为准。 <a href="docs/88API_SETUP.md">接入说明</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" width="140">
|
||||
<a href="https://www.infistar.cc/register?aff=XLK3BCM6&ref_source=link"><img src="docs/sponsors/infistar-logo.svg" alt="Infistar" width="88"></a><br>
|
||||
|
||||
@@ -113,6 +113,7 @@ class ApiKeys(BaseModel):
|
||||
glm: str = Field(default="", description="智谱 GLM API密钥")
|
||||
grok: str = Field(default="", description="xAI Grok API密钥")
|
||||
infistar: str = Field(default="", description="Infistar 无限星河 API密钥")
|
||||
api88: str = Field(default="", description="88API API密钥")
|
||||
seed: str = Field(default="", description="火山方舟 Seed / 豆包 API密钥")
|
||||
jimeng_access: str = Field(default="", description="即梦AI访问密钥")
|
||||
jimeng_secret: str = Field(default="", description="即梦AI秘密密钥")
|
||||
@@ -121,7 +122,7 @@ class ApiKeys(BaseModel):
|
||||
class ApiSettings(BaseModel):
|
||||
"""API设置"""
|
||||
api_keys: ApiKeys = Field(default_factory=ApiKeys, description="API密钥")
|
||||
api_provider: str = Field(default="dashscope", description="当前 LLM 提供商(dashscope / openai / gemini / deepseek / seed / kimi / glm / grok / infistar,或本地预设 ollama / lmstudio)")
|
||||
api_provider: str = Field(default="dashscope", description="当前 LLM 提供商(dashscope / openai / gemini / deepseek / seed / kimi / glm / grok / infistar / api88,或本地预设 ollama / lmstudio)")
|
||||
api_base_url: str = Field(default="", description="OpenAI 兼容接口地址;provider=openai 时空为官方地址,本地预设为空时用预设默认地址")
|
||||
api_model: str = Field(default="qwen-plus", description="默认模型")
|
||||
api_max_tokens: int = Field(default=4096, description="最大Token数")
|
||||
@@ -314,6 +315,7 @@ async def get_settings():
|
||||
glm="",
|
||||
grok="",
|
||||
infistar="",
|
||||
api88="",
|
||||
seed="",
|
||||
jimeng_access="", # 默认值
|
||||
jimeng_secret="" # 默认值
|
||||
@@ -749,6 +751,7 @@ def _saved_provider_api_key(settings: DesktopSettings, provider: str) -> str:
|
||||
"glm": keys.glm,
|
||||
"grok": keys.grok,
|
||||
"infistar": keys.infistar,
|
||||
"api88": keys.api88,
|
||||
"seed": keys.seed,
|
||||
}.get((provider or "").strip().lower(), "") or ""
|
||||
|
||||
|
||||
+2
-1
@@ -59,7 +59,7 @@ def _err(msg: str) -> None:
|
||||
|
||||
def _add_llm_args(p: argparse.ArgumentParser) -> None:
|
||||
g = p.add_argument_group("模型(不填则用桌面应用设置页里的配置)")
|
||||
g.add_argument("--provider", choices=PROVIDER_CHOICES, help="dashscope / openai / gemini / deepseek / seed / kimi / glm / grok / infistar,或本地预设 ollama / lmstudio")
|
||||
g.add_argument("--provider", choices=PROVIDER_CHOICES, help="dashscope / openai / gemini / deepseek / seed / kimi / glm / grok / infistar / api88,或本地预设 ollama / lmstudio")
|
||||
g.add_argument("--model", help="模型名,如 qwen-plus、gpt-4o-mini、qwen2.5:7b")
|
||||
g.add_argument("--base-url", help="OpenAI 兼容接口地址(provider=openai 时用;ollama/lmstudio 有默认值)")
|
||||
g.add_argument("--api-key", help="API Key(本地模型可不填)。也可用环境变量 AUTOCLIP_API_KEY")
|
||||
@@ -206,6 +206,7 @@ def cmd_providers(args: argparse.Namespace) -> int:
|
||||
("kimi", "Kimi(月之暗面)", "需要 key;国内直连"),
|
||||
("glm", "智谱 GLM", "需要 key;国内直连"),
|
||||
("grok", "Grok(xAI)", "需要 key"),
|
||||
("api88", "88API Token聚合平台(赞助)", "需要 key,新用户注册赠送体验额度"),
|
||||
("infistar", "Infistar 无限星河(赞助)", "需要 key,专属链接注册可领体验额度"),
|
||||
] + [
|
||||
(p.key, p.display_name, f"无需 key;默认 {p.base_url}" + (f",默认模型 {p.default_model}" if p.default_model else ""))
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
Sources and intentionally unsupported integrations: docs/AI_MODEL_CONFIGURATION.md.
|
||||
The public/account catalog still determines availability, not this routing table.
|
||||
"""
|
||||
# 88API: https://88api.ai/zh/docs/api/audio/openai-audio/
|
||||
MODELS = {
|
||||
'api88': ['whisper-1'],
|
||||
'openai': ['whisper-1', 'gpt-4o-transcribe-diarize', 'gpt-4o-transcribe', 'gpt-4o-mini-transcribe'],
|
||||
'dashscope': ['qwen-audio-3.1-asr-flash', 'qwen-audio-3.0-asr-flash', 'fun-asr-flash-2026-06-15',
|
||||
'qwen-audio-3.1-asr-flash-filetrans', 'qwen-audio-3.0-asr-flash-filetrans',
|
||||
@@ -15,6 +17,8 @@ DASHSCOPE_SYNC = frozenset(MODELS['dashscope'][:3])
|
||||
|
||||
|
||||
def adapter(provider, model):
|
||||
if provider == 'api88':
|
||||
return 'openai' if model == 'whisper-1' else None
|
||||
if provider == 'dashscope' and model in DASHSCOPE_SYNC:
|
||||
return 'dashscope'
|
||||
if provider in {'openai', 'infistar', 'compatible'}:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"""
|
||||
云端 OpenAI 兼容预设:DeepSeek / Seed / Kimi / GLM / Grok / Infistar(赞助)。
|
||||
云端 OpenAI 兼容预设:DeepSeek / Seed / Kimi / GLM / Grok / Infistar / 88API(赞助)。
|
||||
|
||||
底层仍是 openai + 官方 base_url,和 Ollama 预设同一套路,但必须带各家自己的 key,
|
||||
不能复用用户的 OpenAI key。设置页、CLI、MCP 都用 `--provider deepseek` 这种名字。
|
||||
@@ -78,9 +78,17 @@ CLOUD_PRESETS: Dict[str, CloudPreset] = {
|
||||
docs_url="https://www.infistar.cc/register?aff=XLK3BCM6&ref_source=link",
|
||||
hint="赞助合作伙伴。一个 Key 调 Claude / GPT / Gemini / DeepSeek 等模型,专属链接注册可领体验额度。",
|
||||
),
|
||||
"api88": CloudPreset(
|
||||
key="api88", display_name="88API Token聚合平台", base_url="https://88api.ai/v1",
|
||||
default_model="", api_key_setting="api88_api_key",
|
||||
docs_url="https://88api.ai/sign-up?aff=2PIc",
|
||||
hint="赞助合作伙伴。新用户注册赠送体验额度,站内提供人工客服。",
|
||||
),
|
||||
}
|
||||
|
||||
_ALIASES = {
|
||||
"88api": "api88",
|
||||
"88api.ai": "api88",
|
||||
"deepseek-ai": "deepseek",
|
||||
"moonshot": "kimi",
|
||||
"moonshotai": "kimi",
|
||||
@@ -110,7 +118,7 @@ def normalize_cloud_preset_key(name: Optional[str]) -> Optional[str]:
|
||||
|
||||
def resolve_cloud_preset(provider: Optional[str], base_url: Optional[str] = None) -> Optional[Tuple[str, str, CloudPreset]]:
|
||||
"""
|
||||
若 provider 是 deepseek / seed / kimi / glm / grok / infistar(或别名),返回 (openai, base_url, preset)。
|
||||
若 provider 是 deepseek / seed / kimi / glm / grok / infistar / api88(或别名),返回 (openai, base_url, preset)。
|
||||
不是云端预设则返回 None。
|
||||
"""
|
||||
key = normalize_cloud_preset_key(provider)
|
||||
|
||||
@@ -4,6 +4,7 @@ Checked 2026-09-29. Keep exact provider IDs: gateway aliases are not official ID
|
||||
Sources are retained so catalog additions can be reviewed against provider docs.
|
||||
"""
|
||||
SOURCES = {
|
||||
'api88': 'https://88api.ai/zh/docs/api/image/openai-image/',
|
||||
'seed': 'https://docs.volcengine.com/docs/ark/model-release-announcement?lang=zh',
|
||||
'dashscope': 'https://help.aliyun.com/en/model-studio/image-model/',
|
||||
'openai': 'https://developers.openai.com/api/docs/guides/image-generation',
|
||||
@@ -12,6 +13,7 @@ SOURCES = {
|
||||
'glm': 'https://docs.bigmodel.cn/cn/guide/models/image-generation/glm-image',
|
||||
}
|
||||
MODELS = {
|
||||
'api88': ['gpt-image-1', 'dall-e-3', 'dall-e-2'],
|
||||
'seed': ['doubao-seedream-5-0-flash-260915', 'doubao-seedream-5-0-pro-260628', 'doubao-seedream-4-0-20260415'],
|
||||
'openai': ['gpt-image-2.5-flare', 'gpt-image-2.5-sunburst', 'gpt-image-2', 'gpt-image-1.5', 'gpt-image-1', 'gpt-image-1-mini', 'chatgpt-image-latest'],
|
||||
'gemini': ['gemini-3.1-flash-lite-image', 'gemini-3.1-flash-image', 'gemini-3-pro-image', 'gemini-2.5-flash-image'],
|
||||
|
||||
@@ -105,6 +105,7 @@ class LLMManager:
|
||||
"glm_api_key": "",
|
||||
"grok_api_key": "",
|
||||
"infistar_api_key": "",
|
||||
"api88_api_key": "",
|
||||
"seed_api_key": "",
|
||||
"model_name": "qwen-plus",
|
||||
"chunk_size": 5000,
|
||||
@@ -132,6 +133,7 @@ class LLMManager:
|
||||
"glm_api_key": api_keys.get("glm", ""),
|
||||
"grok_api_key": api_keys.get("grok", ""),
|
||||
"infistar_api_key": api_keys.get("infistar", ""),
|
||||
"api88_api_key": api_keys.get("api88", ""),
|
||||
"seed_api_key": api_keys.get("seed", ""),
|
||||
"model_name": api.get("api_model", "qwen-plus")
|
||||
})
|
||||
@@ -225,6 +227,7 @@ class LLMManager:
|
||||
"glm_api_key": ("API_GLM_API_KEY", "ZHIPU_API_KEY", "GLM_API_KEY"),
|
||||
"grok_api_key": ("API_GROK_API_KEY", "XAI_API_KEY", "GROK_API_KEY"),
|
||||
"infistar_api_key": ("API_INFISTAR_API_KEY", "INFISTAR_API_KEY"),
|
||||
"api88_api_key": ("API_API88_API_KEY", "API88_API_KEY"),
|
||||
"seed_api_key": ("API_SEED_API_KEY", "ARK_API_KEY", "VOLCENGINE_API_KEY", "DOUBAO_API_KEY"),
|
||||
}
|
||||
|
||||
|
||||
@@ -79,6 +79,7 @@ CURATED_MODELS: Dict[str, List[str]] = {
|
||||
],
|
||||
# 多模型网关,型号随账号而定:不内置名单,填好 key 后实时拉取
|
||||
"infistar": [],
|
||||
"api88": [],
|
||||
}
|
||||
|
||||
DEFAULT_MODELS: Dict[str, str] = {
|
||||
@@ -91,6 +92,7 @@ DEFAULT_MODELS: Dict[str, str] = {
|
||||
"glm": "glm-5.3",
|
||||
"grok": "grok-4.6",
|
||||
"infistar": "",
|
||||
"api88": "",
|
||||
}
|
||||
|
||||
PROVIDER_LABELS: Dict[str, str] = {
|
||||
@@ -103,6 +105,7 @@ PROVIDER_LABELS: Dict[str, str] = {
|
||||
"glm": "GLM",
|
||||
"grok": "Grok",
|
||||
"infistar": "Infistar",
|
||||
"api88": "88API",
|
||||
}
|
||||
|
||||
CLOUD_PROVIDERS = frozenset(CURATED_MODELS)
|
||||
@@ -138,6 +141,7 @@ IMAGE_MODELS: Dict[str, List[str]] = {
|
||||
"dashscope": ["wanx2.1-t2i-turbo", "wanx2.1-t2i-plus"],
|
||||
"seed": ["doubao-seedream-5-0-260128"],
|
||||
"openai": ["gpt-image-1"],
|
||||
"api88": ["gpt-image-1", "dall-e-3", "dall-e-2"],
|
||||
# Infistar 是多模型网关,主流生图模型都能调,具体以账号可用型号为准
|
||||
"infistar": ["gpt-image-1", "dall-e-3", "doubao-seedream-5-0-260128", "flux-1.1-pro", "imagen-4", "wanx2.1-t2i-turbo"],
|
||||
}
|
||||
|
||||
@@ -139,7 +139,7 @@ def _catalog_record(provider, model, data):
|
||||
|
||||
def _catalog_capability(provider, model, data):
|
||||
known = _VERIFIED.get((provider, model)) or _catalog_record(provider, model, data).get('capability')
|
||||
if not known and provider == 'infistar':
|
||||
if not known and provider in {'infistar', 'api88'}:
|
||||
# Public gateway IDs can match original vendors exactly. Require agreement;
|
||||
# do not infer a capability from a name prefix or missing image tags.
|
||||
matches = {models[model]['capability'] for models in data.get('metadata', {}).get('providers', {}).values()
|
||||
|
||||
@@ -121,7 +121,7 @@ def _make_override(provider: Optional[str], model: Optional[str], base_url: Opti
|
||||
description=(
|
||||
"把一条本地视频切成高光片段(同步,耗时数分钟到数十分钟,期间会发进度)。"
|
||||
"返回切片列表(标题 / 起止时间 / 评分 / mp4 路径)与合集。"
|
||||
"provider 可选 dashscope / openai / gemini / deepseek / seed / kimi / glm / grok / infistar / ollama / lmstudio;不填用桌面应用里已配置的模型。"
|
||||
"provider 可选 dashscope / openai / gemini / deepseek / seed / kimi / glm / grok / infistar / api88 / ollama / lmstudio;不填用桌面应用里已配置的模型。"
|
||||
),
|
||||
)
|
||||
async def clip_video(
|
||||
@@ -220,7 +220,7 @@ def list_providers() -> Dict[str, Any]:
|
||||
|
||||
return {
|
||||
"current": get_llm_manager().get_current_provider_info(),
|
||||
"cloud": ["dashscope", "openai", "gemini", "deepseek", "seed", "kimi", "glm", "grok", "infistar"],
|
||||
"cloud": ["dashscope", "openai", "gemini", "deepseek", "seed", "kimi", "glm", "grok", "infistar", "api88"],
|
||||
"local_presets": presets_as_dicts(),
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,9 @@ def _text_model_key(provider: str, base_url: str) -> str:
|
||||
return str(settings.get("dashscope_api_key") or "")
|
||||
if kind == "seedream":
|
||||
return str(settings.get("seed_api_key") or "")
|
||||
from urllib.parse import urlparse
|
||||
if urlparse(base_url or "").hostname == "88api.ai":
|
||||
return str(settings.get("api88_api_key") or "")
|
||||
if "infistar" in host:
|
||||
return str(settings.get("infistar_api_key") or "")
|
||||
if not host or "api.openai.com" in host:
|
||||
@@ -136,8 +139,8 @@ def text_model_image_service() -> dict[str, str] | None:
|
||||
return None
|
||||
if preset == "seed":
|
||||
return {"provider": "seedream", "text_provider": "seed", "api_key": str(s.get("seed_api_key") or ""), "base_url": ""}
|
||||
if preset == "infistar":
|
||||
return {"provider": "openai", "text_provider": "infistar", "api_key": str(s.get("infistar_api_key") or ""),
|
||||
if preset in {"infistar", "api88"}:
|
||||
return {"provider": "openai", "text_provider": preset, "api_key": str(s.get(f"{preset}_api_key") or ""),
|
||||
"base_url": str(s.get("openai_base_url") or "")}
|
||||
if preset:
|
||||
return None
|
||||
|
||||
@@ -25,7 +25,7 @@ from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
PROVIDER_CHOICES = ("dashscope", "openai", "gemini", "deepseek", "seed", "kimi", "glm", "grok", "infistar", "ollama", "lmstudio")
|
||||
PROVIDER_CHOICES = ("dashscope", "openai", "gemini", "deepseek", "seed", "kimi", "glm", "grok", "infistar", "api88", "ollama", "lmstudio")
|
||||
|
||||
|
||||
# ---------------------------------------------------------------- environment ---
|
||||
@@ -145,7 +145,7 @@ def configure_llm(override: LLMOverride) -> Dict[str, Any]:
|
||||
"dashscope": "dashscope_api_key", "openai": "openai_api_key",
|
||||
"gemini": "gemini_api_key", "siliconflow": "siliconflow_api_key",
|
||||
"deepseek": "deepseek_api_key",
|
||||
"kimi": "kimi_api_key", "glm": "glm_api_key", "grok": "grok_api_key", "infistar": "infistar_api_key",
|
||||
"kimi": "kimi_api_key", "glm": "glm_api_key", "grok": "grok_api_key", "infistar": "infistar_api_key", "api88": "api88_api_key",
|
||||
"seed": "seed_api_key",
|
||||
}.get(provider)
|
||||
if key_field:
|
||||
|
||||
@@ -346,3 +346,32 @@ def test_cloud_selection_routes_auto_without_loading_whisper(monkeypatch, tmp_pa
|
||||
assert generate_subtitle_for_video(tmp_path / 'video.mp4') == tmp_path / 'result.srt'
|
||||
assert calls[0][2] is value
|
||||
assert configured_whisper_model('small') == 'small'
|
||||
|
||||
|
||||
def test_api88_gateway_catalog_routes_account_models_and_isolates_keys(monkeypatch):
|
||||
async def no_metadata():
|
||||
pass
|
||||
|
||||
async def account_models(url, **kwargs):
|
||||
assert url == 'https://88api.ai/v1/models'
|
||||
assert kwargs['headers']['Authorization'] == 'Bearer sk-88-only'
|
||||
return {'data': [{'id': 'gpt-image-1'}, {'id': 'whisper-1'},
|
||||
{'id': 'gpt-5-mini'}, {'id': 'tts-1'}, {'id': 'sora-2'}]}
|
||||
|
||||
monkeypatch.setattr(registry, '_ensure_metadata', no_metadata)
|
||||
monkeypatch.setattr(registry.model_catalog, '_http_get_json', account_models)
|
||||
c = ai.Connection(id='88', name='88API', provider='api88', api_key='sk-88-only')
|
||||
result = asyncio.run(registry.discover(c, refresh=True))
|
||||
by_id = {m['id']: m for m in result['models']}
|
||||
assert by_id['gpt-image-1']['image'] and not by_id['gpt-image-1']['analysis']
|
||||
assert by_id['whisper-1']['asr_supported'] and not by_id['whisper-1']['analysis']
|
||||
assert by_id['gpt-5-mini']['analysis']
|
||||
assert not by_id['tts-1']['analysis'] and not by_id['sora-2']['analysis']
|
||||
assert ai.image_endpoint(c)['base_url'] == 'https://88api.ai/v1'
|
||||
assert ai.image_endpoint(c)['api_key'] == 'sk-88-only'
|
||||
|
||||
config = ai.ModelSettings(connections=[c], analysis=ai.Assignment(connection_id='88', model='gpt-5-mini'),
|
||||
transcription=ai.Transcription(provider='cloud', connection_id='88', model='whisper-1'))
|
||||
response = ai.save(config)
|
||||
assert 'sk-88-only' not in json.dumps(response)
|
||||
assert ai.load().connections[0].api_key == 'sk-88-only'
|
||||
|
||||
@@ -31,7 +31,7 @@ def fake_openai(monkeypatch):
|
||||
monkeypatch.setitem(sys.modules, "openai", module)
|
||||
_FakeOpenAIClient.created.clear()
|
||||
monkeypatch.delenv("OPENAI_BASE_URL", raising=False)
|
||||
for name in ("LLM_PROVIDER", "API_MODEL_NAME", "LLM_MODEL", "API_OPENAI_API_KEY", "OPENAI_API_KEY", "API_DEEPSEEK_API_KEY", "DEEPSEEK_API_KEY", "API_INFISTAR_API_KEY", "INFISTAR_API_KEY"):
|
||||
for name in ("LLM_PROVIDER", "API_MODEL_NAME", "LLM_MODEL", "API_OPENAI_API_KEY", "OPENAI_API_KEY", "API_DEEPSEEK_API_KEY", "DEEPSEEK_API_KEY", "API_INFISTAR_API_KEY", "INFISTAR_API_KEY", "API_API88_API_KEY", "API88_API_KEY"):
|
||||
monkeypatch.delenv(name, raising=False)
|
||||
from backend.core import llm_manager as manager_module
|
||||
monkeypatch.setattr(manager_module.config_sync_service, "is_sync_needed", lambda: False)
|
||||
@@ -89,7 +89,7 @@ def test_manager_uses_seed_ark_key_not_openai(fake_openai, tmp_path):
|
||||
|
||||
def test_curated_lists_drop_retired_aliases():
|
||||
assert "gpt-4o" not in CLOUD_PRESETS
|
||||
assert set(CLOUD_PRESETS) == {"deepseek", "seed", "kimi", "glm", "grok", "infistar"}
|
||||
assert set(CLOUD_PRESETS) == {"deepseek", "seed", "kimi", "glm", "grok", "infistar", "api88"}
|
||||
|
||||
|
||||
def test_infistar_sponsor_preset_uses_gateway_and_own_key(fake_openai, tmp_path):
|
||||
@@ -120,3 +120,34 @@ def test_infistar_key_from_env(fake_openai, tmp_path, monkeypatch):
|
||||
settings.write_text('{"api": {"api_keys": {}, "api_provider": "infistar", "api_model": "gpt-5-mini"}}', encoding="utf-8")
|
||||
LLMManager(settings_file=settings).get_current_provider_info()
|
||||
assert fake_openai.created[-1]["api_key"] == "sk-infistar-from-env"
|
||||
|
||||
|
||||
def test_api88_sponsor_preset_uses_gateway_and_own_key(fake_openai, tmp_path):
|
||||
provider, url, preset = resolve_cloud_preset("api88")
|
||||
assert provider == "openai"
|
||||
assert url == "https://88api.ai/v1"
|
||||
# 多模型网关不预设型号;专属注册链接带推广参数
|
||||
assert preset.default_model == ""
|
||||
assert "aff=" in preset.docs_url
|
||||
|
||||
settings = tmp_path / "settings.json"
|
||||
settings.write_text(
|
||||
'{"api": {"api_keys": {"openai": "sk-openai-should-not-be-used", "api88": "sk-api88-key-123"}, '
|
||||
'"api_provider": "api88", "api_model": "deepseek-v4-pro"}}',
|
||||
encoding="utf-8",
|
||||
)
|
||||
info = LLMManager(settings_file=settings).get_current_provider_info()
|
||||
assert info["provider"] == "api88"
|
||||
assert info["base_url"] == "https://88api.ai/v1"
|
||||
assert info["model"] == "deepseek-v4-pro"
|
||||
assert fake_openai.created[-1]["api_key"] == "sk-api88-key-123"
|
||||
assert fake_openai.created[-1]["base_url"] == "https://88api.ai/v1"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("env_name", ["API88_API_KEY", "API_API88_API_KEY"])
|
||||
def test_api88_key_from_env(fake_openai, tmp_path, monkeypatch, env_name):
|
||||
monkeypatch.setenv(env_name, "sk-api88-from-env")
|
||||
settings = tmp_path / "settings.json"
|
||||
settings.write_text('{"api": {"api_keys": {}, "api_provider": "api88", "api_model": "gpt-5-mini"}}', encoding="utf-8")
|
||||
LLMManager(settings_file=settings).get_current_provider_info()
|
||||
assert fake_openai.created[-1]["api_key"] == "sk-api88-from-env"
|
||||
|
||||
@@ -311,6 +311,12 @@ def test_cover_follows_text_model_service(data_dir, monkeypatch):
|
||||
_text_keys(monkeypatch, llm_provider="openai", cloud_preset="infistar", infistar_api_key="sk-inf", openai_base_url="https://infistar.cc/v1")
|
||||
cfg = cover.load_config()
|
||||
assert (cfg.provider, cfg.base_url, cfg.api_key) == ("openai", "https://infistar.cc/v1", "sk-inf")
|
||||
# 88API uses its own credentials and gateway when following the text service.
|
||||
_text_keys(monkeypatch, llm_provider="openai", cloud_preset="api88", api88_api_key="sk-88", openai_base_url="https://88api.ai/v1")
|
||||
cfg = cover.load_config()
|
||||
assert (cfg.provider, cfg.base_url, cfg.api_key) == ("openai", "https://88api.ai/v1", "sk-88")
|
||||
assert cover._text_model_key("openai", "https://88api.ai/v1") == "sk-88"
|
||||
assert cover._text_model_key("openai", "https://88api.ai.evil.example/v1") == ""
|
||||
# 换成没有生图的 DeepSeek:自动关掉,封面走截帧
|
||||
_text_keys(monkeypatch, llm_provider="openai", cloud_preset="deepseek", deepseek_api_key="sk-ds")
|
||||
cfg = cover.load_config()
|
||||
|
||||
@@ -246,3 +246,24 @@ def test_infistar_has_no_builtin_list_and_fetches_gateway_models(monkeypatch):
|
||||
assert live.source == "live"
|
||||
# 网关里各家型号都保留,只去掉嵌入 / 视频
|
||||
assert live.models == ["claude-sonnet-5-5", "deepseek-v4-pro", "gemini-3.8-flash"]
|
||||
|
||||
|
||||
def test_api88_has_no_builtin_list_and_fetches_gateway_models(monkeypatch):
|
||||
# 没 key:不拿通义的型号冒充
|
||||
empty = asyncio.run(model_catalog.list_available_models("api88"))
|
||||
assert empty.models == [] and empty.default_model == "" and empty.source == "catalog"
|
||||
|
||||
_FakeAsyncClient.calls = []
|
||||
_FakeAsyncClient.routes = {
|
||||
"https://88api.ai/v1/models": (
|
||||
{"data": [{"id": "claude-sonnet-5-5"}, {"id": "deepseek-v4-pro"}, {"id": "gemini-3.8-flash"},
|
||||
{"id": "text-embedding-3-large"}, {"id": "sora-2"}]},
|
||||
200,
|
||||
)
|
||||
}
|
||||
import httpx
|
||||
monkeypatch.setattr(httpx, "AsyncClient", _FakeAsyncClient)
|
||||
live = asyncio.run(model_catalog.list_available_models("api88", api_key="sk-api88-key-123", refresh=True))
|
||||
assert live.source == "live"
|
||||
# 网关里各家型号都保留,只去掉嵌入 / 视频
|
||||
assert live.models == ["claude-sonnet-5-5", "deepseek-v4-pro", "gemini-3.8-flash"]
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
# Use 88API with AutoClip
|
||||
|
||||
[简体中文](88API_SETUP.md) · [Back to README](../README-EN.md)
|
||||
|
||||
88API Token Platform sponsors AutoClip and provides OpenAI-compatible APIs for analysis, cover images and transcription.
|
||||
|
||||
## Register and create an API key
|
||||
|
||||
Register through the [AutoClip referral link](https://88api.ai/sign-up?aff=2PIc), then create an API key in the console. New users can receive trial credit; the amount, eligibility and terms are set by the platform. Live support is available on the platform. The referral link includes commission that supports project maintenance.
|
||||
|
||||
## Configure AutoClip
|
||||
|
||||
### New versions: select 88API directly
|
||||
|
||||
1. Open **Settings → AI Models → AI service** and select **88API** in the recommended group. You can also configure it during first-run setup.
|
||||
2. Enter your 88API key. The preset base URL is `https://88api.ai/v1`.
|
||||
3. Wait for your account's model list and choose an analysis model. For visual analysis, select a multimodal model that accepts images.
|
||||
4. Test the connection and save. Verify the result with a short video before larger jobs.
|
||||
|
||||
No default text model or account allowlist is assumed. Use exact model IDs available to your account. Image and audio previews shown without a key are reference catalogs, not proof of account access.
|
||||
|
||||
- **Covers** can reuse the AI service key with supported OpenAI Images models such as account-listed `gpt-image-1` or `dall-e-3`. Other image models depend on their actual API and returned capability metadata.
|
||||
- **Transcription** supports account-listed `whisper-1` through the OpenAI audio API with timestamps.
|
||||
- The platform also offers video generation and TTS; these are not corresponding AutoClip features.
|
||||
|
||||
### Older versions: use the OpenAI-compatible provider
|
||||
|
||||
If 88API is not listed, select the custom OpenAI-compatible provider:
|
||||
|
||||
| Setting | Value |
|
||||
| --- | --- |
|
||||
| Provider | Custom OpenAI-compatible API |
|
||||
| Base URL | `https://88api.ai/v1` |
|
||||
| API key | The API key created in the 88API console |
|
||||
| Model | An exact account model ID supporting Chat Completions |
|
||||
|
||||
Include `/v1` only once and do not append `/chat/completions`. Select the model, test the connection and save.
|
||||
|
||||
## CLI / MCP
|
||||
|
||||
```bash
|
||||
export API88_API_KEY="your API key"
|
||||
autoclip run video.mp4 --provider api88 --model "exact account model ID"
|
||||
```
|
||||
|
||||
`API_API88_API_KEY` and `--api-key` are also supported. Use `provider="api88"` with MCP.
|
||||
|
||||
## Troubleshooting and references
|
||||
|
||||
- 401: Check the key, token status, balance and surrounding whitespace.
|
||||
- 404: Check the base URL and exact model ID, and ensure the model supports the requested API.
|
||||
- Empty or failed model list: Check account permissions, refresh or enter the exact ID shown in the console.
|
||||
- A connection test does not validate every API. Verify actual analysis, cover generation or transcription separately.
|
||||
|
||||
[Official setup](https://88api.ai/zh/docs/apps/) · [Image API](https://88api.ai/zh/docs/api/image/openai-image/) · [Audio API and timestamps](https://88api.ai/zh/docs/api/audio/openai-audio/)
|
||||
|
||||
Required content is sent to the selected service and billed to your platform account. Keep API keys out of screenshots and public reports.
|
||||
|
||||
[Back to README](../README-EN.md)
|
||||
@@ -0,0 +1,59 @@
|
||||
# 在 AutoClip 中使用 88API
|
||||
|
||||
[English](88API_SETUP.en.md) · [返回 README](../README.md)
|
||||
|
||||
88API Token聚合平台是 AutoClip 的赞助合作伙伴,可通过 OpenAI 兼容接口用于字幕分析、封面生图和字幕转写。
|
||||
|
||||
## 注册与获取 API Key
|
||||
|
||||
通过 [AutoClip 专属推广链接](https://88api.ai/sign-up?aff=2PIc)注册,在控制台创建 API Key。新用户注册赠送体验额度,具体金额、领取条件与服务条款以平台页面为准;站内提供人工客服。推广链接含分成,用于支持项目维护。
|
||||
|
||||
## 配置 AutoClip
|
||||
|
||||
### 新版本:直接选择 88API
|
||||
|
||||
1. 打开「设置 → AI 模型 → AI 服务」,在「推荐」分组选择 **88API**;首次使用也可在首页配置。
|
||||
2. 填入 88API Key。接口地址已预设为 `https://88api.ai/v1`,无需手动填写。
|
||||
3. 等待账号模型列表加载,选择用于字幕分析的模型;需要画面识别时,选择支持图片输入的多模态模型。
|
||||
4. 点击「测试连接」,然后保存。先用一小段素材验证实际分析效果。
|
||||
|
||||
不预设默认文本模型或账号可用名单,准确模型 ID 与访问权限以账号返回的列表为准。无 Key 时显示的图片、语音目录只是参考,不代表账号权限。
|
||||
|
||||
- **封面**:可复用 AI 服务的 Key,选择支持 OpenAI Images 接口的型号,例如账号列表中的 `gpt-image-1`、`dall-e-3`。其他图片模型是否可用取决于实际接口及返回的能力信息。
|
||||
- **字幕转写**:选择 88API 和账号可用的 `whisper-1`,通过 OpenAI 音频接口获取带时间戳字幕。
|
||||
- 平台也提供视频生成和 TTS;这些平台能力不等于 AutoClip 已提供对应功能。
|
||||
|
||||
### 旧版本:通过 OpenAI 兼容接口接入
|
||||
|
||||
如果提供商列表还没有 88API,选择「自定义 OpenAI 兼容接口」,填写以下内容:
|
||||
|
||||
| 配置项 | 内容 |
|
||||
| --- | --- |
|
||||
| 提供商 | 自定义 OpenAI 兼容接口 |
|
||||
| 接口地址(Base URL) | `https://88api.ai/v1` |
|
||||
| API Key | 在 88API 控制台创建的 API Key |
|
||||
| 模型 | 当前账号可用、支持 Chat Completions 的准确模型 ID |
|
||||
|
||||
地址中的 `/v1` 只填写一次,不要追加 `/chat/completions`。选择模型后点击「测试连接」,然后保存。
|
||||
|
||||
## CLI / MCP
|
||||
|
||||
```bash
|
||||
export API88_API_KEY="你的 API Key"
|
||||
autoclip run video.mp4 --provider api88 --model "账号可用的模型 ID"
|
||||
```
|
||||
|
||||
也支持 `API_API88_API_KEY` 或 `--api-key`。MCP 使用 `provider="api88"`。
|
||||
|
||||
## 排查与参考
|
||||
|
||||
- 401:检查 Key、令牌状态、额度及前后空格。
|
||||
- 404:检查接口地址和模型 ID,确认模型支持所选接口。
|
||||
- 模型列表为空或获取失败:检查账号权限,刷新列表或输入控制台中的准确模型 ID。
|
||||
- 测试连接成功后仍应验证一次实际分析、封面或转写;不同接口可能有不同权限。
|
||||
|
||||
[官方接入指南](https://88api.ai/zh/docs/apps/) · [图像接口](https://88api.ai/zh/docs/api/image/openai-image/) · [音频与时间戳格式](https://88api.ai/zh/docs/api/audio/openai-audio/)
|
||||
|
||||
分析内容会发送至所选服务商,调用费用由该平台账户结算。不要将 API Key 放进截图或公开反馈。
|
||||
|
||||
[返回 README](../README.md)
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 63 KiB |
@@ -4,3 +4,5 @@
|
||||
- `infistar-banner.png`:合作方横幅,由项目维护者在本次合作中提供。保留原图,当前 README 使用上方 Logo。
|
||||
|
||||
八语 README 统一使用 AutoClip 专属推广码 `XLK3BCM6`。品牌名称与 Logo 归相应权利人所有。
|
||||
|
||||
- `88api-logo.jpg`:项目维护者于 2026-09-30 提供的 88API 官方 Logo;原图保存,README 中以 88 像素宽展示。
|
||||
|
||||
@@ -24,6 +24,7 @@ API_DASHSCOPE_API_KEY=your_dashscope_api_key_here
|
||||
# API_GLM_API_KEY=
|
||||
# API_GROK_API_KEY=
|
||||
# API_INFISTAR_API_KEY=
|
||||
# API_API88_API_KEY= # or API88_API_KEY
|
||||
# OpenAI 兼容接口地址(LLM_PROVIDER=openai 时生效,留空走 OpenAI 官方)。
|
||||
# 智谱: https://open.bigmodel.cn/api/paas/v4 DeepSeek: https://api.deepseek.com/v1
|
||||
# Seed / 火山方舟: https://ark.cn-beijing.volces.com/api/v3
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
* 所有 capture 都通过 trackEvent,未初始化 / 已关闭时自动 no-op。
|
||||
*/
|
||||
import { captureBusinessEvent } from './posthog'
|
||||
import type { SponsorId } from '../features/settings/providers'
|
||||
|
||||
export const AnalyticsEvent = {
|
||||
/** 导入素材(上传/选择视频开始一个项目) */
|
||||
@@ -60,7 +61,7 @@ export function trackProcessingFailed(props: {
|
||||
}
|
||||
|
||||
export function trackSponsorLinkOpened(props: {
|
||||
sponsor: 'infistar'
|
||||
sponsor: SponsorId
|
||||
target: 'register' | 'guide'
|
||||
placement: 'settings_model' | 'home_setup'
|
||||
}): void {
|
||||
|
||||
@@ -11,7 +11,7 @@ const enums: Record<string, readonly string[]> = {
|
||||
trigger: ['initial', 'import_blocked', 'auto', 'manual', 'provider_change', 'load', 'edit', 'portrait_preset'],
|
||||
section: ['ai', 'publish', 'app', 'feedback'], role: ['analysis', 'vision', 'cover', 'transcription'],
|
||||
reason: ['setup_required', 'provider', 'model', 'key'], mode: ['initial', 'update'],
|
||||
provider: ['dashscope', 'openai', 'gemini', 'deepseek', 'seed', 'kimi', 'glm', 'grok', 'infistar', 'compatible', 'ollama', 'lmstudio', 'other'],
|
||||
provider: ['dashscope', 'openai', 'gemini', 'deepseek', 'seed', 'kimi', 'glm', 'grok', 'infistar', 'api88', 'compatible', 'ollama', 'lmstudio', 'other'],
|
||||
source: ['live', 'cache', 'catalog'], outcome: ['completed', 'failed', 'blocked', 'unknown'],
|
||||
resolution: ['created', 'reused'], material_origin: ['sample', 'user', 'unknown'],
|
||||
analysis_mode: ['auto', 'subtitle', 'visual'], transcription_mode: ['whisper_local', 'cloud', 'unknown'],
|
||||
|
||||
@@ -53,7 +53,7 @@ function TranscriptionSection({ m }: { m: ModelSettingsStore }) {
|
||||
const main = mainConnection(settings)
|
||||
const options = [
|
||||
{ label: t('本机运行'), options: [{ value: 'whisper_local', label: t('Whisper · 本地') }] },
|
||||
...providerPickerOptions().map(group => ({ ...group, options: group.options.filter(p => ['openai', 'dashscope', 'infistar', 'glm', 'compatible'].includes(p.value)) })).filter(group => group.options.length),
|
||||
...providerPickerOptions().map(group => ({ ...group, options: group.options.filter(p => ['openai', 'dashscope', 'infistar', 'api88', 'glm', 'compatible'].includes(p.value)) })).filter(group => group.options.length),
|
||||
]
|
||||
return <div id="ai-speech" className="ac-model-section">
|
||||
<h3 className="ac-model-section-title">{t('字幕转写')}</h3>
|
||||
@@ -61,7 +61,7 @@ function TranscriptionSection({ m }: { m: ModelSettingsStore }) {
|
||||
<Row wide label={t('转写方式')} hint={t('本地转写免费,首次需下载模型;云端转写无需下载,按用量计费。')}>
|
||||
<Select aria-label={t('转写方式')} style={{ width: '100%' }} value={cloud ? connection?.provider : 'whisper_local'} options={options}
|
||||
onChange={value => value === 'whisper_local' ? m.setTranscriptionLocal(settings.transcription?.model && settings.transcription.provider === 'whisper_local' ? settings.transcription.model : 'base') : m.chooseProvider('transcription', value as ProviderKey)}
|
||||
optionRender={option => <span>{option.value === 'whisper_local' ? t('Whisper · 本地') : PROVIDERS[option.value as ProviderKey]?.name}{option.value === 'infistar' && <small style={{ marginLeft: 8, color: 'var(--sub)' }}>{t('赞助')} · {t('$5 免费体验额度')}</small>}</span>} />
|
||||
optionRender={option => <span>{option.value === 'whisper_local' ? t('Whisper · 本地') : PROVIDERS[option.value as ProviderKey]?.name}{PROVIDERS[option.value as ProviderKey]?.sponsor && <small style={{ marginLeft: 8, color: 'var(--sub)' }}>{t('赞助')} · {PROVIDERS[option.value as ProviderKey]?.sponsor?.offer}</small>}</span>} />
|
||||
</Row>
|
||||
{cloud && connection ? <>
|
||||
<ProviderFields connection={connection} ariaPrefix={t('转写')} hideProvider placement="settings_model"
|
||||
|
||||
@@ -29,18 +29,18 @@ export default function ProviderFields({ connection, ariaPrefix = '', hideProvid
|
||||
const picker = !hideProvider && <Row wide label={t('提供商')} hint={preset?.hint || t('国内直连、国际服务和本机免费模型都可以选;不确定就从「推荐」开始。')}>
|
||||
<Select aria-label={label(t('提供商'))} value={key} placeholder={t('选择一家 AI 服务')} showSearch optionFilterProp="search" style={{ width: '100%' }} options={providerPickerOptions()}
|
||||
onChange={onChoose}
|
||||
optionRender={option => { const p = PROVIDERS[option.value as ProviderKey]; return <span>{p.name}{p.sponsor && <small style={{ marginLeft: 8, color: 'var(--sub)', fontWeight: 400 }}>{t('赞助')} · {t('$5 免费体验额度')}</small>}</span> }} />
|
||||
optionRender={option => { const p = PROVIDERS[option.value as ProviderKey]; return <span>{p.name}{p.sponsor && <small style={{ marginLeft: 8, color: 'var(--sub)', fontWeight: 400 }}>{t('赞助')} · {p.sponsor.offer}</small>}</span> }} />
|
||||
</Row>
|
||||
if (!connection) return <>{picker}</>
|
||||
return <>
|
||||
{picker}
|
||||
{preset?.sponsor && <div className="ac-sponsor">
|
||||
<div className="ac-sponsor-text"><b>{preset.name}<span className="ac-badge">{t('赞助')}</span></b>
|
||||
<span>{t('通过专属链接注册,可领取 $5 免费体验额度。')}</span>
|
||||
<span>{preset.sponsor.description}</span>
|
||||
<small>{t('该链接含推广分成,用于支持项目维护;领取条件以活动页面为准。')}</small></div>
|
||||
<div className="ac-sponsor-actions">
|
||||
<Btn variant="cta" size="sm" onClick={() => { trackSponsorLinkOpened({ sponsor: 'infistar', target: 'register', placement }); openExternalLink(preset.sponsor!.registerUrl) }}>{t('注册并领取体验额度')}</Btn>
|
||||
<Btn variant="text" size="sm" onClick={() => { trackSponsorLinkOpened({ sponsor: 'infistar', target: 'guide', placement }); openExternalLink(preset.sponsor!.guideUrl) }}>{t('接入说明')}</Btn>
|
||||
<Btn variant="cta" size="sm" onClick={() => { trackSponsorLinkOpened({ sponsor: preset.sponsor!.id, target: 'register', placement }); openExternalLink(preset.sponsor!.registerUrl) }}>{t('注册并领取体验额度')}</Btn>
|
||||
<Btn variant="text" size="sm" onClick={() => { trackSponsorLinkOpened({ sponsor: preset.sponsor!.id, target: 'guide', placement }); openExternalLink(preset.sponsor!.guideUrl) }}>{t('接入说明')}</Btn>
|
||||
</div>
|
||||
</div>}
|
||||
{!preset?.local && (sharedKey && !editShared
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { t } from '../../i18n'
|
||||
|
||||
export type ProviderKey = 'dashscope' | 'openai' | 'compatible' | 'infistar' | 'gemini' | 'deepseek' | 'seed' | 'kimi' | 'glm' | 'grok' | 'ollama' | 'lmstudio'
|
||||
export type ProviderKey = 'dashscope' | 'openai' | 'compatible' | 'infistar' | 'api88' | 'gemini' | 'deepseek' | 'seed' | 'kimi' | 'glm' | 'grok' | 'ollama' | 'lmstudio'
|
||||
type LocalPreset = { baseUrl: string; defaultModel: string; docsUrl: string; app: string }
|
||||
type CloudPreset = { baseUrl: string; defaultModel: string }
|
||||
type Sponsor = { registerUrl: string; guideUrl: string }
|
||||
export type SponsorId = 'infistar' | 'api88'
|
||||
type Sponsor = { id: SponsorId; registerUrl: string; guideUrl: string; offer: string; description: string }
|
||||
// 提供商会越来越多:下拉按组展示、可搜索,新增一家只在 PROVIDERS 里加一条并标 group
|
||||
type ProviderGroup = 'sponsor' | 'cloud' | 'compatible' | 'local'
|
||||
const PROVIDER_GROUPS: Array<{ key: ProviderGroup; label: () => string }> = [
|
||||
@@ -17,7 +18,8 @@ export const PROVIDERS: Record<ProviderKey, { name: string; short: string; hint:
|
||||
openai: { name: 'OpenAI', short: 'OpenAI', get hint() { return t('OpenAI 官方服务,接口地址已预设。') }, group: 'cloud', apiKeyField: 'openai_api_key', placeholder: 'sk-…', keyUrl: 'https://platform.openai.com/api-keys' },
|
||||
compatible: { get name() { return t('自定义 OpenAI 兼容接口') }, get short() { return t('自定义兼容接口') }, get hint() { return t('适用于 OpenRouter、第三方网关或自建服务。') }, group: 'compatible', apiKeyField: 'openai_api_key', get placeholder() { return t('sk-…(自建服务可留空)') }, keyUrl: '' },
|
||||
// 赞助合作伙伴(docs/INFISTAR_SETUP.md)。多模型网关,型号随账号而定:不预设默认模型,填好 key 后实时拉取
|
||||
infistar: { get name() { return t("Infistar 无限星河") }, short: 'Infistar', get hint() { return t("赞助合作伙伴。一个 Key 调用 Claude、GPT、Gemini、DeepSeek 等模型,接口地址已预设。") }, group: 'sponsor', apiKeyField: 'infistar_api_key', placeholder: 'sk-…', keyUrl: 'https://www.infistar.cc/register?aff=XLK3BCM6&ref_source=link', cloud: { baseUrl: 'https://infistar.cc/v1', defaultModel: '' }, sponsor: { registerUrl: 'https://www.infistar.cc/register?aff=XLK3BCM6&ref_source=link', guideUrl: 'https://github.com/zhouxiaoka/autoclip/blob/main/docs/INFISTAR_SETUP.md' } },
|
||||
api88: { get name() { return t('88API Token聚合平台') }, short: '88API', get hint() { return t('赞助合作伙伴。聚合语言、图片与语音模型,接口地址已预设。') }, group: 'sponsor', apiKeyField: 'api88_api_key', placeholder: 'sk-…', keyUrl: 'https://88api.ai/sign-up?aff=2PIc', cloud: { baseUrl: 'https://88api.ai/v1', defaultModel: '' }, sponsor: { id: 'api88', registerUrl: 'https://88api.ai/sign-up?aff=2PIc', guideUrl: 'https://github.com/zhouxiaoka/autoclip/blob/main/docs/88API_SETUP.md', get offer() { return t('新用户体验额度') }, get description() { return t('新用户通过专属链接注册可获体验额度;站内提供人工客服。') } } },
|
||||
infistar: { get name() { return t("Infistar 无限星河") }, short: 'Infistar', get hint() { return t("赞助合作伙伴。一个 Key 调用 Claude、GPT、Gemini、DeepSeek 等模型,接口地址已预设。") }, group: 'sponsor', apiKeyField: 'infistar_api_key', placeholder: 'sk-…', keyUrl: 'https://www.infistar.cc/register?aff=XLK3BCM6&ref_source=link', cloud: { baseUrl: 'https://infistar.cc/v1', defaultModel: '' }, sponsor: { id: 'infistar', get offer() { return t('$5 免费体验额度') }, get description() { return t('通过专属链接注册,可领取 $5 免费体验额度。') }, registerUrl: 'https://www.infistar.cc/register?aff=XLK3BCM6&ref_source=link', guideUrl: 'https://github.com/zhouxiaoka/autoclip/blob/main/docs/INFISTAR_SETUP.md' } },
|
||||
gemini: { name: 'Google Gemini', short: 'Gemini', get hint() { return t("Google AI Studio 的 Gemini 系列。") }, group: 'cloud', apiKeyField: 'gemini_api_key', placeholder: 'AIza…', keyUrl: 'https://aistudio.google.com/apikey' },
|
||||
deepseek: { name: 'DeepSeek', short: 'DeepSeek', get hint() { return t("DeepSeek 官方。国内直连,deepseek-flash 是当前 V4.1。") }, group: 'cloud', apiKeyField: 'deepseek_api_key', placeholder: 'sk-…', keyUrl: 'https://platform.deepseek.com/api_keys', cloud: { baseUrl: 'https://api.deepseek.com', defaultModel: 'deepseek-flash' } },
|
||||
seed: { name: 'Seed', short: 'Seed', get hint() { return t("火山方舟 Seed。国内直连,豆包 Seed 2.1 系列。") }, group: 'cloud', apiKeyField: 'seed_api_key', placeholder: '…', keyUrl: 'https://console.volcengine.com/ark/region:ark+cn-beijing/apiKey', cloud: { baseUrl: 'https://ark.cn-beijing.volces.com/api/v3', defaultModel: 'doubao-seed-2-1-lite-260915' } },
|
||||
|
||||
@@ -1409,5 +1409,9 @@
|
||||
"暂停": "Pause",
|
||||
"成片进度": "Clip position",
|
||||
"静音": "Mute",
|
||||
"取消静音": "Unmute"
|
||||
"取消静音": "Unmute",
|
||||
"88API Token聚合平台": "88API Token Platform",
|
||||
"赞助合作伙伴。聚合语言、图片与语音模型,接口地址已预设。": "Sponsor partner. Language, image and audio models with a preset API endpoint.",
|
||||
"新用户体验额度": "New-user trial credit",
|
||||
"新用户通过专属链接注册可获体验额度;站内提供人工客服。": "New users can receive trial credit via our referral link; live support is available on the platform."
|
||||
}
|
||||
|
||||
@@ -1409,5 +1409,9 @@
|
||||
"暂停": "Pausar",
|
||||
"成片进度": "Posición del clip",
|
||||
"静音": "Silenciar",
|
||||
"取消静音": "Activar sonido"
|
||||
"取消静音": "Activar sonido",
|
||||
"88API Token聚合平台": "Plataforma de tokens 88API",
|
||||
"赞助合作伙伴。聚合语言、图片与语音模型,接口地址已预设。": "Patrocinador. Modelos de lenguaje, imagen y audio con la dirección API preconfigurada.",
|
||||
"新用户体验额度": "Crédito de prueba para nuevos usuarios",
|
||||
"新用户通过专属链接注册可获体验额度;站内提供人工客服。": "Los nuevos usuarios pueden recibir crédito de prueba mediante nuestro enlace; la plataforma ofrece atención humana."
|
||||
}
|
||||
|
||||
@@ -1409,5 +1409,9 @@
|
||||
"暂停": "Pause",
|
||||
"成片进度": "Position dans le clip",
|
||||
"静音": "Couper le son",
|
||||
"取消静音": "Réactiver le son"
|
||||
"取消静音": "Réactiver le son",
|
||||
"88API Token聚合平台": "Plateforme de tokens 88API",
|
||||
"赞助合作伙伴。聚合语言、图片与语音模型,接口地址已预设。": "Partenaire sponsor. Modèles de langage, d’image et d’audio avec une adresse API préconfigurée.",
|
||||
"新用户体验额度": "Crédit d’essai pour les nouveaux utilisateurs",
|
||||
"新用户通过专属链接注册可获体验额度;站内提供人工客服。": "Les nouveaux utilisateurs peuvent recevoir un crédit d’essai via notre lien ; une assistance humaine est disponible sur la plateforme."
|
||||
}
|
||||
|
||||
@@ -1409,5 +1409,9 @@
|
||||
"暂停": "一時停止",
|
||||
"成片进度": "成片の再生位置",
|
||||
"静音": "ミュート",
|
||||
"取消静音": "ミュート解除"
|
||||
"取消静音": "ミュート解除",
|
||||
"88API Token聚合平台": "88API Tokenプラットフォーム",
|
||||
"赞助合作伙伴。聚合语言、图片与语音模型,接口地址已预设。": "スポンサー。言語・画像・音声モデルを集約。APIアドレスは設定済みです。",
|
||||
"新用户体验额度": "新規ユーザー体験クレジット",
|
||||
"新用户通过专属链接注册可获体验额度;站内提供人工客服。": "専用リンクから新規登録すると体験クレジットを受け取れます。サイト内で有人サポートを利用できます。"
|
||||
}
|
||||
|
||||
@@ -1409,5 +1409,9 @@
|
||||
"暂停": "일시정지",
|
||||
"成片进度": "완성본 재생 위치",
|
||||
"静音": "음소거",
|
||||
"取消静音": "음소거 해제"
|
||||
"取消静音": "음소거 해제",
|
||||
"88API Token聚合平台": "88API Token 플랫폼",
|
||||
"赞助合作伙伴。聚合语言、图片与语音模型,接口地址已预设。": "후원 파트너. 언어·이미지·음성 모델을 제공하며 API 주소가 미리 설정되어 있습니다.",
|
||||
"新用户体验额度": "신규 가입 체험 크레딧",
|
||||
"新用户通过专属链接注册可获体验额度;站内提供人工客服。": "전용 링크로 신규 가입하면 체험 크레딧을 받을 수 있습니다. 사이트에서 상담원 지원을 제공합니다."
|
||||
}
|
||||
|
||||
@@ -1409,5 +1409,9 @@
|
||||
"暂停": "Pausar",
|
||||
"成片进度": "Posição do clipe",
|
||||
"静音": "Silenciar",
|
||||
"取消静音": "Ativar som"
|
||||
"取消静音": "Ativar som",
|
||||
"88API Token聚合平台": "Plataforma de tokens 88API",
|
||||
"赞助合作伙伴。聚合语言、图片与语音模型,接口地址已预设。": "Patrocinador. Modelos de linguagem, imagem e áudio com endereço de API predefinido.",
|
||||
"新用户体验额度": "Crédito de teste para novos usuários",
|
||||
"新用户通过专属链接注册可获体验额度;站内提供人工客服。": "Novos usuários podem receber crédito de teste pelo nosso link; a plataforma oferece atendimento humano."
|
||||
}
|
||||
|
||||
@@ -1409,5 +1409,9 @@
|
||||
"暂停": "Пауза",
|
||||
"成片进度": "Позиция в клипе",
|
||||
"静音": "Без звука",
|
||||
"取消静音": "Включить звук"
|
||||
"取消静音": "Включить звук",
|
||||
"88API Token聚合平台": "Платформа токенов 88API",
|
||||
"赞助合作伙伴。聚合语言、图片与语音模型,接口地址已预设。": "Спонсор. Языковые, графические и аудиомодели с настроенным адресом API.",
|
||||
"新用户体验额度": "Пробный кредит для новых пользователей",
|
||||
"新用户通过专属链接注册可获体验额度;站内提供人工客服。": "Новые пользователи могут получить пробный кредит по нашей ссылке; на платформе доступна поддержка операторов."
|
||||
}
|
||||
|
||||
@@ -1409,5 +1409,9 @@
|
||||
"暂停": "暂停",
|
||||
"成片进度": "成片进度",
|
||||
"静音": "静音",
|
||||
"取消静音": "取消静音"
|
||||
"取消静音": "取消静音",
|
||||
"88API Token聚合平台": "88API Token聚合平台",
|
||||
"赞助合作伙伴。聚合语言、图片与语音模型,接口地址已预设。": "赞助合作伙伴。聚合语言、图片与语音模型,接口地址已预设。",
|
||||
"新用户体验额度": "新用户体验额度",
|
||||
"新用户通过专属链接注册可获体验额度;站内提供人工客服。": "新用户通过专属链接注册可获体验额度;站内提供人工客服。"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
const { test } = require('node:test')
|
||||
const assert = require('node:assert/strict')
|
||||
const fs = require('node:fs'), path = require('node:path'), vm = require('node:vm'), ts = require('typescript')
|
||||
const React = require('react')
|
||||
const dir = path.join(__dirname, '../src/features/settings')
|
||||
function load(file, mocks) {
|
||||
const exports = {}
|
||||
const code = ts.transpileModule(fs.readFileSync(path.join(dir, file), 'utf8'), {
|
||||
compilerOptions: { module: ts.ModuleKind.CommonJS, jsx: ts.JsxEmit.ReactJSX },
|
||||
}).outputText
|
||||
vm.runInNewContext(code, { exports, require: id => {
|
||||
if (id in mocks) return mocks[id]
|
||||
if (id === 'react/jsx-runtime') return require(id)
|
||||
throw Error(id)
|
||||
} })
|
||||
return exports
|
||||
}
|
||||
const providers = load('providers.ts', { '../../i18n': { t: text => text } })
|
||||
const plain = value => JSON.parse(JSON.stringify(value))
|
||||
function flatten(node) {
|
||||
if (!React.isValidElement(node)) return [node]
|
||||
return [node, ...React.Children.toArray(node.props.children).flatMap(flatten)]
|
||||
}
|
||||
|
||||
test('sponsor selection, offer, registration link and attribution follow the chosen provider', () => {
|
||||
const events = [], links = []
|
||||
const Fields = load('ProviderFields.tsx', {
|
||||
react: { useState: () => [false, () => {}] },
|
||||
antd: { Input: { Password: 'password' }, Select: 'select' },
|
||||
'../../i18n': { t: text => text }, '../../ui': { Btn: 'button', Row: 'row' },
|
||||
'../../utils/externalLinks': { openExternalLink: url => links.push(url) },
|
||||
'../../analytics/events': { trackSponsorLinkOpened: event => events.push(plain(event)) },
|
||||
'./providers': providers, './modelSettingsLogic': { presetKey: c => c.provider },
|
||||
}).default
|
||||
const options = providers.providerPickerOptions()
|
||||
assert.deepEqual(Array.from(options[0].options, p => p.value), ['api88', 'infistar'])
|
||||
for (const placement of ['home_setup', 'settings_model']) {
|
||||
for (const provider of ['api88', 'infistar']) {
|
||||
const preset = providers.PROVIDERS[provider]
|
||||
const nodes = flatten(Fields({ connection: { provider, api_key: '' }, placement, onChoose() {}, onEdit() {} }))
|
||||
const text = nodes.filter(n => typeof n === 'string').join(' ')
|
||||
assert.ok(text.includes(preset.sponsor.description))
|
||||
assert.equal(text.includes('$5'), provider === 'infistar')
|
||||
const buttons = nodes.filter(n => n?.type === 'button')
|
||||
buttons[0].props.onClick()
|
||||
assert.equal(links.at(-1), preset.sponsor.registerUrl)
|
||||
assert.deepEqual(events.at(-1), { sponsor: provider, target: 'register', placement })
|
||||
buttons[1].props.onClick()
|
||||
assert.equal(links.at(-1), preset.sponsor.guideUrl)
|
||||
assert.deepEqual(events.at(-1), { sponsor: provider, target: 'guide', placement })
|
||||
}
|
||||
}
|
||||
assert.equal(providers.PROVIDERS.api88.sponsor.registerUrl, 'https://88api.ai/sign-up?aff=2PIc')
|
||||
})
|
||||
@@ -18,6 +18,7 @@ GUIDES = (
|
||||
"docs/FAQ.md", "docs/FAQ.en.md", "docs/images/README.md",
|
||||
"CONTRIBUTING.md", "docs/MULTI_LLM_PROVIDER_GUIDE.md",
|
||||
"docs/INFISTAR_SETUP.md", "docs/INFISTAR_SETUP.en.md",
|
||||
"docs/88API_SETUP.md", "docs/88API_SETUP.en.md",
|
||||
)
|
||||
FENCES = re.compile(r"^```[^\n]*\n(.*?)^```\s*$", re.MULTILINE | re.DOTALL)
|
||||
LINKS = re.compile(r'\]\(([^\s)]+)\)|(?:href|src)="([^"]+)"')
|
||||
|
||||
Reference in New Issue
Block a user