feat(themes): 添加可编辑的站点外观和皮肤主题(#408)
This commit is contained in:
@@ -8,6 +8,7 @@ import "@/lib/plugins/builtin";
|
||||
import { createEagleAssetSource, EAGLE_DEFAULT_BASE_URL, eagleAssetPlugin } from "@/lib/plugins/builtin/eagle";
|
||||
import type { ExternalAssetFolder, ExternalAssetItem } from "@/lib/plugins/plugin-types";
|
||||
import type { Asset } from "@/stores/use-asset-store";
|
||||
import { useAppearanceStore } from "@/stores/use-appearance-store";
|
||||
import { usePluginStore } from "@/stores/use-plugin-store";
|
||||
import { CollectionGrid, ListToolbar, PageHeader, PaginationBar, WorkspacePage } from "@/components/layout/workspace-page";
|
||||
import { AssetLibraryCard, AssetLibraryCardMedia } from "@/components/assets/asset-library-card";
|
||||
@@ -16,6 +17,7 @@ import "./eagle.css";
|
||||
export default function EagleLibraryPage() {
|
||||
const navigate = useNavigate();
|
||||
const { message } = App.useApp();
|
||||
const brandName = useAppearanceStore((state) => state.appearance.brandName);
|
||||
const installations = usePluginStore((state) => state.installations);
|
||||
const hydrated = usePluginStore((state) => state.hydrated);
|
||||
const ensurePlugin = usePluginStore((state) => state.ensurePlugin);
|
||||
@@ -133,13 +135,13 @@ export default function EagleLibraryPage() {
|
||||
<WorkspacePage grid className="library-page eagle-library-page">
|
||||
<PageHeader
|
||||
title="Eagle 素材库"
|
||||
description="把 Eagle 作为影策的外部素材来源,直接浏览和管理 Eagle 原始文件。"
|
||||
actions={<Button icon={<ArrowLeft className="size-3.5" />} onClick={() => navigate("/assets")}>返回影策素材库</Button>}
|
||||
description={`把 Eagle 作为${brandName}的外部素材来源,直接浏览和管理 Eagle 原始文件。`}
|
||||
actions={<Button icon={<ArrowLeft className="size-3.5" />} onClick={() => navigate("/assets")}>返回{brandName}素材库</Button>}
|
||||
/>
|
||||
<section className="mt-4 library-card-surface flex min-h-72 flex-col items-center justify-center rounded-[var(--r-xl)] px-6 py-10 text-center">
|
||||
<span className="grid size-14 place-items-center rounded-[var(--r-lg)] bg-[var(--workspace-accent-soft)] text-[var(--workspace-accent)]"><FolderOpen className="size-7" aria-hidden="true" /></span>
|
||||
<h2 className="mt-4 text-base font-semibold">先启用 Eagle 素材来源</h2>
|
||||
<p className="mt-2 max-w-md text-sm leading-6 text-foreground/55">启用后,这里会直接显示 Eagle 原本的文件夹和文件,不会把素材复制成影策本地素材。</p>
|
||||
<p className="mt-2 max-w-md text-sm leading-6 text-foreground/55">启用后,这里会直接显示 Eagle 原本的文件夹和文件,不会把素材复制成{brandName}本地素材。</p>
|
||||
<Button type="primary" className="mt-5" icon={<Settings2 className="size-4" />} onClick={() => navigate("/plugins")}>去插件中心启用</Button>
|
||||
</section>
|
||||
</WorkspacePage>
|
||||
@@ -152,13 +154,13 @@ export default function EagleLibraryPage() {
|
||||
<div className="studio-band">
|
||||
<PageHeader
|
||||
title="Eagle 素材库"
|
||||
description="Eagle 是影策的外部素材来源;这里复用影策素材库的浏览方式,直接读取和写入 Eagle 原始文件。"
|
||||
description={`Eagle 是${brandName}的外部素材来源;这里复用${brandName}素材库的浏览方式,直接读取和写入 Eagle 原始文件。`}
|
||||
meta={<span className="app-projects-header-meta assets-header-meta">{error ? "Eagle · 连接异常" : "Eagle · 已连接"}</span>}
|
||||
actions={(
|
||||
<div className="assets-header-actions">
|
||||
<div className="assets-header-action-buttons">
|
||||
<Button className="library-primary-action" type="primary" icon={<Upload className="size-3.5" />} onClick={() => fileInputRef.current?.click()} disabled={working}>写入素材</Button>
|
||||
<Button icon={<ArrowLeft className="size-3.5" />} onClick={() => navigate("/assets")}>影策素材库</Button>
|
||||
<Button icon={<ArrowLeft className="size-3.5" />} onClick={() => navigate("/assets")}>{brandName}素材库</Button>
|
||||
<Button icon={<Settings2 className="size-3.5" />} onClick={() => navigate("/plugins")}>插件设置</Button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -210,7 +212,7 @@ export default function EagleLibraryPage() {
|
||||
<h2 className="text-base font-semibold">{currentFolder?.name || "全部素材"}</h2>
|
||||
<span className="app-projects-header-meta">{items.length} 个素材</span>
|
||||
</div>
|
||||
<p className="mt-1 text-xs text-foreground/48">{currentFolder ? "当前文件夹由 Eagle 管理,影策只负责展示和调用。" : "当前展示 Eagle 素材库中的全部文件。"}</p>
|
||||
<p className="mt-1 text-xs text-foreground/48">{currentFolder ? `当前文件夹由 Eagle 管理,${brandName}只负责展示和调用。` : "当前展示 Eagle 素材库中的全部文件。"}</p>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<Button icon={<FolderPlus className="size-3.5" />} onClick={() => setFolderName((value) => value ? "" : "新文件夹")}>新建文件夹</Button>
|
||||
@@ -348,4 +350,4 @@ function externalFolderPath(folders: ExternalAssetFolder[], folderId: string) {
|
||||
current = current.parentId ? byId.get(current.parentId) : undefined;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ import { ART_CRITIQUE_PLUGIN_ID } from "@/lib/art-critique/contracts";
|
||||
import type { PluginManifest, RegisteredPlugin } from "@/lib/plugins/plugin-types";
|
||||
import { getEagleLibrary, type EagleFolder } from "@/services/api/eagle";
|
||||
import { fetchPlugins, setUserPluginEnabled, type BackendPlugin, type PluginState } from "@/services/api/plugins";
|
||||
import { useAppearanceStore } from "@/stores/use-appearance-store";
|
||||
import { usePluginStore } from "@/stores/use-plugin-store";
|
||||
import { useUserStore } from "@/stores/use-user-store";
|
||||
|
||||
@@ -64,6 +65,7 @@ const protocolSectionMeta = [
|
||||
export default function PluginsPage() {
|
||||
const { message } = App.useApp();
|
||||
const navigate = useNavigate();
|
||||
const brandName = useAppearanceStore((state) => state.appearance.brandName);
|
||||
const user = useUserStore((state) => state.user);
|
||||
const features = useUserStore((state) => state.features);
|
||||
const installations = usePluginStore((state) => state.installations);
|
||||
@@ -516,14 +518,14 @@ export default function PluginsPage() {
|
||||
<div className="min-w-0">
|
||||
<label htmlFor="eagle-base-url">Eagle 本地 API 地址</label>
|
||||
<Input id="eagle-base-url" aria-label="Eagle 本地 API 地址" value={eagleBaseUrl} onChange={(event) => setEagleBaseUrl(event.target.value)} placeholder="http://localhost:41595" />
|
||||
<p>Eagle 必须在本机运行;影策通过插件直接读取和写入 Eagle 原始文件。</p>
|
||||
<p>Eagle 必须在本机运行;{brandName}通过插件直接读取和写入 Eagle 原始文件。</p>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="plugin-setting-label-row">
|
||||
<label htmlFor="eagle-auto-upload-generated">自动归档生成结果</label>
|
||||
<Switch id="eagle-auto-upload-generated" checked={eagleAutoUploadGenerated} onChange={setEagleAutoUploadGenerated} aria-label="自动归档生成结果到 Eagle" />
|
||||
</div>
|
||||
<p>图片、视频和音频生成成功后,自动写入 Eagle;影策本地素材仍会保留。</p>
|
||||
<p>图片、视频和音频生成成功后,自动写入 Eagle;{brandName}本地素材仍会保留。</p>
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<div className="plugin-setting-label-row">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# 上传统一插件包
|
||||
|
||||
影策只有一种插件:一个版本化 Manifest 描述一个插件包可以向宿主贡献的全部能力。Provider、工作流、画布节点、媒体转换、素材源、Agent 和命令都写在同一个 `contributes` 下;运行时可以不同,但不会再出现“协议插件”和“UI 插件”两套清单。
|
||||
宿主只有一种插件:一个版本化 Manifest 描述一个插件包可以向宿主贡献的全部能力。Provider、工作流、画布节点、媒体转换、素材源、Agent 和命令都写在同一个 `contributes` 下;运行时可以不同,但不会再出现“协议插件”和“UI 插件”两套清单。
|
||||
|
||||
## 1. 包格式
|
||||
|
||||
@@ -65,9 +65,9 @@ my-plugin.yingce-plugin
|
||||
|
||||
## 4. Provider 与工作流
|
||||
|
||||
请求字段右侧只能引用影策统一请求,例如 `request.prompt`、`request.model`、`request.duration`、`request.extra`。声明式字段也支持安全的对象/数组路径(例如 `request.images.0.url`)和通用字符串变换(`|trim`、`|lower`、`|upper`),不执行插件代码。渠道声明的枚举值由画布完整保留,插件可使用通用大小写变换适配上游,不应要求宿主增加渠道专用归一化。字段解析为空时会省略该字段,适合映射可选参考素材。Provider 如果把 `requiresPublicMediaUrls` 设为 `true`,宿主会在请求前把用户资源转换为短期签名公网 URL。响应映射支持对象路径和数组下标,并可用 `errorPaths` 声明上游错误码路径。同步 provider 只需要 `create` 和 `response`;异步 provider 再声明 `poll`。
|
||||
请求字段右侧只能引用宿主统一请求,例如 `request.prompt`、`request.model`、`request.duration`、`request.extra`。声明式字段也支持安全的对象/数组路径(例如 `request.images.0.url`)和通用字符串变换(`|trim`、`|lower`、`|upper`),不执行插件代码。渠道声明的枚举值由画布完整保留,插件可使用通用大小写变换适配上游,不应要求宿主增加渠道专用归一化。字段解析为空时会省略该字段,适合映射可选参考素材。Provider 如果把 `requiresPublicMediaUrls` 设为 `true`,宿主会在请求前把用户资源转换为短期签名公网 URL。响应映射支持对象路径和数组下标,并可用 `errorPaths` 声明上游错误码路径。同步 provider 只需要 `create` 和 `response`;异步 provider 再声明 `poll`。
|
||||
|
||||
结果 URL 如果是短期地址,必须设置 `resultEphemeral: true`。宿主会在任务完成后立即下载并保存资源,画布和任务记录只保存影策资源引用。
|
||||
结果 URL 如果是短期地址,必须设置 `resultEphemeral: true`。宿主会在任务完成后立即下载并保存资源,画布和任务记录只保存宿主资源引用。
|
||||
|
||||
## 5. 安全与运行边界
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ export function UploadPluginModal({ open, onClose, onUpload }: UploadPluginModal
|
||||
<span className="plugin-upload-panel-icon"><CloudUpload className="size-5" /></span>
|
||||
<div>
|
||||
<h2>安装插件包</h2>
|
||||
<p>选择统一影策插件包,安装后会立即进入插件中心。</p>
|
||||
<p>选择统一站点插件包,安装后会立即进入插件中心。</p>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user