add stable media support and sync skillhub UI

This commit is contained in:
wtgoku
2026-04-08 23:17:42 +08:00
parent ff4f370763
commit 4b20dc5e37
32 changed files with 6269 additions and 393 deletions
+188
View File
@@ -0,0 +1,188 @@
import type { Locale } from "@/lib/site-content";
type LiveCopy = {
nav: {
skills: string;
};
header: {
zh: string;
en: string;
logout: string;
loggingOut: string;
loginHint: string;
};
login: {
fieldLabel: string;
fieldHint: string;
submit: string;
submitting: string;
helperTitle: string;
helperBody: string;
commandLabel: string;
alreadySignedIn: string;
continueCta: string;
invalidKey: string;
};
console: {
unauthenticatedTitle: string;
unauthenticatedBody: string;
loginCta: string;
docsCta: string;
signedInAs: string;
liveTitle: string;
liveSubtitle: string;
quickstartTitle: string;
quickstartBody: string;
catalogCount: string;
noSkills: string;
noUsage: string;
loadErrorPrefix: string;
};
skills: {
tag: string;
title: string;
subtitle: string;
searchLabel: string;
searchPlaceholder: string;
searchAction: string;
resultsPrefix: string;
unauthenticatedTitle: string;
unauthenticatedBody: string;
loginCta: string;
docsCta: string;
noResults: string;
routeKey: string;
modelType: string;
latency: string;
tags: string;
loadErrorPrefix: string;
};
};
const copy: Record<Locale, LiveCopy> = {
zh: {
nav: {
skills: "技能页",
},
header: {
zh: "中文",
en: "EN",
logout: "登出",
loggingOut: "退出中...",
loginHint: "未登录",
},
login: {
fieldLabel: "PopiNewAPI Key",
fieldHint: "使用和 `popiart auth login --key ...` 相同的上游 key 登录,页面会换取产品层 session。",
submit: "登录控制台",
submitting: "登录中...",
helperTitle: "与 CLI 使用同一条认证链路",
helperBody: "页面登录后,控制台会通过 popiartServer 会话去读取 skills、budget 和 usage,而不是直接在浏览器里保存 provider key。",
commandLabel: "CLI 等价命令",
alreadySignedIn: "当前已登录,可直接进入控制台或继续查看技能目录。",
continueCta: "进入控制台",
invalidKey: "请输入可用的 PopiNewAPI Key。",
},
console: {
unauthenticatedTitle: "登录后查看真实控制台数据",
unauthenticatedBody: "这页现在直接读取 popiartServer 的 auth、skills、budget 和 usage 接口。未登录时不会伪造密钥和余额。",
loginCta: "去登录",
docsCta: "看接入文档",
signedInAs: "当前账号",
liveTitle: "已接入 popiartServer 实时数据",
liveSubtitle: "页面逻辑沿用 `popiart auth login`、`skills list` 和 `budget` 的产品层协议。",
quickstartTitle: "快速安装指令",
quickstartBody: "CLI 和 Web 共用同一个产品层 endpoint,下面这段命令可直接对应到当前控制台。",
catalogCount: "技能目录",
noSkills: "当前账号下没有可见技能。",
noUsage: "当前周期还没有技能用量。",
loadErrorPrefix: "控制台数据加载失败",
},
skills: {
tag: "SKILL CATALOG",
title: "官方技能目录",
subtitle: "当前列表直接读取 `/v1/skills`,与 `popiart skills list` 使用同一份产品层数据。",
searchLabel: "搜索技能",
searchPlaceholder: "搜索技能名、描述或标签",
searchAction: "查询",
resultsPrefix: "共找到",
unauthenticatedTitle: "登录后查看真实技能目录",
unauthenticatedBody: "技能页会直接走产品层会话鉴权,因此未登录时不展示假数据。",
loginCta: "登录查看",
docsCta: "阅读文档",
noResults: "没有匹配的技能。",
routeKey: "路由键",
modelType: "模型类型",
latency: "预计耗时",
tags: "标签",
loadErrorPrefix: "技能目录加载失败",
},
},
en: {
nav: {
skills: "Skills",
},
header: {
zh: "中文",
en: "EN",
logout: "Logout",
loggingOut: "Logging out...",
loginHint: "Signed out",
},
login: {
fieldLabel: "PopiNewAPI Key",
fieldHint:
"Use the same upstream key as `popiart auth login --key ...`. The web app exchanges it for a product-layer session.",
submit: "Sign in to console",
submitting: "Signing in...",
helperTitle: "The same auth path as the CLI",
helperBody:
"After sign-in, the console reads skills, budget, and usage through popiartServer instead of storing provider credentials in the browser.",
commandLabel: "Equivalent CLI command",
alreadySignedIn: "You are already signed in. Jump back to the console or continue browsing the catalog.",
continueCta: "Open console",
invalidKey: "Enter a valid PopiNewAPI key.",
},
console: {
unauthenticatedTitle: "Sign in to view live console data",
unauthenticatedBody:
"This page now reads popiartServer auth, skills, budget, and usage endpoints directly. It no longer fakes keys or credits for signed-out users.",
loginCta: "Sign in",
docsCta: "Read docs",
signedInAs: "Signed in as",
liveTitle: "Connected to live popiartServer data",
liveSubtitle:
"The page follows the same product-layer protocol used by `popiart auth login`, `skills list`, and `budget`.",
quickstartTitle: "Quick install commands",
quickstartBody: "The CLI and the web console share the same product endpoint and auth flow.",
catalogCount: "Catalog",
noSkills: "No skills are visible for this account yet.",
noUsage: "No skill usage has been recorded for the current period.",
loadErrorPrefix: "Failed to load console data",
},
skills: {
tag: "SKILL CATALOG",
title: "Official skill catalog",
subtitle: "This list is fetched from `/v1/skills`, the same surface used by `popiart skills list`.",
searchLabel: "Search skills",
searchPlaceholder: "Search by skill name, description, or tag",
searchAction: "Search",
resultsPrefix: "Results",
unauthenticatedTitle: "Sign in to view the live catalog",
unauthenticatedBody:
"The catalog page uses product-layer session auth, so it does not render fake skill data for signed-out users.",
loginCta: "Sign in",
docsCta: "Read docs",
noResults: "No skills matched this query.",
routeKey: "Route key",
modelType: "Model type",
latency: "Estimated duration",
tags: "Tags",
loadErrorPrefix: "Failed to load the skill catalog",
},
},
};
export function getLiveCopy(locale: Locale) {
return copy[locale] ?? copy.zh;
}
+223
View File
@@ -0,0 +1,223 @@
import { cookies } from "next/headers";
export const SESSION_COOKIE_NAME = "popiart_session";
type ApiEnvelope<T> = {
ok: boolean;
data?: T;
error?: {
code?: string;
message?: string;
details?: unknown;
};
};
export type PopiartUser = {
id: string;
email: string;
name: string;
scopes?: string[];
};
export type LoginResponse = {
key: string;
token: string;
user: PopiartUser;
};
export type AuthSessionView = {
user: PopiartUser;
session_key?: string;
upstream_key_masked?: string;
};
export type Skill = {
id: string;
name: string;
description: string;
tags: string[];
version: string;
model_type: string;
route_key?: string;
estimated_duration_s: number;
};
export type SkillListResponse = {
items: Skill[];
total: number;
limit: number;
offset: number;
};
export type BudgetSummary = {
period: {
start: string;
end: string;
};
used: {
tokens: number;
cost_usd: number;
};
limit: {
monthly_tokens: number;
monthly_cost_usd: number;
};
remaining: {
tokens: number;
cost_usd: number;
};
};
export type BudgetUsageRow = {
dimension: string;
tokens_used: number;
cost_usd: number;
job_count: number;
};
export type BudgetUsage = {
rows: BudgetUsageRow[];
total: {
tokens_used: number;
cost_usd: number;
job_count: number;
};
};
export class PopiartApiError extends Error {
status: number;
code?: string;
details?: unknown;
constructor(status: number, message: string, code?: string, details?: unknown) {
super(message);
this.name = "PopiartApiError";
this.status = status;
this.code = code;
this.details = details;
}
}
function stripTrailingSlash(value: string) {
return value.replace(/\/+$/, "");
}
export function getPopiartEndpoint() {
const configured =
process.env.POPIART_ENDPOINT ??
process.env.POPIART_SERVER_URL ??
"http://127.0.0.1:8080/v1";
return stripTrailingSlash(configured);
}
export function buildPopiartUrl(pathname: string) {
const path = pathname.startsWith("/") ? pathname : `/${pathname}`;
return `${getPopiartEndpoint()}${path}`;
}
async function readEnvelope<T>(response: Response) {
try {
return (await response.json()) as ApiEnvelope<T>;
} catch {
return null;
}
}
export async function popiartFetchEnvelope<T>(pathname: string, init: RequestInit = {}) {
const response = await fetch(buildPopiartUrl(pathname), {
...init,
cache: "no-store",
});
const payload = await readEnvelope<T>(response);
return { response, payload };
}
export async function popiartRequest<T>(
pathname: string,
options: {
method?: string;
body?: BodyInit | object;
headers?: HeadersInit;
token?: string;
} = {},
) {
const headers = new Headers(options.headers);
let body: BodyInit | undefined;
if (options.token) {
headers.set("Authorization", `Bearer ${options.token}`);
}
if (options.body instanceof FormData || typeof options.body === "string") {
body = options.body;
} else if (options.body !== undefined) {
headers.set("Content-Type", "application/json");
body = JSON.stringify(options.body);
}
const { response, payload } = await popiartFetchEnvelope<T>(pathname, {
method: options.method ?? (body ? "POST" : "GET"),
headers,
body,
});
if (!response.ok || !payload?.ok) {
throw new PopiartApiError(
response.status,
payload?.error?.message ?? `Request failed with status ${response.status}`,
payload?.error?.code,
payload?.error?.details,
);
}
return payload.data as T;
}
export async function getSessionToken() {
const cookieStore = await cookies();
return cookieStore.get(SESSION_COOKIE_NAME)?.value ?? null;
}
export async function getViewerSession() {
const token = await getSessionToken();
if (!token) {
return null;
}
try {
return await popiartRequest<AuthSessionView>("/auth/me", { token });
} catch (error) {
if (error instanceof PopiartApiError && error.status === 401) {
return null;
}
throw error;
}
}
export async function getSkillsCatalog(search?: string) {
const token = await getSessionToken();
if (!token) {
return null;
}
const query = new URLSearchParams();
query.set("limit", "100");
if (search) {
query.set("search", search);
}
return popiartRequest<SkillListResponse>(`/skills?${query.toString()}`, { token });
}
export async function getBudgetSummary() {
const token = await getSessionToken();
if (!token) {
return null;
}
return popiartRequest<BudgetSummary>("/budget", { token });
}
export async function getBudgetUsage() {
const token = await getSessionToken();
if (!token) {
return null;
}
return popiartRequest<BudgetUsage>("/budget/usage", { token });
}
+159 -78
View File
@@ -18,6 +18,14 @@ type Step = {
description: string;
};
type InstallMethod = {
badge: string;
name: string;
description: string;
command: string;
note: string;
};
type Plan = {
badge: string;
name: string;
@@ -116,6 +124,13 @@ export type AppDictionary = {
flowTitle: string;
flowSubtitle: string;
flowSteps: Step[];
installTag: string;
installTitle: string;
installSubtitle: string;
installMethods: InstallMethod[];
bootstrapTitle: string;
bootstrapDescription: string;
bootstrapCommand: string;
trustTag: string;
trustTitle: string;
trustStats: Stat[];
@@ -194,23 +209,23 @@ const zh: AppDictionary = {
},
home: {
tag: "OFFICIAL SKILLS",
title: "把官方 PopiArt Skills 接进你的创作与自动化流程",
title: "安装 popiartcli把官方 PopiArt Skills 接进你的 Coding Agent",
subtitle:
"覆盖图片生成、编辑、视频与批量工作流,让团队用统一账号、统一计费、统一控制台管理所有 AI 能力。",
primaryCta: "开始使用",
"通过统一 CLI 发现、查看、调用官方 skills;当任务需要图像、视频与多模态模型时,再由产品层统一处理授权、路由和计费。",
primaryCta: "安装 CLI",
secondaryCta: "查看文档",
freeTrial: "无需信用卡,注册即送 50 Credits",
socialProof: "已为团队工作流准备好账号、订阅与用量追踪",
stageLabel: "本月概览",
stageValue: "12,480 Credits",
stageDescription: "管理官方技能、订阅计划、API key 与项目级用量。",
freeTrial: "支持 Homebrew、curl | sh、Windows PowerShell 与源码构建",
socialProof: "安装后可直接 bootstrap 到 Codex、OpenCode 与 OpenClaw",
stageLabel: "CLI 命令面",
stageValue: "auth / skills / run",
stageDescription: "继续用 jobs、artifacts、budget、project、models、bootstrap 完成完整工作流。",
heroPhrases: [
"图片生成",
"图像编辑",
"图生视频",
"动作迁移",
"批量处理",
"官方计费",
"auth login",
"skills list",
"run --wait",
"jobs wait",
"artifacts pull",
"budget status",
],
scenesTag: "SCENARIOS",
scenesTitle: "覆盖高频 AI 创作场景",
@@ -247,30 +262,62 @@ const zh: AppDictionary = {
description: "把技能能力、额度、项目和账单统一到一个控制台管理。",
},
],
flowTag: "INTEGRATION",
flowTitle: "三步接入官方控制台",
flowSubtitle: "先搭出官网结构,再逐步接回 popiartServer 的登录、用量和计费接口。",
flowTag: "QUICK START",
flowTitle: "三步开始使用 popiartcli",
flowSubtitle: "直接沿用仓库 README 的真实安装与首次使用路径,而不是另造一套平台流程。",
flowSteps: [
{
title: "注册并登录 PopiArt 账号",
description: "先拿到产品层账号与 API key,而不是把供应商密钥直接暴露给客户端。",
title: "安装 popiartcli",
description: "优先使用 Homebrew、官方 install.sh 或 PowerShell 脚本,把 CLI 先装到本地环境。",
},
{
title: "在文档页完成 CLI 或 API 接入",
description: "根据官方 quick start 配置 endpoint、token 和默认输出目录。",
title: "执行 bootstrap 接入 agent 生态",
description: "生成 completion、默认 discovery profile,并可直接暴露给 Codex / OpenCode 使用。",
},
{
title: "在控制台查看技能、用量和账单",
description: "按项目查看技能调用量、订阅状态和充值记录。",
title: "登录并调用官方 skill",
description: "执行 auth login、skills list、run、jobs wait、artifacts pull,进入完整工作流。",
},
],
trustTag: "CONTROL",
trustTitle: "围绕产品层能力组织,而不是裸模型调用",
installTag: "INSTALL",
installTitle: "首页直接开始安装 popiartcli",
installSubtitle: "推荐先安装 CLI,再按需执行 bootstrap,让 PopiArt 在你的 agent 环境里直接可发现。",
installMethods: [
{
badge: "RECOMMENDED",
name: "Homebrew",
description: "适合 macOS / Linux,也是后续升级最稳定的方式。",
command: "brew tap wtgoku-create/popi\nbrew install wtgoku-create/popi/popiart",
note: "升级:brew upgrade wtgoku-create/popi/popiart",
},
{
badge: "CLI ONLY",
name: "curl | sh",
description: "从 GitHub Releases 安装 Go CLI 二进制,默认只安装 CLI 本体。",
command:
"curl -fsSL https://raw.githubusercontent.com/wtgoku-create/popiartcli/main/install.sh | sh -s -- --cli-only",
note: "升级:popiart update",
},
{
badge: "WINDOWS",
name: "PowerShell",
description: "Windows 环境使用官方 install.ps1,支持指定版本安装。",
command:
"irm https://raw.githubusercontent.com/wtgoku-create/popiartcli/main/install.ps1 | iex",
note: "也可以通过 VERSION 环境变量安装指定版本。",
},
],
bootstrapTitle: "安装后建议立即执行 bootstrap",
bootstrapDescription: "生成 shell completion、默认 skill discovery profile,并把 PopiArt 直接暴露给 agent。",
bootstrapCommand:
"popiart bootstrap --agent codex --completion zsh\npopiart bootstrap --agent codex --discoverable\npopiart skills list",
trustTag: "CLI DESIGN",
trustTitle: "围绕 agent 工作流设计,而不是零散脚本拼装",
trustStats: [
{ label: "官方技能目录", value: "24+" },
{ label: "项目级路由覆盖", value: "Per Project" },
{ label: "统一登录与会话", value: "1 Account" },
{ label: "订阅与充值", value: "Built In" },
{ label: "默认 JSON 输出", value: "ok / data" },
{ label: "长任务轮询", value: "jobs wait" },
{ label: "工件恢复下载", value: "artifacts pull" },
{ label: "生态引导", value: "bootstrap" },
],
},
pricing: {
@@ -329,23 +376,24 @@ const zh: AppDictionary = {
tag: "DOCUMENTATION",
title: "PopiArt 开发者文档",
intro:
"先把官网、登录和控制台骨架搭起来,后续直接把文档页接到 popiartcli 与 popiartServer 的真实协议。",
"首页与文档都直接围绕 popiartcli README 的真实安装链路、bootstrap 和命令面来组织。",
quickStartTitle: "快速开始",
quickSteps: [
{
title: "1. 设置服务地址",
description: "将 Web 控制台和 CLI 指向统一的产品层后端。",
code: "export POPIART_ENDPOINT=http://127.0.0.1:8080/v1",
title: "1. 安装 popiartcli",
description: "优先使用 Homebrew,或者用官方 install.sh / install.ps1 安装 CLI。",
code: "brew tap wtgoku-create/popi\nbrew install wtgoku-create/popi/popiart",
},
{
title: "2. 登录并写入 token",
description: "先拿到产品层 session,后端再桥接到上游模型网关。",
code: "popiart auth login --key <your-product-token>",
title: "2. 执行 bootstrap",
description: "把 PopiArt 直接接进 Codex / OpenCode,并生成 completion 与默认发现配置。",
code: "popiart bootstrap --agent codex --discoverable",
},
{
title: "3. 调用官方 skill",
description: "直接通过技能名或 route key 发起任务。",
code: "popiart skills list\npopiart run --skill popiskill-image-text2image-basic-v1",
title: "3. 登录并调用官方 skill",
description: "完成 auth login 后,先列技能,再执行 run / jobs wait / artifacts pull。",
code:
"popiart auth login --key <your-product-token>\npopiart skills list\npopiart run popiskill-image-text2image-basic-v1 --input @params.json --wait",
},
],
envTitle: "环境变量",
@@ -486,23 +534,23 @@ const en: AppDictionary = {
},
home: {
tag: "OFFICIAL SKILLS",
title: "Bring official PopiArt skills into your creative and automation workflows",
title: "Install popiartcli and bring official PopiArt skills into your coding agent",
subtitle:
"One account, one console, and one billing layer for image generation, editing, video, and team-ready AI operations.",
primaryCta: "Get started",
"Use one CLI to discover, inspect, and run official skills, while product-layer auth, routing, and billing stay behind the PopiArt backend.",
primaryCta: "Install CLI",
secondaryCta: "Read docs",
freeTrial: "No credit card required. 50 free credits on signup.",
socialProof: "Built for account management, subscriptions, and usage visibility.",
stageLabel: "This month",
stageValue: "12,480 Credits",
stageDescription: "Manage official skills, subscriptions, API keys, and per-project usage from one place.",
freeTrial: "Supports Homebrew, curl | sh, Windows PowerShell, and source builds",
socialProof: "Bootstrap directly into Codex, OpenCode, and OpenClaw after install",
stageLabel: "CLI surface",
stageValue: "auth / skills / run",
stageDescription: "Then move into jobs, artifacts, budget, project, models, and bootstrap for the full workflow.",
heroPhrases: [
"Text to image",
"Image edit",
"Image to video",
"Motion transfer",
"Batch ops",
"Official billing",
"auth login",
"skills list",
"run --wait",
"jobs wait",
"artifacts pull",
"budget status",
],
scenesTag: "SCENARIOS",
scenesTitle: "Designed around high-frequency AI production flows",
@@ -540,31 +588,63 @@ const en: AppDictionary = {
description: "Bring skills, quotas, projects, and billing together in one product surface.",
},
],
flowTag: "INTEGRATION",
flowTitle: "Three steps to launch the official console",
flowTag: "QUICK START",
flowTitle: "Three steps to start with popiartcli",
flowSubtitle:
"Start with the web experience, then connect auth, usage, and billing to popiartServer.",
"This mirrors the real install and first-run path from the popiartcli README instead of inventing a separate web-only flow.",
flowSteps: [
{
title: "Create and sign into a PopiArt account",
description: "Use a product-level account instead of pushing provider credentials into the client.",
title: "Install popiartcli",
description: "Use Homebrew, the official install.sh, or PowerShell to get the CLI onto your local machine.",
},
{
title: "Complete CLI or API setup from the docs",
description: "Point your tooling to the shared endpoint and configure the official token flow.",
title: "Run bootstrap for your agent environment",
description: "Generate completion, default discovery profiles, and make PopiArt directly discoverable to Codex or OpenCode.",
},
{
title: "Track skills, usage, and billing in the console",
description: "Review usage by project, skill, and billing period from a single dashboard.",
title: "Sign in and run official skills",
description: "Use auth login, skills list, run, jobs wait, and artifacts pull to complete the first real workflow.",
},
],
trustTag: "CONTROL",
trustTitle: "Organized around product semantics instead of raw model calls",
installTag: "INSTALL",
installTitle: "Install popiartcli directly from the homepage",
installSubtitle: "Install the CLI first, then optionally run bootstrap so PopiArt becomes discoverable inside your agent environment.",
installMethods: [
{
badge: "RECOMMENDED",
name: "Homebrew",
description: "Best for macOS and Linux, and the cleanest upgrade path later.",
command: "brew tap wtgoku-create/popi\nbrew install wtgoku-create/popi/popiart",
note: "Upgrade with: brew upgrade wtgoku-create/popi/popiart",
},
{
badge: "CLI ONLY",
name: "curl | sh",
description: "Download the released Go CLI binary directly from GitHub Releases.",
command:
"curl -fsSL https://raw.githubusercontent.com/wtgoku-create/popiartcli/main/install.sh | sh -s -- --cli-only",
note: "Upgrade with: popiart update",
},
{
badge: "WINDOWS",
name: "PowerShell",
description: "Use the official install.ps1 flow on Windows, with optional version pinning.",
command:
"irm https://raw.githubusercontent.com/wtgoku-create/popiartcli/main/install.ps1 | iex",
note: "You can also pin a VERSION before running the installer.",
},
],
bootstrapTitle: "Run bootstrap right after installation",
bootstrapDescription: "Generate shell completion, a default discovery profile, and make PopiArt directly available to your coding agent.",
bootstrapCommand:
"popiart bootstrap --agent codex --completion zsh\npopiart bootstrap --agent codex --discoverable\npopiart skills list",
trustTag: "CLI DESIGN",
trustTitle: "Designed around agent workflows instead of scattered scripts",
trustStats: [
{ label: "Official skills", value: "24+" },
{ label: "Project routing", value: "Per project" },
{ label: "Unified auth", value: "1 account" },
{ label: "Billing flows", value: "Built in" },
{ label: "JSON-first output", value: "ok / data" },
{ label: "Long-task polling", value: "jobs wait" },
{ label: "Artifact recovery", value: "artifacts pull" },
{ label: "Agent bootstrap", value: "bootstrap" },
],
},
pricing: {
@@ -623,23 +703,24 @@ const en: AppDictionary = {
tag: "DOCUMENTATION",
title: "PopiArt developer docs",
intro:
"This first pass recreates the information architecture of the reference site while aligning it with popiartcli and popiartServer.",
"The homepage and docs now follow the real install chain, bootstrap flow, and command surface from popiartcli.",
quickStartTitle: "Quick start",
quickSteps: [
{
title: "1. Configure the endpoint",
description: "Point both the console and CLI at the same product-layer backend.",
code: "export POPIART_ENDPOINT=http://127.0.0.1:8080/v1",
title: "1. Install popiartcli",
description: "Use Homebrew first when possible, or fall back to install.sh / install.ps1.",
code: "brew tap wtgoku-create/popi\nbrew install wtgoku-create/popi/popiart",
},
{
title: "2. Sign in with a product token",
description: "Keep provider credentials behind the backend and work through the product session layer.",
code: "popiart auth login --key <your-product-token>",
title: "2. Bootstrap your agent environment",
description: "Generate completion, default discovery profiles, and make PopiArt directly discoverable.",
code: "popiart bootstrap --agent codex --discoverable",
},
{
title: "3. Run official skills",
description: "Invoke skills by skill id or move to model-level calls when you need direct debugging.",
code: "popiart skills list\npopiart run --skill popiskill-image-text2image-basic-v1",
title: "3. Sign in and run official skills",
description: "Authenticate, inspect the catalog, and run the first skill through the real job workflow.",
code:
"popiart auth login --key <your-product-token>\npopiart skills list\npopiart run popiskill-image-text2image-basic-v1 --input @params.json --wait",
},
],
envTitle: "Environment variables",