feat: 增加水印
This commit is contained in:
@@ -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>
|
||||
);
|
||||
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user