feat: AI套图分层方案 + Gemini集成 - 4种图案类型处理 + 正片叠底 + 宽高比 + 模型选择

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-02-07 16:59:56 +08:00
parent 12395d8eca
commit dae906aba7
277 changed files with 15009 additions and 19922 deletions

View File

@@ -13,7 +13,7 @@ const config: ICepConfig = {
"panels": [
{
"name": "AdminPanel-dev",
"displayName": "管理面板",
"displayName": "ps套版",
"main": "./index.html",
"width": 280,
"height": 600,

View File

@@ -7,6 +7,7 @@
</head>
<body>
<div id="app"></div>
<script src="/CSInterface.js"></script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@@ -78,6 +78,13 @@ export class CEP {
this.writeDebug()
this.copyJson2()
// 确保 CEP 扩展目录存在
const cepExtDir = getAdobeCepDir()
if (!fs.existsSync(cepExtDir)) {
fs.mkdirSync(cepExtDir, { recursive: true })
console.log('[CEP] 已创建 CEP 扩展目录:', cepExtDir)
}
// 创建符号链接或直接复制
if (!fs.existsSync(this.cepLink)) {
try {
@@ -85,8 +92,8 @@ export class CEP {
console.log('[CEP] 符号链接已创建')
} catch (error: any) {
// 权限不足时,改用复制
if (error.code === 'EPERM') {
console.warn('[CEP] 符号链接权限不足,改用复制方式')
if (error.code === 'EPERM' || error.code === 'ENOENT') {
console.warn('[CEP] 符号链接创建失败,改用复制方式')
this.copyToCepDir()
} else {
throw error
@@ -160,7 +167,7 @@ export class CEP {
}
// 3. 复制并修正 HTML 路径
const builtHtmlPath = path.join(this.dist, 'src/launcher/index.html')
const builtHtmlPath = path.join(this.dist, 'index.html')
const targetHtmlPath = path.join(this.cepOutput, 'index.html')
if (fs.existsSync(builtHtmlPath)) {

View File

@@ -0,0 +1,3 @@
## 目录说明
- cep 插件壳模板文件
- cep.config.json 配置文件

View File

@@ -0,0 +1,65 @@
import { ICepConfig } from "@plugins";
const config: ICepConfig = {
name: "cepName",
id: "com.cepName",
version: "1.0.0",
extensionVersion: "6.1.0",
requiredRuntimeVersion: "9.0",
type: "Panel",
parameters: [
"--enable-nodejs",
],
panels: [
{
name: "cepName",
displayName: "panelName",
main: "./index.html",
width: 400,
height: 300,
minWidth: 400,
minHeight: 300,
maxWidth: 4000,
maxHeight: 3000,
}
],
hosts:[
{
name: "AEFT",
version: "[0.0,99.9]",
},
{
name: "PPRO",
version: "[0.0,99.9]",
},
{
name: "ILST",
version: "[0.0,99.9]",
},
{
name: "PHXS",
version: "[0.0,99.9]",
},
{
name: "FLPR",
version: "[0.0,99.9]",
},
],
build: {
jsxBin: false,
/**国家 */
country: "CN",
/**省份 */
province: "GD",
/**公司名称 */
org: "你的公司名称",
/**签名密码 */
password: "",
tsa: "",
},
zxp: {
jsxBin: false
}
}
export default config;

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ExtensionManifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ExtensionBundleId="com.temp.id" ExtensionBundleVersion="1.0" Version="6.0"> <!-- MAJOR-VERSION-UPDATE-MARKER -->
<ExtensionList>
<Extension Id="com.temp.id" Version="6.1.0"/>
</ExtensionList>
<ExecutionEnvironment>
<HostList>
<Host Name="PHXS" Version="[11.0,99.9]"/>
<Host Name="ILST" Version="[11.0,99.9]"/>
</HostList>
<LocaleList>
<Locale Code="All"/>
</LocaleList>
<RequiredRuntimeList>
<RequiredRuntime Name="CSXS" Version="7.0"/> <!-- MAJOR-VERSION-UPDATE-MARKER -->
</RequiredRuntimeList>
</ExecutionEnvironment>
<DispatchInfoList>
</DispatchInfoList>
</ExtensionManifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 846 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{name}}</title>
<script>
window.location.href = "{{serverURL}}"
</script>
</head>
<body>
</body>
</html>

View File

@@ -0,0 +1,15 @@
export function joinDebug(id: string, hosts: { name: string }[]) {
let port = 7090
return `
<?xml version="1.0" encoding="UTF-8"?>
<ExtensionList>
<Extension Id="${id}">
<HostList>
${hosts
.map((host) => `<Host Name="${host.name}" Port="${port++}"/>`)
.join("\n")}
</HostList>
</Extension>
</ExtensionList>
`
}

View File

@@ -0,0 +1,15 @@
export function joinHtml(name:string,server:string){
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>${name}</title>
<script>
// 跳转到开发服务器地址
window.location.href = "${server}";
</script>
</head>
<body>
</body>
</html>`
}

View File

@@ -0,0 +1,66 @@
import { ICepConfig } from "../types";
export function joinManifest(config: ICepConfig) {
const { id, version, extensionVersion, requiredRuntimeVersion, hosts, parameters, panels } = config
const mainId=`${id}`
const panel = panels[0]
return `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<ExtensionManifest xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ExtensionBundleId="${id}.body" ExtensionBundleVersion="1.0" Version="6.0"> <!-- MAJOR-VERSION-UPDATE-MARKER -->
<ExtensionList>
<Extension Id="${mainId}" Version="${extensionVersion}"/>
</ExtensionList>
<ExecutionEnvironment>
<HostList>
${hosts.map(item => `<Host Name="${item.name}" Version="${item.version}"/>`).join("\n")}
</HostList>
<LocaleList>
<Locale Code="All"/>
</LocaleList>
<RequiredRuntimeList>
<RequiredRuntime Name="CSXS" Version="${requiredRuntimeVersion}"/> <!-- MAJOR-VERSION-UPDATE-MARKER -->
</RequiredRuntimeList>
</ExecutionEnvironment>
<DispatchInfoList>
<Extension Id="${mainId}">
<DispatchInfo>
<Resources>
<MainPath>./index.html</MainPath>
<!-- <ScriptPath>./jsx/core.jsx</ScriptPath> -->
<CEFCommandLine>
${parameters.map(item => `<Parameter>${item}</Parameter>`).join("\n")}
</CEFCommandLine>
</Resources>
<Lifecycle>
<AutoVisible>true</AutoVisible>
</Lifecycle>
<UI>
<Type>Panel</Type>
<Menu>${panel.displayName}</Menu>
<Geometry>
<Size>
<Height>${panel.height}</Height>
<Width>${panel.width}</Width>
</Size>
<MaxSize>
<Height>${panel.maxHeight||panel.height}</Height>
<Width>${panel.maxWidth||panel.width}</Width>
</MaxSize>
<MinSize>
<Height>${panel.minHeight||panel.height}</Height>
<Width>${panel.minWidth||panel.width}</Width>
</MinSize>
</Geometry>
<Icons>
<Icon Type="Normal">./img/highlight.png</Icon>
<Icon Type="RollOver">./img/dark.png</Icon>
<Icon Type="DarkNormal">./img/highlight.png</Icon>
<Icon Type="DarkRollOver">./img/dark.png</Icon>
</Icons>
</UI>
</DispatchInfo>
</Extension>
</DispatchInfoList>
</ExtensionManifest>
`
}

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

Before

Width:  |  Height:  |  Size: 1.5 KiB

View File

@@ -5,10 +5,7 @@
</template>
<script setup lang="ts">
import { useTheme } from '@/hooks/useTheme'
// 初始化主题
const { isDark } = useTheme()
// 主题初始化已在 main.ts 中完成
</script>
<style>

View File

@@ -1,5 +1,4 @@
import { isNodeJSEnabled } from "./tool"
console.error('isNodeJSEnabled()'+isNodeJSEnabled());
/**
* cep_node 为ps内置的全局变量

View File

@@ -107,8 +107,31 @@ export const updateTheme = () => {
const isLight = brightness > 128;
applyTheme(bgColorHex, isLight, skinInfo.baseFontSize);
// 发送主题给 iframe
const iframe = document.querySelector('iframe');
if (iframe && iframe.contentWindow) {
const message: PSThemeMessage = {
type: 'PS_THEME',
theme: {
bgColor: bgColorHex,
isLight: isLight,
fontSize: skinInfo.baseFontSize
}
};
iframe.contentWindow.postMessage(message, '*');
}
};
/**
* 处理来自子 iframe 的请求
*/
function handleChildMessage(event: MessageEvent) {
if (event.data && event.data.type === 'REQUEST_THEME') {
updateTheme();
}
}
/**
* 处理来自父窗口的主题消息iframe 模式)
*/
@@ -138,6 +161,8 @@ export const initThemeListener = () => {
logger.log('[Theme] CEP 模式 - 直接监听主题变化');
updateTheme();
cep.addEventListener('com.adobe.csxs.events.ThemeColorChanged', updateTheme);
// 监听来自 iframe 的请求
window.addEventListener('message', handleChildMessage);
} else {
// 纯浏览器模式
logger.log('[Theme] 浏览器模式 - 使用默认主题');

View File

@@ -1,26 +1,13 @@
<template>
<div class="iframe-container">
<!-- 顶部工具栏 -->
<div class="toolbar">
<a-button size="small" @click="goBack">
<template #icon><icon-arrow-left /></template>
返回
</a-button>
<span class="url-display">{{ currentUrl }}</span>
<a-button size="small" @click="reload">
<template #icon><icon-refresh /></template>
刷新
</a-button>
<a-button size="small" @click="openExternal">
<template #icon><icon-export /></template>
外部打开
</a-button>
</div>
<!-- 顶部工具栏 (已移除) -->
<!-- 加载指示器 -->
<div v-if="loading" class="loading-overlay">
<a-spin size="32" />
<p>加载中...</p>
<p style="font-size:12px;color:#999;margin-top:10px;">目标地址: {{ currentUrl }}</p>
<p style="font-size:12px;color:#999;">开发环境: {{ isDev ? '是' : '否' }}</p>
</div>
<!-- iframe -->
@@ -36,39 +23,21 @@
<script setup lang="ts">
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { IconArrowLeft, IconRefresh, IconExport } from '@arco-design/web-vue/es/icon'
import { updateTheme } from '@/utils/theme'
const router = useRouter()
const frameRef = ref<HTMLIFrameElement | null>(null)
const loading = ref(true)
const isDev = ref(typeof __DEV__ !== 'undefined' ? __DEV__ : false)
const currentUrl = ref(
localStorage.getItem('adminPanelUrl') || (
__DEV__
? 'http://localhost:5173'
: 'https://app.aidg168.uk'
)
localStorage.getItem('adminPanelUrl') || 'http://localhost:5173'
)
function goBack() {
router.push('/')
}
function reload() {
loading.value = true
if (frameRef.value) {
frameRef.value.src = currentUrl.value
}
}
function openExternal() {
window.open(currentUrl.value, '_blank')
}
function onFrameLoad() {
loading.value = false
console.log('✅ iframe 加载完成')
// iframe 加载完成后立即同步主题
updateTheme()
}
function onFrameError(e: Event) {
@@ -94,24 +63,6 @@ onMounted(() => {
background: var(--ps-bg, #1e1e1e);
}
.toolbar {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
background: var(--color-bg-2, #2d2d2d);
border-bottom: 1px solid var(--ps-border, #444);
}
.url-display {
flex: 1;
font-size: 12px;
color: var(--ps-text, #aaa);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.main-frame {
flex: 1;
width: 100%;

View File

@@ -19,7 +19,7 @@ export default defineConfig(({ command }) => {
legacy({
targets: ['chrome 41', 'not IE 11']
}),
!isBuild && cepPlugin(), // 开发时自动复制到 PS 扩展目录
cepPlugin(), // 开发时自动复制到 PS 扩展目录,构建时生成 CEP 结构
vue(),
tsconfigPaths(),
],