feat: 增加水印

This commit is contained in:
R524809
2026-08-24 17:15:08 +08:00
parent 95cb93a160
commit 024777ed8a
13 changed files with 272 additions and 7 deletions
+50 -3
View File
@@ -137,7 +137,7 @@ const App: React.FC = () => {
const uploadSeqRef = useRef(0);
// 服务端
const [settings, setSettings] = useState<BackendSettings>({ baseUrl: 'http://127.0.0.1:3300', token: '' });
const [settings, setSettings] = useState<BackendSettings>({ baseUrl: 'http://127.0.0.1:3300', token: '', watermark: { enabled: false, type: 'image', text: 'xiongmaoyx', opacity: 30 } });
// 出图方案
const [platform, setPlatform] = useState<'ozon' | 'wb' | 'cn'>('ozon');
@@ -368,7 +368,9 @@ const App: React.FC = () => {
try {
const payload = buildGeneratePayload(
allImages, selectedKeys, editedTexts(),
{ style_set: styleSet, style_prompt: currentStylePrompt, requirements: requirements.trim() || null, plan: activePlan, platform, model },
{ style_set: styleSet, style_prompt: currentStylePrompt, requirements: requirements.trim() || null, plan: activePlan, platform, model,
// 水印开启才下发,关闭时不带(服务端按无水印处理)
watermark: settings.watermark.enabled ? settings.watermark : undefined },
);
const { suite_id } = await send<{ suite_id: string }>('generateSuite', {
baseUrl: settings.baseUrl, token: settings.token, payload,
@@ -603,8 +605,15 @@ const App: React.FC = () => {
else if (el.dataset.step === '2') { el.dataset.step = '3'; el.src = proxied(url); }
};
/** 更新水印子设置并落盘(弹窗里控件共用) */
const patchWatermark = (patch: Partial<BackendSettings['watermark']>) => {
const next = { ...settings, watermark: { ...settings.watermark, ...patch } };
setSettings(next);
saveSettings(next);
};
const settingsPopup = (
<div style={{ width: 260 }}>
<div style={{ width: 300 }}>
<Field label="后端地址">
<input
value={settings.baseUrl}
@@ -619,6 +628,44 @@ const App: React.FC = () => {
onBlur={() => saveSettings(settings)}
/>
</Field>
<div className="divider" style={{ margin: '10px 0' }} />
<label className="auto-chk" title="开启后,生成的图在落盘前由服务端合成水印(导出即带水印)">
<input
type="checkbox"
checked={settings.watermark.enabled}
onChange={(e) => patchWatermark({ enabled: e.target.checked })}
/>
</label>
{settings.watermark.enabled && (
<>
<div className="field" style={{ marginTop: 8 }}>
<label></label>
<div className="pills">
<span className={`pill ${settings.watermark.type === 'image' ? 'on' : ''}`} onClick={() => patchWatermark({ type: 'image' })}></span>
<span className={`pill ${settings.watermark.type === 'text' ? 'on' : ''}`} onClick={() => patchWatermark({ type: 'text' })}></span>
</div>
</div>
{settings.watermark.type === 'text' && (
<Field label="水印文字">
<input
value={settings.watermark.text}
onChange={(e) => setSettings({ ...settings, watermark: { ...settings.watermark, text: e.target.value } })}
onBlur={() => saveSettings(settings)}
/>
</Field>
)}
<Field label="不透明度(%">
<input
type="number" min={1} max={100} step={5}
value={settings.watermark.opacity}
onChange={(e) => setSettings({ ...settings, watermark: { ...settings.watermark, opacity: Number(e.target.value) || 1 } })}
onBlur={() => saveSettings(settings)}
/>
</Field>
<div className="hint" style={{ marginTop: 4 }}></div>
</>
)}
</div>
);
+10 -1
View File
@@ -156,6 +156,14 @@ function authHeaders(token: string): Record<string, string> {
return token ? { Authorization: `Bearer ${token}` } : {};
}
/** 生成图水印选项(服务端在 AI 出图后合成;shape 对齐 server WatermarkOptions */
export interface WatermarkPayload {
enabled: boolean;
type: 'image' | 'text';
text: string;
opacity: number;
}
/** 无状态生成请求体:采集数据 + 勾选图片 + 出图方案,一次携带 */
export interface GeneratePayload {
texts: Array<{ kind: string; content: string; pairs?: Array<{ key: string; value: string }> | null }>;
@@ -166,6 +174,7 @@ export interface GeneratePayload {
plan: PlanItem[];
platform: string;
model?: string | null;
watermark?: WatermarkPayload;
}
/** 组装无状态生成请求:编辑后的文本 + 已勾选图片(含手动上传)+ 出图方案 */
@@ -173,7 +182,7 @@ export function buildGeneratePayload(
images: ImageMaterial[],
selectedKeys: Set<string>,
texts: GeneratePayload['texts'],
config: { style_set: number; style_prompt?: string; requirements?: string | null; plan: PlanItem[]; platform: string; model?: string | null },
config: { style_set: number; style_prompt?: string; requirements?: string | null; plan: PlanItem[]; platform: string; model?: string | null; watermark?: WatermarkPayload },
): GeneratePayload {
const selected = images
.filter((img) => selectedKeys.has(img.key))
+19 -2
View File
@@ -1,9 +1,19 @@
/**
* 服务端设置(上传/生成用):后端地址 + Bearer Token,持久化到 chrome.storage.local。
* 服务端设置(上传/生成用):后端地址 + Bearer Token + 水印选项,持久化到 chrome.storage.local。
*/
/** 生成图水印:服务端在 AI 出图后、落盘前合成(与生图模型无关)。默认复刻 ozonSeller。 */
export interface WatermarkSettings {
enabled: boolean;
type: 'image' | 'text';
text: string;
opacity: number; // 1-100%
}
export interface BackendSettings {
baseUrl: string;
token: string;
watermark: WatermarkSettings;
}
const KEY = 'suite_backend_settings';
@@ -13,6 +23,7 @@ export const DEFAULT_BASE_URL = 'http://127.0.0.1:3300';
const DEFAULT: BackendSettings = {
baseUrl: DEFAULT_BASE_URL,
token: '',
watermark: { enabled: false, type: 'image', text: 'xiongmaoyx', opacity: 30 },
};
/** 历史默认地址 → 当前默认地址(换端口后自动迁移用户已保存的设置) */
@@ -30,7 +41,13 @@ export async function loadSettings(): Promise<BackendSettings> {
const r = await chrome.storage.local.get(KEY);
const saved = r[KEY] ?? {};
const baseUrl = MIGRATE[saved.baseUrl] ?? saved.baseUrl ?? DEFAULT.baseUrl;
const s: BackendSettings = { token: '', ...saved, baseUrl };
// 水印子对象深合并:老版本存储里没有 watermark,避免整对象覆盖丢默认值
const s: BackendSettings = {
token: '',
...saved,
baseUrl,
watermark: { ...DEFAULT.watermark, ...(saved.watermark ?? {}) },
};
if (baseUrl !== saved.baseUrl) await chrome.storage.local.set({ [KEY]: s }); // 迁移结果写回
return s;
}