@w3kits-com/plugin-dreamifly 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/__w3kits/icon.svg +8 -0
- package/dist/config.js +166 -0
- package/dist/index.html +13 -0
- package/dist/main.js +401 -0
- package/dist/runtime.js +319 -0
- package/dist/styles.css +280 -0
- package/package.json +30 -0
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# W3Kits Dreamifly Plugin
|
|
2
|
+
|
|
3
|
+
Thin W3Kits plugin package extracted from the Dreamifly creative surface.
|
|
4
|
+
|
|
5
|
+
V1 scope:
|
|
6
|
+
|
|
7
|
+
- create image drafts
|
|
8
|
+
- repair / upscale workflows
|
|
9
|
+
- shared W3Kits locale bootstrap
|
|
10
|
+
- W3Kits runtime-session AI calls
|
|
11
|
+
- draft/history state in plugin storage
|
|
12
|
+
- reference and output files persisted into the shared desktop VFS
|
|
13
|
+
|
|
14
|
+
Out of scope:
|
|
15
|
+
|
|
16
|
+
- hosted Dreamifly auth, billing, subscriptions, community, admin, and OSS flows
|
|
17
|
+
- Dreamifly Postgres / Drizzle backend
|
|
18
|
+
- direct provider ownership inside the plugin
|
|
19
|
+
|
|
20
|
+
Build:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
node scripts/prepare-dist.mjs
|
|
24
|
+
```
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
2
|
+
<rect x="36" y="28" width="184" height="200" rx="44" fill="#141319"/>
|
|
3
|
+
<path d="M75 84C75 63.0132 92.0132 46 113 46H160C173.255 46 184 56.7452 184 70V154C184 174.987 166.987 192 146 192H99C85.7452 192 75 181.255 75 168V84Z" fill="#F6F2EA"/>
|
|
4
|
+
<path d="M122.749 74.2C140.966 74.2 155.965 88.0515 157.771 105.797L157.943 109.194C157.943 127.411 144.091 142.41 126.347 144.216L122.749 144.388H116.581V169.2H98.6652V74.2H122.749ZM122.749 90.8848H116.581V127.703H122.749C132.528 127.703 139.972 120.259 139.972 110.479C139.972 100.956 132.913 93.6655 123.491 90.9978L122.749 90.8848Z" fill="#141319"/>
|
|
5
|
+
<path d="M151.48 124.23C169.143 124.23 183.461 138.548 183.461 156.211C183.461 173.874 169.143 188.192 151.48 188.192C133.817 188.192 119.499 173.874 119.499 156.211C119.499 138.548 133.817 124.23 151.48 124.23Z" fill="#8D7BFF"/>
|
|
6
|
+
<path d="M149.776 141.851L162.385 154.46L149.776 167.069" stroke="#F6F2EA" stroke-width="11" stroke-linecap="round" stroke-linejoin="round"/>
|
|
7
|
+
<path d="M138.274 154.46H162.385" stroke="#F6F2EA" stroke-width="11" stroke-linecap="round"/>
|
|
8
|
+
</svg>
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
export const DESKTOP_ROOT = "/.w3kits/desktop/files/Dreamifly";
|
|
2
|
+
export const REFERENCE_DIR = `${DESKTOP_ROOT}/references`;
|
|
3
|
+
export const OUTPUT_DIR = `${DESKTOP_ROOT}/outputs`;
|
|
4
|
+
export const STATE_DRAFT_PATH = "state/draft.json";
|
|
5
|
+
export const STATE_HISTORY_PATH = "state/history.json";
|
|
6
|
+
|
|
7
|
+
export const IMAGE_MODELS = [
|
|
8
|
+
{
|
|
9
|
+
id: "gpt-image-2",
|
|
10
|
+
name: "GPT Image 2",
|
|
11
|
+
description: {
|
|
12
|
+
en: "OpenAI image generation and single-image editing.",
|
|
13
|
+
"zh-CN": "OpenAI 图像生成与单图编辑。",
|
|
14
|
+
},
|
|
15
|
+
supportsReference: true,
|
|
16
|
+
supportsGeneration: true,
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
id: "dall-e-3",
|
|
20
|
+
name: "DALL-E 3",
|
|
21
|
+
description: {
|
|
22
|
+
en: "OpenAI text-to-image generation.",
|
|
23
|
+
"zh-CN": "OpenAI 文生图模型。",
|
|
24
|
+
},
|
|
25
|
+
supportsReference: false,
|
|
26
|
+
supportsGeneration: true,
|
|
27
|
+
},
|
|
28
|
+
];
|
|
29
|
+
|
|
30
|
+
export const WORKFLOWS = [
|
|
31
|
+
{
|
|
32
|
+
id: "create",
|
|
33
|
+
title: {
|
|
34
|
+
en: "Create",
|
|
35
|
+
"zh-CN": "创作",
|
|
36
|
+
},
|
|
37
|
+
description: {
|
|
38
|
+
en: "Prompt-driven image generation.",
|
|
39
|
+
"zh-CN": "基于提示词的图像生成。",
|
|
40
|
+
},
|
|
41
|
+
requiresReference: false,
|
|
42
|
+
promptPrefix: "",
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
id: "repair",
|
|
46
|
+
title: {
|
|
47
|
+
en: "Repair",
|
|
48
|
+
"zh-CN": "修复",
|
|
49
|
+
},
|
|
50
|
+
description: {
|
|
51
|
+
en: "Repair the uploaded image while preserving the original composition.",
|
|
52
|
+
"zh-CN": "修复上传图片并尽量保留原始构图。",
|
|
53
|
+
},
|
|
54
|
+
requiresReference: true,
|
|
55
|
+
promptPrefix: {
|
|
56
|
+
en: "Repair and restore the uploaded image.",
|
|
57
|
+
"zh-CN": "修复并还原上传的图片。",
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
id: "upscale",
|
|
62
|
+
title: {
|
|
63
|
+
en: "Upscale",
|
|
64
|
+
"zh-CN": "放大",
|
|
65
|
+
},
|
|
66
|
+
description: {
|
|
67
|
+
en: "Enhance detail and clarity for the uploaded image.",
|
|
68
|
+
"zh-CN": "增强上传图片的细节与清晰度。",
|
|
69
|
+
},
|
|
70
|
+
requiresReference: true,
|
|
71
|
+
promptPrefix: {
|
|
72
|
+
en: "Upscale the uploaded image with cleaner detail and improved sharpness.",
|
|
73
|
+
"zh-CN": "放大上传图片并提升细节和锐度。",
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
];
|
|
77
|
+
|
|
78
|
+
export const SIZE_OPTIONS = [
|
|
79
|
+
{ id: "square", label: "1:1", width: 1024, height: 1024 },
|
|
80
|
+
{ id: "landscape", label: "16:9", width: 1536, height: 1024 },
|
|
81
|
+
{ id: "portrait", label: "9:16", width: 1024, height: 1536 },
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
export const VIDEO_MODELS = [
|
|
85
|
+
{
|
|
86
|
+
id: "grok-video",
|
|
87
|
+
name: "Grok Video",
|
|
88
|
+
ready: false,
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
id: "veo",
|
|
92
|
+
name: "Veo",
|
|
93
|
+
ready: false,
|
|
94
|
+
},
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
export const COPY = {
|
|
98
|
+
en: {
|
|
99
|
+
appTitle: "Dreamifly",
|
|
100
|
+
subtitle: "W3Kits creative create/workflows surface",
|
|
101
|
+
workflowTitle: "Workflow",
|
|
102
|
+
modelTitle: "Image model",
|
|
103
|
+
promptTitle: "Prompt",
|
|
104
|
+
promptPlaceholder: "Describe the image you want to create or how to transform the reference.",
|
|
105
|
+
generate: "Generate",
|
|
106
|
+
generating: "Generating...",
|
|
107
|
+
referenceTitle: "Reference image",
|
|
108
|
+
referenceHint: "Stored in shared /home/agent VFS.",
|
|
109
|
+
clearReference: "Clear",
|
|
110
|
+
draftTitle: "Draft state",
|
|
111
|
+
draftSaved: "Drafts and history sync through W3Kits plugin storage.",
|
|
112
|
+
outputsTitle: "Recent outputs",
|
|
113
|
+
noOutputs: "No outputs yet.",
|
|
114
|
+
outputPath: "Saved path",
|
|
115
|
+
outputModel: "Model",
|
|
116
|
+
outputWorkflow: "Workflow",
|
|
117
|
+
statusReady: "Ready.",
|
|
118
|
+
statusLogin: "Login required.",
|
|
119
|
+
openLogin: "Open login",
|
|
120
|
+
missingPrompt: "Prompt is required.",
|
|
121
|
+
missingReference: "This workflow requires a reference image.",
|
|
122
|
+
videoTitle: "Reserved video models",
|
|
123
|
+
videoHint: "Reserved for the broader W3Kits video-provider contract.",
|
|
124
|
+
videoReserved: "Reserved provider",
|
|
125
|
+
openFolder: "Outputs write into /home/agent/Dreamifly/outputs.",
|
|
126
|
+
},
|
|
127
|
+
"zh-CN": {
|
|
128
|
+
appTitle: "Dreamifly",
|
|
129
|
+
subtitle: "W3Kits 创作 / 工作流切片",
|
|
130
|
+
workflowTitle: "工作流",
|
|
131
|
+
modelTitle: "图像模型",
|
|
132
|
+
promptTitle: "提示词",
|
|
133
|
+
promptPlaceholder: "描述你想生成的图像,或说明如何处理参考图。",
|
|
134
|
+
generate: "开始生成",
|
|
135
|
+
generating: "生成中...",
|
|
136
|
+
referenceTitle: "参考图",
|
|
137
|
+
referenceHint: "文件会保存到共享 /home/agent VFS。",
|
|
138
|
+
clearReference: "清除",
|
|
139
|
+
draftTitle: "草稿状态",
|
|
140
|
+
draftSaved: "草稿和历史记录通过 W3Kits 插件存储同步。",
|
|
141
|
+
outputsTitle: "最近输出",
|
|
142
|
+
noOutputs: "暂无输出。",
|
|
143
|
+
outputPath: "保存路径",
|
|
144
|
+
outputModel: "模型",
|
|
145
|
+
outputWorkflow: "工作流",
|
|
146
|
+
statusReady: "已就绪。",
|
|
147
|
+
statusLogin: "需要登录。",
|
|
148
|
+
openLogin: "打开登录",
|
|
149
|
+
missingPrompt: "请输入提示词。",
|
|
150
|
+
missingReference: "当前工作流需要参考图。",
|
|
151
|
+
videoTitle: "预留视频模型",
|
|
152
|
+
videoHint: "为更广义的 W3Kits 视频提供商协议预留。",
|
|
153
|
+
videoReserved: "预留提供商",
|
|
154
|
+
openFolder: "输出会写入 /home/agent/Dreamifly/outputs。",
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
export function localized(locale, value) {
|
|
159
|
+
if (typeof value === "string") return value;
|
|
160
|
+
if (value && typeof value === "object") return value[locale] || value.en || "";
|
|
161
|
+
return "";
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
export function t(locale, key) {
|
|
165
|
+
return COPY[locale]?.[key] || COPY.en[key] || key;
|
|
166
|
+
}
|
package/dist/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>Dreamifly</title>
|
|
7
|
+
<link rel="stylesheet" href="./styles.css" />
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="app"></div>
|
|
11
|
+
<script type="module" src="./main.js"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
package/dist/main.js
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DESKTOP_ROOT,
|
|
3
|
+
IMAGE_MODELS,
|
|
4
|
+
OUTPUT_DIR,
|
|
5
|
+
REFERENCE_DIR,
|
|
6
|
+
SIZE_OPTIONS,
|
|
7
|
+
STATE_DRAFT_PATH,
|
|
8
|
+
STATE_HISTORY_PATH,
|
|
9
|
+
VIDEO_MODELS,
|
|
10
|
+
WORKFLOWS,
|
|
11
|
+
localized,
|
|
12
|
+
t,
|
|
13
|
+
} from "./config.js";
|
|
14
|
+
import {
|
|
15
|
+
bootstrapLocale,
|
|
16
|
+
desktopMkdir,
|
|
17
|
+
desktopReadDataUrl,
|
|
18
|
+
desktopWriteDataUrl,
|
|
19
|
+
generateImage,
|
|
20
|
+
readPluginJson,
|
|
21
|
+
requestLogin,
|
|
22
|
+
setWindowTitle,
|
|
23
|
+
syncPluginStorage,
|
|
24
|
+
visibleDesktopPath,
|
|
25
|
+
writePluginJson,
|
|
26
|
+
} from "./runtime.js";
|
|
27
|
+
|
|
28
|
+
const locale = bootstrapLocale();
|
|
29
|
+
setWindowTitle(`Dreamifly - ${localized(locale, WORKFLOWS[0].title)}`);
|
|
30
|
+
|
|
31
|
+
const defaultState = {
|
|
32
|
+
locale,
|
|
33
|
+
workflowId: "create",
|
|
34
|
+
modelId: "gpt-image-2",
|
|
35
|
+
sizeId: "square",
|
|
36
|
+
prompt: "",
|
|
37
|
+
busy: false,
|
|
38
|
+
status: t(locale, "statusReady"),
|
|
39
|
+
reference: null,
|
|
40
|
+
history: [],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const state = { ...defaultState };
|
|
44
|
+
const app = document.querySelector("#app");
|
|
45
|
+
|
|
46
|
+
void bootstrap();
|
|
47
|
+
|
|
48
|
+
async function bootstrap() {
|
|
49
|
+
try {
|
|
50
|
+
await Promise.all([
|
|
51
|
+
desktopMkdir(DESKTOP_ROOT),
|
|
52
|
+
desktopMkdir(REFERENCE_DIR),
|
|
53
|
+
desktopMkdir(OUTPUT_DIR),
|
|
54
|
+
]);
|
|
55
|
+
} catch (error) {
|
|
56
|
+
state.status = error instanceof Error ? error.message : String(error);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const storedDraft = await readPluginJson(STATE_DRAFT_PATH, null);
|
|
60
|
+
const storedHistory = await readPluginJson(STATE_HISTORY_PATH, []);
|
|
61
|
+
if (storedDraft && typeof storedDraft === "object") {
|
|
62
|
+
Object.assign(state, sanitizeDraft(storedDraft));
|
|
63
|
+
}
|
|
64
|
+
if (Array.isArray(storedHistory)) {
|
|
65
|
+
state.history = storedHistory.filter(isHistoryRecord).slice(0, 12);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (state.reference?.path) {
|
|
69
|
+
try {
|
|
70
|
+
const dataUrl = await desktopReadDataUrl(state.reference.path);
|
|
71
|
+
if (dataUrl) state.reference.dataUrl = dataUrl;
|
|
72
|
+
} catch {
|
|
73
|
+
state.reference = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
render();
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function sanitizeDraft(value) {
|
|
81
|
+
return {
|
|
82
|
+
workflowId: typeof value.workflowId === "string" ? value.workflowId : defaultState.workflowId,
|
|
83
|
+
modelId: typeof value.modelId === "string" ? value.modelId : defaultState.modelId,
|
|
84
|
+
sizeId: typeof value.sizeId === "string" ? value.sizeId : defaultState.sizeId,
|
|
85
|
+
prompt: typeof value.prompt === "string" ? value.prompt : "",
|
|
86
|
+
reference: sanitizeReference(value.reference),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function sanitizeReference(value) {
|
|
91
|
+
if (!value || typeof value !== "object") return null;
|
|
92
|
+
if (typeof value.path !== "string" || typeof value.name !== "string") return null;
|
|
93
|
+
return {
|
|
94
|
+
path: value.path,
|
|
95
|
+
name: value.name,
|
|
96
|
+
contentType: typeof value.contentType === "string" ? value.contentType : "image/png",
|
|
97
|
+
dataUrl: typeof value.dataUrl === "string" ? value.dataUrl : undefined,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function isHistoryRecord(value) {
|
|
102
|
+
return value && typeof value === "object" && typeof value.id === "string" && typeof value.path === "string";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function currentWorkflow() {
|
|
106
|
+
return WORKFLOWS.find((item) => item.id === state.workflowId) || WORKFLOWS[0];
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function currentModel() {
|
|
110
|
+
return IMAGE_MODELS.find((item) => item.id === state.modelId) || IMAGE_MODELS[0];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function currentSize() {
|
|
114
|
+
return SIZE_OPTIONS.find((item) => item.id === state.sizeId) || SIZE_OPTIONS[0];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function escapeHtml(value) {
|
|
118
|
+
return String(value || "")
|
|
119
|
+
.replaceAll("&", "&")
|
|
120
|
+
.replaceAll("<", "<")
|
|
121
|
+
.replaceAll(">", ">")
|
|
122
|
+
.replaceAll('"', """);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function render() {
|
|
126
|
+
const workflow = currentWorkflow();
|
|
127
|
+
const model = currentModel();
|
|
128
|
+
const promptMissing = !state.prompt.trim();
|
|
129
|
+
const referenceMissing = workflow.requiresReference && !state.reference?.dataUrl;
|
|
130
|
+
const disabled = state.busy || promptMissing || referenceMissing;
|
|
131
|
+
|
|
132
|
+
app.innerHTML = `
|
|
133
|
+
<main class="shell">
|
|
134
|
+
<section class="hero">
|
|
135
|
+
<div>
|
|
136
|
+
<h1>${escapeHtml(t(locale, "appTitle"))}</h1>
|
|
137
|
+
<p>${escapeHtml(t(locale, "subtitle"))}</p>
|
|
138
|
+
</div>
|
|
139
|
+
<div class="status-pill">${escapeHtml(state.status)}</div>
|
|
140
|
+
</section>
|
|
141
|
+
<section class="grid">
|
|
142
|
+
<div class="panel stack">
|
|
143
|
+
<div class="panel-head">
|
|
144
|
+
<h2>${escapeHtml(t(locale, "workflowTitle"))}</h2>
|
|
145
|
+
<span>${escapeHtml(t(locale, "openFolder"))}</span>
|
|
146
|
+
</div>
|
|
147
|
+
<div class="workflow-grid">
|
|
148
|
+
${WORKFLOWS.map((item) => `
|
|
149
|
+
<button class="workflow-card${item.id === workflow.id ? " active" : ""}" data-workflow="${escapeHtml(item.id)}">
|
|
150
|
+
<strong>${escapeHtml(localized(locale, item.title))}</strong>
|
|
151
|
+
<span>${escapeHtml(localized(locale, item.description))}</span>
|
|
152
|
+
</button>
|
|
153
|
+
`).join("")}
|
|
154
|
+
</div>
|
|
155
|
+
|
|
156
|
+
<label class="field">
|
|
157
|
+
<span>${escapeHtml(t(locale, "modelTitle"))}</span>
|
|
158
|
+
<select id="model">
|
|
159
|
+
${IMAGE_MODELS.map((item) => `
|
|
160
|
+
<option value="${escapeHtml(item.id)}" ${item.id === model.id ? "selected" : ""}>
|
|
161
|
+
${escapeHtml(item.name)}
|
|
162
|
+
</option>
|
|
163
|
+
`).join("")}
|
|
164
|
+
</select>
|
|
165
|
+
<small>${escapeHtml(localized(locale, model.description))}</small>
|
|
166
|
+
</label>
|
|
167
|
+
|
|
168
|
+
<div class="field-row">
|
|
169
|
+
<label class="field">
|
|
170
|
+
<span>Aspect</span>
|
|
171
|
+
<select id="size">
|
|
172
|
+
${SIZE_OPTIONS.map((item) => `
|
|
173
|
+
<option value="${escapeHtml(item.id)}" ${item.id === state.sizeId ? "selected" : ""}>
|
|
174
|
+
${escapeHtml(item.label)} · ${item.width}x${item.height}
|
|
175
|
+
</option>
|
|
176
|
+
`).join("")}
|
|
177
|
+
</select>
|
|
178
|
+
</label>
|
|
179
|
+
<label class="field">
|
|
180
|
+
<span>${escapeHtml(t(locale, "referenceTitle"))}</span>
|
|
181
|
+
<input id="reference" type="file" accept="image/*" />
|
|
182
|
+
<small>${escapeHtml(t(locale, "referenceHint"))}</small>
|
|
183
|
+
</label>
|
|
184
|
+
</div>
|
|
185
|
+
|
|
186
|
+
<label class="field">
|
|
187
|
+
<span>${escapeHtml(t(locale, "promptTitle"))}</span>
|
|
188
|
+
<textarea id="prompt" rows="8" placeholder="${escapeHtml(t(locale, "promptPlaceholder"))}">${escapeHtml(state.prompt)}</textarea>
|
|
189
|
+
</label>
|
|
190
|
+
|
|
191
|
+
<div class="action-row">
|
|
192
|
+
<button id="generate" class="primary" ${disabled ? "disabled" : ""}>
|
|
193
|
+
${escapeHtml(state.busy ? t(locale, "generating") : t(locale, "generate"))}
|
|
194
|
+
</button>
|
|
195
|
+
<button id="login" type="button">${escapeHtml(t(locale, "openLogin"))}</button>
|
|
196
|
+
<button id="clear-reference" type="button" ${state.reference ? "" : "disabled"}>${escapeHtml(t(locale, "clearReference"))}</button>
|
|
197
|
+
</div>
|
|
198
|
+
|
|
199
|
+
<div class="reference-card${state.reference ? " has-reference" : ""}">
|
|
200
|
+
${
|
|
201
|
+
state.reference?.dataUrl
|
|
202
|
+
? `<img src="${escapeHtml(state.reference.dataUrl)}" alt="${escapeHtml(state.reference.name)}" />
|
|
203
|
+
<div>
|
|
204
|
+
<strong>${escapeHtml(state.reference.name)}</strong>
|
|
205
|
+
<span>${escapeHtml(visibleDesktopPath(state.reference.path))}</span>
|
|
206
|
+
</div>`
|
|
207
|
+
: `<div>
|
|
208
|
+
<strong>${escapeHtml(t(locale, "draftTitle"))}</strong>
|
|
209
|
+
<span>${escapeHtml(t(locale, "draftSaved"))}</span>
|
|
210
|
+
</div>`
|
|
211
|
+
}
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
|
|
215
|
+
<div class="panel stack">
|
|
216
|
+
<div class="panel-head">
|
|
217
|
+
<h2>${escapeHtml(t(locale, "outputsTitle"))}</h2>
|
|
218
|
+
<span>${escapeHtml(t(locale, "videoTitle"))}</span>
|
|
219
|
+
</div>
|
|
220
|
+
<div class="video-strip">
|
|
221
|
+
${VIDEO_MODELS.map((item) => `
|
|
222
|
+
<div class="video-card">
|
|
223
|
+
<strong>${escapeHtml(item.name)}</strong>
|
|
224
|
+
<span>${escapeHtml(t(locale, "videoReserved"))}</span>
|
|
225
|
+
</div>
|
|
226
|
+
`).join("")}
|
|
227
|
+
</div>
|
|
228
|
+
<p class="video-hint">${escapeHtml(t(locale, "videoHint"))}</p>
|
|
229
|
+
<div class="history-list">
|
|
230
|
+
${
|
|
231
|
+
state.history.length
|
|
232
|
+
? state.history.map((item) => `
|
|
233
|
+
<article class="history-card">
|
|
234
|
+
<img src="${escapeHtml(item.previewDataUrl)}" alt="${escapeHtml(item.prompt)}" />
|
|
235
|
+
<div class="history-copy">
|
|
236
|
+
<strong>${escapeHtml(item.modelName)}</strong>
|
|
237
|
+
<span>${escapeHtml(item.workflowName)}</span>
|
|
238
|
+
<span>${escapeHtml(item.createdAt)}</span>
|
|
239
|
+
<span>${escapeHtml(t(locale, "outputPath"))}: ${escapeHtml(visibleDesktopPath(item.path))}</span>
|
|
240
|
+
</div>
|
|
241
|
+
</article>
|
|
242
|
+
`).join("")
|
|
243
|
+
: `<div class="empty">${escapeHtml(t(locale, "noOutputs"))}</div>`
|
|
244
|
+
}
|
|
245
|
+
</div>
|
|
246
|
+
</div>
|
|
247
|
+
</section>
|
|
248
|
+
</main>
|
|
249
|
+
`;
|
|
250
|
+
|
|
251
|
+
app.querySelectorAll("[data-workflow]").forEach((element) => {
|
|
252
|
+
element.addEventListener("click", async () => {
|
|
253
|
+
state.workflowId = element.getAttribute("data-workflow") || "create";
|
|
254
|
+
state.status = t(locale, "statusReady");
|
|
255
|
+
await persistDraft();
|
|
256
|
+
render();
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
app.querySelector("#model")?.addEventListener("change", async (event) => {
|
|
261
|
+
state.modelId = event.target.value;
|
|
262
|
+
await persistDraft();
|
|
263
|
+
render();
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
app.querySelector("#size")?.addEventListener("change", async (event) => {
|
|
267
|
+
state.sizeId = event.target.value;
|
|
268
|
+
await persistDraft();
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
app.querySelector("#prompt")?.addEventListener("input", async (event) => {
|
|
272
|
+
state.prompt = event.target.value;
|
|
273
|
+
await persistDraft();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
app.querySelector("#login")?.addEventListener("click", () => {
|
|
277
|
+
requestLogin("plugin_runtime");
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
app.querySelector("#clear-reference")?.addEventListener("click", async () => {
|
|
281
|
+
state.reference = null;
|
|
282
|
+
await persistDraft();
|
|
283
|
+
render();
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
app.querySelector("#reference")?.addEventListener("change", async (event) => {
|
|
287
|
+
const [file] = event.target.files || [];
|
|
288
|
+
if (!file) return;
|
|
289
|
+
const dataUrl = await fileToDataUrl(file);
|
|
290
|
+
const safeName = `${Date.now()}-${sanitizeFileName(file.name)}`;
|
|
291
|
+
const path = `${REFERENCE_DIR}/${safeName}`;
|
|
292
|
+
await desktopWriteDataUrl(path, dataUrl);
|
|
293
|
+
state.reference = {
|
|
294
|
+
path,
|
|
295
|
+
name: file.name,
|
|
296
|
+
contentType: file.type || "image/png",
|
|
297
|
+
dataUrl,
|
|
298
|
+
};
|
|
299
|
+
state.status = visibleDesktopPath(path);
|
|
300
|
+
await persistDraft();
|
|
301
|
+
render();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
app.querySelector("#generate")?.addEventListener("click", () => {
|
|
305
|
+
void handleGenerate();
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async function handleGenerate() {
|
|
310
|
+
const workflow = currentWorkflow();
|
|
311
|
+
const model = currentModel();
|
|
312
|
+
const size = currentSize();
|
|
313
|
+
const promptText = state.prompt.trim();
|
|
314
|
+
if (!promptText) {
|
|
315
|
+
state.status = t(locale, "missingPrompt");
|
|
316
|
+
render();
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (workflow.requiresReference && !state.reference?.dataUrl) {
|
|
320
|
+
state.status = t(locale, "missingReference");
|
|
321
|
+
render();
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const workflowPrefix = localized(locale, workflow.promptPrefix);
|
|
326
|
+
const finalPrompt = workflowPrefix ? `${workflowPrefix}\n\n${promptText}` : promptText;
|
|
327
|
+
|
|
328
|
+
state.busy = true;
|
|
329
|
+
state.status = t(locale, "generating");
|
|
330
|
+
render();
|
|
331
|
+
|
|
332
|
+
try {
|
|
333
|
+
const previewDataUrl = await generateImage({
|
|
334
|
+
model: model.id,
|
|
335
|
+
prompt: finalPrompt,
|
|
336
|
+
width: size.width,
|
|
337
|
+
height: size.height,
|
|
338
|
+
referenceDataUrl: state.reference?.dataUrl || null,
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
const fileName = `${Date.now()}-${sanitizeFileName(model.id)}.png`;
|
|
342
|
+
const path = `${OUTPUT_DIR}/${fileName}`;
|
|
343
|
+
await desktopWriteDataUrl(path, previewDataUrl);
|
|
344
|
+
|
|
345
|
+
const record = {
|
|
346
|
+
id: crypto.randomUUID(),
|
|
347
|
+
path,
|
|
348
|
+
previewDataUrl,
|
|
349
|
+
modelId: model.id,
|
|
350
|
+
modelName: model.name,
|
|
351
|
+
workflowId: workflow.id,
|
|
352
|
+
workflowName: localized(locale, workflow.title),
|
|
353
|
+
prompt: promptText,
|
|
354
|
+
createdAt: new Date().toISOString(),
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
state.history = [record, ...state.history].slice(0, 12);
|
|
358
|
+
state.status = visibleDesktopPath(path);
|
|
359
|
+
await Promise.all([persistDraft(), persistHistory()]);
|
|
360
|
+
} catch (error) {
|
|
361
|
+
state.status = error instanceof Error ? error.message : String(error);
|
|
362
|
+
} finally {
|
|
363
|
+
state.busy = false;
|
|
364
|
+
render();
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
async function persistDraft() {
|
|
369
|
+
await writePluginJson(STATE_DRAFT_PATH, {
|
|
370
|
+
workflowId: state.workflowId,
|
|
371
|
+
modelId: state.modelId,
|
|
372
|
+
sizeId: state.sizeId,
|
|
373
|
+
prompt: state.prompt,
|
|
374
|
+
reference: state.reference
|
|
375
|
+
? {
|
|
376
|
+
path: state.reference.path,
|
|
377
|
+
name: state.reference.name,
|
|
378
|
+
contentType: state.reference.contentType,
|
|
379
|
+
}
|
|
380
|
+
: null,
|
|
381
|
+
});
|
|
382
|
+
await syncPluginStorage();
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
async function persistHistory() {
|
|
386
|
+
await writePluginJson(STATE_HISTORY_PATH, state.history);
|
|
387
|
+
await syncPluginStorage();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function sanitizeFileName(value) {
|
|
391
|
+
return String(value || "asset").toLowerCase().replace(/[^a-z0-9._-]+/g, "-");
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
function fileToDataUrl(file) {
|
|
395
|
+
return new Promise((resolve, reject) => {
|
|
396
|
+
const reader = new FileReader();
|
|
397
|
+
reader.onload = () => resolve(String(reader.result || ""));
|
|
398
|
+
reader.onerror = () => reject(reader.error || new Error("file_read_failed"));
|
|
399
|
+
reader.readAsDataURL(file);
|
|
400
|
+
});
|
|
401
|
+
}
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
const W3KITS_PLUGIN_ID = "dreamifly";
|
|
2
|
+
const W3KITS_BRIDGE_VERSION = 1;
|
|
3
|
+
const W3KITS_RESPONSE = "W3KITS_RESPONSE";
|
|
4
|
+
const W3KITS_AUTH_REQUIRED = "W3KITS_AUTH_REQUIRED";
|
|
5
|
+
const W3KITS_RUNTIME_SESSION_REQUEST = "W3KITS_RUNTIME_SESSION_REQUEST";
|
|
6
|
+
const W3KITS_STORAGE_READ = "W3KITS_STORAGE_READ";
|
|
7
|
+
const W3KITS_STORAGE_WRITE = "W3KITS_STORAGE_WRITE";
|
|
8
|
+
const W3KITS_STORAGE_SYNC = "W3KITS_STORAGE_SYNC";
|
|
9
|
+
const W3KITS_DESKTOP_FS_READ = "W3KITS_DESKTOP_FS_READ";
|
|
10
|
+
const W3KITS_DESKTOP_FS_WRITE = "W3KITS_DESKTOP_FS_WRITE";
|
|
11
|
+
const W3KITS_DESKTOP_FS_MKDIR = "W3KITS_DESKTOP_FS_MKDIR";
|
|
12
|
+
const W3KITS_WINDOW_TITLE = "W3KITS_WINDOW_TITLE";
|
|
13
|
+
const LOCALE_STORAGE_KEY = "dreamifly.w3kits.locale";
|
|
14
|
+
|
|
15
|
+
let cachedRuntimeSession = null;
|
|
16
|
+
|
|
17
|
+
function queryParam(name) {
|
|
18
|
+
return new URL(window.location.href).searchParams.get(name);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function normalizeLocale(value) {
|
|
22
|
+
if (!value) return null;
|
|
23
|
+
const normalized = value.toLowerCase();
|
|
24
|
+
if (normalized.startsWith("zh")) return "zh-CN";
|
|
25
|
+
if (normalized.startsWith("en")) return "en";
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function getParentOrigin() {
|
|
30
|
+
const explicit = queryParam("w3kitsParentOrigin");
|
|
31
|
+
if (explicit) return explicit;
|
|
32
|
+
const fallback = queryParam("openaiBaseUrl") || queryParam("w3kitsOpenAiBaseUrl");
|
|
33
|
+
if (fallback) {
|
|
34
|
+
try {
|
|
35
|
+
return new URL(fallback).origin;
|
|
36
|
+
} catch {
|
|
37
|
+
return "https://w3kits.com";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return "https://w3kits.com";
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function bridgeRequest(message, timeoutMs = 20000) {
|
|
44
|
+
if (window.parent === window) {
|
|
45
|
+
return Promise.reject(new Error("W3Kits runtime bridge is unavailable."));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const requestId = `dreamifly-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
49
|
+
const parentOrigin = getParentOrigin();
|
|
50
|
+
|
|
51
|
+
return new Promise((resolve, reject) => {
|
|
52
|
+
const timeout = window.setTimeout(() => {
|
|
53
|
+
window.removeEventListener("message", onMessage);
|
|
54
|
+
reject(new Error("W3Kits runtime bridge timed out."));
|
|
55
|
+
}, timeoutMs);
|
|
56
|
+
|
|
57
|
+
const onMessage = (event) => {
|
|
58
|
+
if (event.source !== window.parent) return;
|
|
59
|
+
if (event.origin !== parentOrigin) return;
|
|
60
|
+
const data = event.data;
|
|
61
|
+
if (!data || data.type !== W3KITS_RESPONSE || data.requestId !== requestId) return;
|
|
62
|
+
|
|
63
|
+
window.clearTimeout(timeout);
|
|
64
|
+
window.removeEventListener("message", onMessage);
|
|
65
|
+
|
|
66
|
+
if (data.ok) {
|
|
67
|
+
resolve(data.data);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const message = data?.error?.message || data?.error?.code || "W3Kits runtime bridge failed.";
|
|
72
|
+
reject(new Error(message));
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
window.addEventListener("message", onMessage);
|
|
76
|
+
window.parent.postMessage({ ...message, version: W3KITS_BRIDGE_VERSION, requestId }, parentOrigin);
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function bootstrapLocale() {
|
|
81
|
+
const queryLocale = normalizeLocale(queryParam("w3kitsLocale"));
|
|
82
|
+
const storedLocale = normalizeLocale(window.localStorage.getItem(LOCALE_STORAGE_KEY));
|
|
83
|
+
const browserLocale =
|
|
84
|
+
normalizeLocale(window.navigator.language) ||
|
|
85
|
+
normalizeLocale(window.navigator.languages?.[0]);
|
|
86
|
+
const locale = queryLocale || storedLocale || browserLocale || "en";
|
|
87
|
+
|
|
88
|
+
window.localStorage.setItem(LOCALE_STORAGE_KEY, locale);
|
|
89
|
+
document.documentElement.lang = locale;
|
|
90
|
+
return locale;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function setWindowTitle(title) {
|
|
94
|
+
if (window.parent === window) return;
|
|
95
|
+
window.parent.postMessage(
|
|
96
|
+
{
|
|
97
|
+
type: W3KITS_WINDOW_TITLE,
|
|
98
|
+
version: W3KITS_BRIDGE_VERSION,
|
|
99
|
+
pluginId: W3KITS_PLUGIN_ID,
|
|
100
|
+
title,
|
|
101
|
+
},
|
|
102
|
+
getParentOrigin(),
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function requestLogin(reason = "plugin_runtime") {
|
|
107
|
+
if (window.parent === window) return;
|
|
108
|
+
window.parent.postMessage(
|
|
109
|
+
{
|
|
110
|
+
type: W3KITS_AUTH_REQUIRED,
|
|
111
|
+
version: W3KITS_BRIDGE_VERSION,
|
|
112
|
+
pluginId: W3KITS_PLUGIN_ID,
|
|
113
|
+
reason,
|
|
114
|
+
},
|
|
115
|
+
getParentOrigin(),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export async function getRuntimeSession() {
|
|
120
|
+
const now = Date.now();
|
|
121
|
+
if (cachedRuntimeSession && cachedRuntimeSession.expiresAt - now > 30000) {
|
|
122
|
+
return cachedRuntimeSession.value;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const session = await bridgeRequest({
|
|
126
|
+
type: W3KITS_RUNTIME_SESSION_REQUEST,
|
|
127
|
+
pluginId: W3KITS_PLUGIN_ID,
|
|
128
|
+
origin: window.location.origin,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
cachedRuntimeSession = {
|
|
132
|
+
value: session,
|
|
133
|
+
expiresAt: now + Math.max(30, (session.expiresIn || 60) - 30) * 1000,
|
|
134
|
+
};
|
|
135
|
+
return session;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function runtimeHeaders(session, includeJsonContentType = true) {
|
|
139
|
+
const headers = {};
|
|
140
|
+
if (includeJsonContentType) headers["Content-Type"] = "application/json";
|
|
141
|
+
headers["x-w3kits-runtime-session"] = session.token;
|
|
142
|
+
headers["x-w3kits-plugin-id"] = session.pluginId || W3KITS_PLUGIN_ID;
|
|
143
|
+
headers["x-w3kits-plugin-version"] = session.pluginVersion;
|
|
144
|
+
|
|
145
|
+
for (const [key, value] of Object.entries(session.identityHeaders || {})) {
|
|
146
|
+
if (typeof value === "string" && value) headers[key] = value;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (session.packageName) headers["x-w3kits-plugin-package"] = session.packageName;
|
|
150
|
+
if (session.packageIntegrity) headers["x-w3kits-plugin-integrity"] = session.packageIntegrity;
|
|
151
|
+
return headers;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function readPluginJson(path, fallback) {
|
|
155
|
+
try {
|
|
156
|
+
const result = await bridgeRequest({
|
|
157
|
+
type: W3KITS_STORAGE_READ,
|
|
158
|
+
pluginId: W3KITS_PLUGIN_ID,
|
|
159
|
+
path,
|
|
160
|
+
});
|
|
161
|
+
if (!result?.body) return fallback;
|
|
162
|
+
return JSON.parse(result.body);
|
|
163
|
+
} catch (error) {
|
|
164
|
+
const message = error instanceof Error ? error.message.toLowerCase() : "";
|
|
165
|
+
if (message.includes("not_found")) return fallback;
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export async function writePluginJson(path, value) {
|
|
171
|
+
return bridgeRequest({
|
|
172
|
+
type: W3KITS_STORAGE_WRITE,
|
|
173
|
+
pluginId: W3KITS_PLUGIN_ID,
|
|
174
|
+
path,
|
|
175
|
+
body: JSON.stringify(value, null, 2),
|
|
176
|
+
contentType: "application/json",
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export async function syncPluginStorage() {
|
|
181
|
+
return bridgeRequest({
|
|
182
|
+
type: W3KITS_STORAGE_SYNC,
|
|
183
|
+
pluginId: W3KITS_PLUGIN_ID,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
export async function desktopMkdir(path) {
|
|
188
|
+
return bridgeRequest({
|
|
189
|
+
type: W3KITS_DESKTOP_FS_MKDIR,
|
|
190
|
+
pluginId: W3KITS_PLUGIN_ID,
|
|
191
|
+
path,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export async function desktopReadDataUrl(path) {
|
|
196
|
+
const result = await bridgeRequest({
|
|
197
|
+
type: W3KITS_DESKTOP_FS_READ,
|
|
198
|
+
pluginId: W3KITS_PLUGIN_ID,
|
|
199
|
+
path,
|
|
200
|
+
});
|
|
201
|
+
if (result?.bodyBase64) {
|
|
202
|
+
return `data:${result.contentType || "application/octet-stream"};base64,${result.bodyBase64}`;
|
|
203
|
+
}
|
|
204
|
+
if (typeof result?.body === "string" && result.body) {
|
|
205
|
+
return result.body;
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export async function desktopWriteDataUrl(path, dataUrl) {
|
|
211
|
+
const parsed = parseDataUrl(dataUrl);
|
|
212
|
+
return bridgeRequest({
|
|
213
|
+
type: W3KITS_DESKTOP_FS_WRITE,
|
|
214
|
+
pluginId: W3KITS_PLUGIN_ID,
|
|
215
|
+
path,
|
|
216
|
+
bodyBase64: parsed.base64,
|
|
217
|
+
contentType: parsed.contentType,
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function parseDataUrl(dataUrl) {
|
|
222
|
+
const match = /^data:([^;,]+);base64,(.*)$/u.exec(dataUrl || "");
|
|
223
|
+
if (!match) {
|
|
224
|
+
throw new Error("invalid_data_url");
|
|
225
|
+
}
|
|
226
|
+
return {
|
|
227
|
+
contentType: match[1],
|
|
228
|
+
base64: match[2].replace(/\s/g, ""),
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
async function fetchImageAsDataUrl(url) {
|
|
233
|
+
const response = await fetch(url);
|
|
234
|
+
if (!response.ok) {
|
|
235
|
+
throw new Error(`Failed to download generated asset (${response.status}).`);
|
|
236
|
+
}
|
|
237
|
+
const blob = await response.blob();
|
|
238
|
+
return blobToDataUrl(blob);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function blobToDataUrl(blob) {
|
|
242
|
+
return new Promise((resolve, reject) => {
|
|
243
|
+
const reader = new FileReader();
|
|
244
|
+
reader.onload = () => resolve(String(reader.result || ""));
|
|
245
|
+
reader.onerror = () => reject(reader.error || new Error("file_read_failed"));
|
|
246
|
+
reader.readAsDataURL(blob);
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function isLoginRequired(payload, status) {
|
|
251
|
+
if (status === 401) return true;
|
|
252
|
+
const code = payload?.error?.code || payload?.error || payload?.code;
|
|
253
|
+
return code === "login_required" || code === "plugin_runtime_session_required" || code === "invalid_plugin_runtime_session";
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export async function generateImage({ model, prompt, width, height, referenceDataUrl }) {
|
|
257
|
+
const session = await getRuntimeSession();
|
|
258
|
+
const baseUrl = String(session.openaiBaseUrl || queryParam("openaiBaseUrl") || "/api/ai/openai/v1").replace(/\/+$/, "");
|
|
259
|
+
|
|
260
|
+
if (referenceDataUrl && model === "gpt-image-2") {
|
|
261
|
+
const formData = new FormData();
|
|
262
|
+
const parsed = parseDataUrl(referenceDataUrl);
|
|
263
|
+
const binary = atob(parsed.base64);
|
|
264
|
+
const bytes = new Uint8Array(binary.length);
|
|
265
|
+
for (let index = 0; index < binary.length; index += 1) bytes[index] = binary.charCodeAt(index);
|
|
266
|
+
formData.append("model", model);
|
|
267
|
+
formData.append("prompt", prompt);
|
|
268
|
+
formData.append("n", "1");
|
|
269
|
+
formData.append("size", `${width}x${height}`);
|
|
270
|
+
formData.append("image", new Blob([bytes], { type: parsed.contentType }), "reference.png");
|
|
271
|
+
|
|
272
|
+
const response = await fetch(`${baseUrl}/images/edits`, {
|
|
273
|
+
method: "POST",
|
|
274
|
+
headers: runtimeHeaders(session, false),
|
|
275
|
+
body: formData,
|
|
276
|
+
});
|
|
277
|
+
const payload = await response.json().catch(() => ({}));
|
|
278
|
+
if (!response.ok) {
|
|
279
|
+
if (isLoginRequired(payload, response.status)) requestLogin("ai_request");
|
|
280
|
+
throw new Error(payload?.error?.message || payload?.message || `Image edit failed (${response.status}).`);
|
|
281
|
+
}
|
|
282
|
+
return readImageResult(payload);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const response = await fetch(`${baseUrl}/images/generations`, {
|
|
286
|
+
method: "POST",
|
|
287
|
+
headers: runtimeHeaders(session, true),
|
|
288
|
+
body: JSON.stringify({
|
|
289
|
+
model,
|
|
290
|
+
prompt,
|
|
291
|
+
n: 1,
|
|
292
|
+
size: `${width}x${height}`,
|
|
293
|
+
}),
|
|
294
|
+
});
|
|
295
|
+
const payload = await response.json().catch(() => ({}));
|
|
296
|
+
if (!response.ok) {
|
|
297
|
+
if (isLoginRequired(payload, response.status)) requestLogin("ai_request");
|
|
298
|
+
throw new Error(payload?.error?.message || payload?.message || `Image generation failed (${response.status}).`);
|
|
299
|
+
}
|
|
300
|
+
return readImageResult(payload);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function readImageResult(payload) {
|
|
304
|
+
const asset = Array.isArray(payload?.data) ? payload.data[0] : null;
|
|
305
|
+
if (!asset) {
|
|
306
|
+
throw new Error("Generated response missing image data.");
|
|
307
|
+
}
|
|
308
|
+
if (asset.b64_json) {
|
|
309
|
+
return `data:image/png;base64,${asset.b64_json}`;
|
|
310
|
+
}
|
|
311
|
+
if (asset.url) {
|
|
312
|
+
return fetchImageAsDataUrl(asset.url);
|
|
313
|
+
}
|
|
314
|
+
throw new Error("Generated response missing b64_json/url.");
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function visibleDesktopPath(path) {
|
|
318
|
+
return String(path || "").replace(/^\/\.w3kits\/desktop\/files/, "/home/agent");
|
|
319
|
+
}
|
package/dist/styles.css
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
color-scheme: dark;
|
|
3
|
+
--bg: #0f1013;
|
|
4
|
+
--panel: #17191f;
|
|
5
|
+
--panel-2: #1f232b;
|
|
6
|
+
--border: rgba(255, 255, 255, 0.08);
|
|
7
|
+
--text: #f4f5f7;
|
|
8
|
+
--muted: #aeb5c2;
|
|
9
|
+
--accent: #8d7bff;
|
|
10
|
+
--accent-2: #4cc2ff;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
* {
|
|
14
|
+
box-sizing: border-box;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
html,
|
|
18
|
+
body {
|
|
19
|
+
margin: 0;
|
|
20
|
+
min-height: 100%;
|
|
21
|
+
background:
|
|
22
|
+
radial-gradient(circle at top right, rgba(141, 123, 255, 0.18), transparent 28%),
|
|
23
|
+
radial-gradient(circle at bottom left, rgba(76, 194, 255, 0.14), transparent 26%),
|
|
24
|
+
var(--bg);
|
|
25
|
+
color: var(--text);
|
|
26
|
+
font: 14px/1.45 Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
button,
|
|
30
|
+
input,
|
|
31
|
+
select,
|
|
32
|
+
textarea {
|
|
33
|
+
font: inherit;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
button,
|
|
37
|
+
select,
|
|
38
|
+
input,
|
|
39
|
+
textarea {
|
|
40
|
+
color: inherit;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
button {
|
|
44
|
+
border: 1px solid var(--border);
|
|
45
|
+
background: var(--panel-2);
|
|
46
|
+
border-radius: 10px;
|
|
47
|
+
padding: 10px 14px;
|
|
48
|
+
cursor: pointer;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
button:disabled {
|
|
52
|
+
opacity: 0.5;
|
|
53
|
+
cursor: not-allowed;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
button.primary {
|
|
57
|
+
background: linear-gradient(135deg, var(--accent), #6f67ff);
|
|
58
|
+
border-color: transparent;
|
|
59
|
+
color: #fff;
|
|
60
|
+
min-width: 132px;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
select,
|
|
64
|
+
input[type="file"],
|
|
65
|
+
textarea {
|
|
66
|
+
width: 100%;
|
|
67
|
+
background: #11131a;
|
|
68
|
+
border: 1px solid var(--border);
|
|
69
|
+
border-radius: 10px;
|
|
70
|
+
padding: 10px 12px;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
textarea {
|
|
74
|
+
resize: vertical;
|
|
75
|
+
min-height: 160px;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
.shell {
|
|
79
|
+
padding: 18px;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.hero,
|
|
83
|
+
.grid {
|
|
84
|
+
display: grid;
|
|
85
|
+
gap: 16px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
.hero {
|
|
89
|
+
grid-template-columns: 1fr auto;
|
|
90
|
+
align-items: end;
|
|
91
|
+
margin-bottom: 16px;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.hero h1 {
|
|
95
|
+
margin: 0;
|
|
96
|
+
font-size: 28px;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.hero p {
|
|
100
|
+
margin: 6px 0 0;
|
|
101
|
+
color: var(--muted);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.status-pill {
|
|
105
|
+
align-self: start;
|
|
106
|
+
border: 1px solid var(--border);
|
|
107
|
+
border-radius: 999px;
|
|
108
|
+
padding: 8px 12px;
|
|
109
|
+
color: var(--muted);
|
|
110
|
+
background: rgba(255, 255, 255, 0.03);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.grid {
|
|
114
|
+
grid-template-columns: minmax(0, 1.35fr) minmax(320px, 0.95fr);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.panel {
|
|
118
|
+
border: 1px solid var(--border);
|
|
119
|
+
background: rgba(23, 25, 31, 0.92);
|
|
120
|
+
border-radius: 18px;
|
|
121
|
+
padding: 16px;
|
|
122
|
+
min-height: 0;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
.stack {
|
|
126
|
+
display: grid;
|
|
127
|
+
gap: 14px;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.panel-head {
|
|
131
|
+
display: flex;
|
|
132
|
+
align-items: baseline;
|
|
133
|
+
justify-content: space-between;
|
|
134
|
+
gap: 12px;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
.panel-head h2 {
|
|
138
|
+
margin: 0;
|
|
139
|
+
font-size: 16px;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.panel-head span,
|
|
143
|
+
.field small,
|
|
144
|
+
.video-hint,
|
|
145
|
+
.empty,
|
|
146
|
+
.history-copy span,
|
|
147
|
+
.reference-card span {
|
|
148
|
+
color: var(--muted);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.workflow-grid {
|
|
152
|
+
display: grid;
|
|
153
|
+
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
154
|
+
gap: 10px;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
.workflow-card {
|
|
158
|
+
display: grid;
|
|
159
|
+
gap: 6px;
|
|
160
|
+
text-align: left;
|
|
161
|
+
min-height: 104px;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
.workflow-card.active {
|
|
165
|
+
border-color: rgba(141, 123, 255, 0.8);
|
|
166
|
+
box-shadow: inset 0 0 0 1px rgba(141, 123, 255, 0.35);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.field,
|
|
170
|
+
.field-row {
|
|
171
|
+
display: grid;
|
|
172
|
+
gap: 8px;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
.field-row {
|
|
176
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.action-row {
|
|
180
|
+
display: flex;
|
|
181
|
+
gap: 10px;
|
|
182
|
+
flex-wrap: wrap;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
.reference-card {
|
|
186
|
+
display: grid;
|
|
187
|
+
grid-template-columns: 1fr;
|
|
188
|
+
gap: 12px;
|
|
189
|
+
padding: 14px;
|
|
190
|
+
border-radius: 14px;
|
|
191
|
+
background: rgba(255, 255, 255, 0.025);
|
|
192
|
+
border: 1px dashed var(--border);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
.reference-card.has-reference {
|
|
196
|
+
grid-template-columns: 112px 1fr;
|
|
197
|
+
align-items: center;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
.reference-card img {
|
|
201
|
+
width: 112px;
|
|
202
|
+
height: 112px;
|
|
203
|
+
object-fit: cover;
|
|
204
|
+
border-radius: 12px;
|
|
205
|
+
border: 1px solid var(--border);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.video-strip {
|
|
209
|
+
display: grid;
|
|
210
|
+
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
211
|
+
gap: 10px;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
.video-card,
|
|
215
|
+
.empty {
|
|
216
|
+
border: 1px solid var(--border);
|
|
217
|
+
border-radius: 14px;
|
|
218
|
+
padding: 12px;
|
|
219
|
+
background: rgba(255, 255, 255, 0.025);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
.history-list {
|
|
223
|
+
display: grid;
|
|
224
|
+
gap: 10px;
|
|
225
|
+
max-height: 640px;
|
|
226
|
+
overflow: auto;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.history-card {
|
|
230
|
+
display: grid;
|
|
231
|
+
grid-template-columns: 132px 1fr;
|
|
232
|
+
gap: 12px;
|
|
233
|
+
padding: 10px;
|
|
234
|
+
border: 1px solid var(--border);
|
|
235
|
+
border-radius: 14px;
|
|
236
|
+
background: rgba(255, 255, 255, 0.025);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.history-card img {
|
|
240
|
+
width: 132px;
|
|
241
|
+
height: 132px;
|
|
242
|
+
object-fit: cover;
|
|
243
|
+
border-radius: 12px;
|
|
244
|
+
border: 1px solid var(--border);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
.history-copy {
|
|
248
|
+
display: grid;
|
|
249
|
+
gap: 4px;
|
|
250
|
+
min-width: 0;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.history-copy strong,
|
|
254
|
+
.history-copy span,
|
|
255
|
+
.reference-card strong,
|
|
256
|
+
.reference-card span {
|
|
257
|
+
overflow: hidden;
|
|
258
|
+
text-overflow: ellipsis;
|
|
259
|
+
white-space: nowrap;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
@media (max-width: 1080px) {
|
|
263
|
+
.grid {
|
|
264
|
+
grid-template-columns: 1fr;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.workflow-grid {
|
|
268
|
+
grid-template-columns: 1fr;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
.field-row,
|
|
272
|
+
.video-strip {
|
|
273
|
+
grid-template-columns: 1fr;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
.history-card,
|
|
277
|
+
.reference-card.has-reference {
|
|
278
|
+
grid-template-columns: 1fr;
|
|
279
|
+
}
|
|
280
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@w3kits-com/plugin-dreamifly",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "W3Kits Dreamifly plugin extracted from the Dreamifly creative create/workflows surface.",
|
|
7
|
+
"license": "UNLICENSED",
|
|
8
|
+
"author": "W3Kits",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"README.md"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/W3Kits/Dreamifly.git"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/W3Kits/Dreamifly#readme",
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=22.12.0"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "node scripts/prepare-dist.mjs",
|
|
26
|
+
"prepare:dist": "node scripts/prepare-dist.mjs",
|
|
27
|
+
"pack:dry-run": "npm pack --json --dry-run",
|
|
28
|
+
"pack:artifact": "npm pack --json"
|
|
29
|
+
}
|
|
30
|
+
}
|