mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-08-02 06:51:14 +08:00
feat(plugin-sdk): add TypeScript SDK for Infinite Canvas plugins with automatic JSX and build support
This commit is contained in:
@@ -9,7 +9,7 @@ import { useAssetStore } from "@/stores/use-asset-store";
|
||||
import { modelOptionLabel, modelOptionName, normalizeModelOptionValue, selectableModelsByCapability, useConfigStore } from "@/stores/use-config-store";
|
||||
import { useWorkbenchAgentStore } from "@/stores/use-workbench-agent-store";
|
||||
|
||||
// 在网页端执行 Agent 的「站点级」工具(画布列表、工作台生成、提示词搜索、素材增删查等)。
|
||||
// 在网页端执行 Agent 的「站点级」工具(画布列表、工作台生成、提示词搜索、资产增删查等)。
|
||||
// 这些工具的数据都在浏览器本地(localforage / zustand),因此由本模块直接读写对应 store 后返回结果。
|
||||
|
||||
export const SITE_TOOL_NAMES = [
|
||||
@@ -36,8 +36,8 @@ export const SITE_TOOL_LABELS: Record<SiteToolName, string> = {
|
||||
workbench_video_get_config: "视频配置",
|
||||
workbench_video_generate: "视频创作台生成",
|
||||
prompts_search: "搜索提示词",
|
||||
assets_list: "素材列表",
|
||||
assets_add: "添加素材",
|
||||
assets_list: "资产列表",
|
||||
assets_add: "添加资产",
|
||||
};
|
||||
|
||||
type SiteToolInput = Record<string, unknown>;
|
||||
@@ -194,7 +194,7 @@ async function searchPrompts(input: SiteToolInput) {
|
||||
|
||||
function listAssets(input: SiteToolInput) {
|
||||
const { assets, hydrated } = useAssetStore.getState();
|
||||
if (!hydrated) throw new Error("素材还在加载中,请稍后重试");
|
||||
if (!hydrated) throw new Error("资产还在加载中,请稍后重试");
|
||||
const kind = input.kind === "text" || input.kind === "image" || input.kind === "video" ? input.kind : "all";
|
||||
const keyword = String(input.keyword || "").trim().toLowerCase();
|
||||
const filtered = assets.filter((asset) => {
|
||||
@@ -221,7 +221,7 @@ function listAssets(input: SiteToolInput) {
|
||||
async function addAsset(input: SiteToolInput) {
|
||||
const kind = input.kind;
|
||||
const title = String(input.title || "").trim();
|
||||
if (!title) throw new Error("请提供素材标题 title");
|
||||
if (!title) throw new Error("请提供资产标题 title");
|
||||
const tags = Array.isArray(input.tags) ? input.tags.filter((tag): tag is string => typeof tag === "string") : [];
|
||||
const source = typeof input.source === "string" ? input.source : "Agent";
|
||||
const note = typeof input.note === "string" ? input.note : undefined;
|
||||
|
||||
@@ -56,17 +56,17 @@ function withCacheBust(url: string) {
|
||||
}
|
||||
|
||||
// 从 URL 安装(或覆盖更新)一个插件,成功后立即启用
|
||||
export async function installPluginFromUrl(url: string) {
|
||||
export async function installPluginFromUrl(url: string, opts?: { official?: boolean }) {
|
||||
const source = await fetchPluginSource(url);
|
||||
const plugin = await evaluatePluginSource(source);
|
||||
deactivatePlugin(plugin.id); // 覆盖旧版本
|
||||
usePluginStore.getState().upsert({ id: plugin.id, name: plugin.name || plugin.id, version: plugin.version || "0.0.0", description: plugin.description, url, source, enabled: true });
|
||||
usePluginStore.getState().upsert({ id: plugin.id, name: plugin.name || plugin.id, version: plugin.version || "0.0.0", description: plugin.description, url, source, enabled: true, official: opts?.official });
|
||||
activatePlugin(plugin);
|
||||
return plugin;
|
||||
}
|
||||
|
||||
export async function updatePlugin(record: InstalledPlugin) {
|
||||
return installPluginFromUrl(record.url);
|
||||
return installPluginFromUrl(record.url, { official: record.official });
|
||||
}
|
||||
|
||||
export async function setPluginEnabled(record: InstalledPlugin, enabled: boolean) {
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { PLUGIN_REGISTRY_URL } from "@/constant/env";
|
||||
|
||||
// 官方插件清单里的一条(entry 已解析成绝对 URL)
|
||||
export type OfficialPluginEntry = {
|
||||
id: string;
|
||||
name: string;
|
||||
version: string;
|
||||
description?: string;
|
||||
icon?: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
type RawEntry = { id?: string; name?: string; version?: string; description?: string; icon?: string; entry?: string; url?: string };
|
||||
type RawManifest = { plugins?: RawEntry[] };
|
||||
|
||||
// 拉取官方插件清单;entry(相对文件名)按清单地址解析成绝对 URL,再走既有 URL 安装流程
|
||||
export async function fetchOfficialPlugins(registryUrl: string = PLUGIN_REGISTRY_URL): Promise<OfficialPluginEntry[]> {
|
||||
const response = await fetch(registryUrl, { headers: { accept: "application/json" } });
|
||||
if (!response.ok) throw new Error(`获取官方插件列表失败 (HTTP ${response.status})`);
|
||||
const data = (await response.json()) as RawManifest;
|
||||
const list = Array.isArray(data?.plugins) ? data.plugins : [];
|
||||
return list
|
||||
.filter((item): item is RawEntry & { id: string } => Boolean(item && item.id && (item.entry || item.url)))
|
||||
.map((item) => ({
|
||||
id: item.id,
|
||||
name: item.name || item.id,
|
||||
version: item.version || "0.0.0",
|
||||
description: item.description,
|
||||
icon: item.icon,
|
||||
url: item.url ? item.url : new URL(item.entry as string, registryUrl).toString(),
|
||||
}));
|
||||
}
|
||||
@@ -141,7 +141,7 @@ export function buildSeedancePromptText(prompt: string, images: ReferenceImage[]
|
||||
];
|
||||
const text = prompt.trim();
|
||||
if (!labels.length) return text;
|
||||
return `参考素材编号:${labels.join("、")}。请按这些编号理解提示词中的图片、视频和音频引用。\n\n${text}`;
|
||||
return `参考资产编号:${labels.join("、")}。请按这些编号理解提示词中的图片、视频和音频引用。\n\n${text}`;
|
||||
}
|
||||
|
||||
export function seedanceVideoReferenceError(videos: ReferenceVideo[]) {
|
||||
@@ -166,4 +166,4 @@ export function seedanceVideoReferenceError(videos: ReferenceVideo[]) {
|
||||
return "";
|
||||
}
|
||||
|
||||
export const seedanceVideoReferenceHint = "参考视频需为 mp4/mov,H.264/H.265,FPS 24-60;含真人人脸素材请使用火山授权 asset:// 素材。";
|
||||
export const seedanceVideoReferenceHint = "参考视频需为 mp4/mov,H.264/H.265,FPS 24-60;含真人人脸资产请使用火山授权 asset:// 资产。";
|
||||
|
||||
Reference in New Issue
Block a user