@yysng/ai-edit-engine 2.1.1 → 2.3.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/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +63 -0
- package/dist/core/edit.d.ts +2 -0
- package/dist/core/edit.js +3 -0
- package/dist/core/generate.d.ts +1 -0
- package/dist/core/generate.js +51 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.js +3 -0
- package/dist/server/routes/edit.d.ts +1 -0
- package/dist/server/routes/edit.js +11 -0
- package/dist/server/routes/generate.d.ts +1 -0
- package/dist/server/routes/generate.js +6 -0
- package/dist/server/routes/ui.d.ts +1 -0
- package/dist/server/routes/ui.js +15 -0
- package/dist/ui/index.d.ts +1 -0
- package/dist/ui/index.js +19 -0
- package/package.json +28 -25
- package/README.md +0 -9
- package/src/engine/api/ai-edit.js +0 -45
- package/src/engine/api/ai-generate.js +0 -112
- package/src/engine/index.js +0 -2
- package/src/engine/permissions/ai/permission.json +0 -11
- package/src/index.js +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function mountAiEditor(root: HTMLElement): void;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
export function mountAiEditor(root) {
|
|
2
|
+
root.innerHTML = `
|
|
3
|
+
<h1>AI Editor</h1>
|
|
4
|
+
|
|
5
|
+
<select id="ai-section">
|
|
6
|
+
<option value="hero">Hero</option>
|
|
7
|
+
<option value="cta">CTA</option>
|
|
8
|
+
</select>
|
|
9
|
+
|
|
10
|
+
<br/><br/>
|
|
11
|
+
|
|
12
|
+
<textarea id="ai-input" placeholder="Describe your change..."></textarea>
|
|
13
|
+
|
|
14
|
+
<br/><br/>
|
|
15
|
+
|
|
16
|
+
<button id="ai-apply">Apply Change</button>
|
|
17
|
+
<span id="ai-status"></span>
|
|
18
|
+
|
|
19
|
+
<pre id="ai-output"></pre>
|
|
20
|
+
`;
|
|
21
|
+
const textarea = root.querySelector("#ai-input");
|
|
22
|
+
const button = root.querySelector("#ai-apply");
|
|
23
|
+
const output = root.querySelector("#ai-output");
|
|
24
|
+
const select = root.querySelector("#ai-section");
|
|
25
|
+
const status = root.querySelector("#ai-status");
|
|
26
|
+
if (!textarea || !button || !output || !select || !status) {
|
|
27
|
+
throw new Error("AI Editor UI failed to mount");
|
|
28
|
+
}
|
|
29
|
+
button.addEventListener("click", async () => {
|
|
30
|
+
const instruction = textarea.value;
|
|
31
|
+
const section = select.value;
|
|
32
|
+
button.disabled = true;
|
|
33
|
+
status.textContent = `Editing ${section}...`;
|
|
34
|
+
try {
|
|
35
|
+
// 1️⃣ Generate
|
|
36
|
+
const genRes = await fetch("/api/ai-generate", {
|
|
37
|
+
method: "POST",
|
|
38
|
+
headers: { "Content-Type": "application/json" },
|
|
39
|
+
body: JSON.stringify({ instruction, section })
|
|
40
|
+
});
|
|
41
|
+
const data = await genRes.json();
|
|
42
|
+
output.textContent = JSON.stringify(data, null, 2);
|
|
43
|
+
// 2️⃣ Live preview
|
|
44
|
+
window.dispatchEvent(new CustomEvent("ai-preview", {
|
|
45
|
+
detail: { section, data }
|
|
46
|
+
}));
|
|
47
|
+
// 3️⃣ Persist
|
|
48
|
+
await fetch("/api/ai-edit", {
|
|
49
|
+
method: "POST",
|
|
50
|
+
headers: { "Content-Type": "application/json" },
|
|
51
|
+
body: JSON.stringify({ section, content: data })
|
|
52
|
+
});
|
|
53
|
+
status.textContent = `Saved to ${section}`;
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
console.error(err);
|
|
57
|
+
status.textContent = "Error applying change";
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
button.disabled = false;
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function generateContent(section: "hero" | "cta", instruction: string, runtimeEnv: any): Promise<any>;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import OpenAI from "openai";
|
|
2
|
+
export async function generateContent(section, instruction, runtimeEnv) {
|
|
3
|
+
const client = new OpenAI({
|
|
4
|
+
apiKey: runtimeEnv.OPENAI_API_KEY
|
|
5
|
+
});
|
|
6
|
+
if (!client.apiKey) {
|
|
7
|
+
throw new Error("Missing OPENAI_API_KEY");
|
|
8
|
+
}
|
|
9
|
+
if (section === "cta") {
|
|
10
|
+
const prompt = `
|
|
11
|
+
Return JSON ONLY:
|
|
12
|
+
{
|
|
13
|
+
"heading": string,
|
|
14
|
+
"description": string,
|
|
15
|
+
"button": { "label": string, "href": string }
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
User instruction:
|
|
19
|
+
${instruction}
|
|
20
|
+
`;
|
|
21
|
+
const completion = await client.chat.completions.create({
|
|
22
|
+
model: "gpt-4o-mini",
|
|
23
|
+
messages: [{ role: "user", content: prompt }],
|
|
24
|
+
temperature: 0.7
|
|
25
|
+
});
|
|
26
|
+
const raw = completion.choices[0].message.content;
|
|
27
|
+
if (!raw)
|
|
28
|
+
throw new Error("OpenAI returned empty content");
|
|
29
|
+
return JSON.parse(raw);
|
|
30
|
+
}
|
|
31
|
+
const heroPrompt = `
|
|
32
|
+
Return JSON ONLY:
|
|
33
|
+
{
|
|
34
|
+
"title": string,
|
|
35
|
+
"subtitle": string,
|
|
36
|
+
"cta": { "label": string, "href": string }
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
User instruction:
|
|
40
|
+
${instruction}
|
|
41
|
+
`;
|
|
42
|
+
const completion = await client.chat.completions.create({
|
|
43
|
+
model: "gpt-4o-mini",
|
|
44
|
+
messages: [{ role: "user", content: heroPrompt }],
|
|
45
|
+
temperature: 0.7
|
|
46
|
+
});
|
|
47
|
+
const raw = completion.choices[0].message.content;
|
|
48
|
+
if (!raw)
|
|
49
|
+
throw new Error("OpenAI returned empty content");
|
|
50
|
+
return JSON.parse(raw);
|
|
51
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./server/index.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./server/index.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function handleEdit(request: Request, env: any): Promise<Response>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { editContent } from "../../core/edit.js";
|
|
2
|
+
export async function handleEdit(request, env) {
|
|
3
|
+
const { section, content } = await request.json();
|
|
4
|
+
// The client will inject this at runtime
|
|
5
|
+
const updateContent = env.UPDATE_CONTENT;
|
|
6
|
+
if (!updateContent) {
|
|
7
|
+
throw new Error("UPDATE_CONTENT not provided to engine");
|
|
8
|
+
}
|
|
9
|
+
const result = await editContent(section, content, env, updateContent);
|
|
10
|
+
return Response.json({ result });
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function handleGenerate(request: Request, env: any): Promise<Response>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { generateContent } from "../../core/generate.js";
|
|
2
|
+
export async function handleGenerate(request, env) {
|
|
3
|
+
const { section, instruction } = await request.json();
|
|
4
|
+
const result = await generateContent(section, instruction, env);
|
|
5
|
+
return Response.json(result);
|
|
6
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function handleUI(): Response;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function handleUI() {
|
|
2
|
+
return new Response(`
|
|
3
|
+
<!DOCTYPE html>
|
|
4
|
+
<html>
|
|
5
|
+
<head>
|
|
6
|
+
<meta charset="utf-8" />
|
|
7
|
+
<title>AI Editor</title>
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div id="ai-editor-root"></div>
|
|
11
|
+
<script type="module" src="/__ai_edit_ui_app.js"></script>
|
|
12
|
+
</body>
|
|
13
|
+
</html>
|
|
14
|
+
`, { headers: { "Content-Type": "text/html" } });
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function renderEditor(root: HTMLElement): void;
|
package/dist/ui/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export function renderEditor(root) {
|
|
2
|
+
root.innerHTML = `
|
|
3
|
+
<div style="font-family: system-ui; max-width: 900px; margin: 2rem auto">
|
|
4
|
+
<h1>AI Editor</h1>
|
|
5
|
+
|
|
6
|
+
<textarea id="instruction" rows="4" style="width:100%" placeholder="Describe your change..."></textarea>
|
|
7
|
+
|
|
8
|
+
<div style="margin-top:1rem">
|
|
9
|
+
<button id="ping">Ping Engine</button>
|
|
10
|
+
<span id="status" style="margin-left:1rem;color:#555"></span>
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
13
|
+
`;
|
|
14
|
+
const status = root.querySelector("#status");
|
|
15
|
+
const btn = root.querySelector("#ping");
|
|
16
|
+
btn.addEventListener("click", () => {
|
|
17
|
+
status.textContent = "Engine connected successfully.";
|
|
18
|
+
});
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,33 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yysng/ai-edit-engine",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"
|
|
6
|
-
|
|
5
|
+
"sideEffects": false,
|
|
6
|
+
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
|
|
7
10
|
"exports": {
|
|
8
|
-
".":
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"./client": {
|
|
16
|
+
"import": "./dist/client/index.js",
|
|
17
|
+
"types": "./dist/client/index.d.ts"
|
|
18
|
+
},
|
|
19
|
+
"./server": {
|
|
20
|
+
"import": "./dist/server/index.js",
|
|
21
|
+
"types": "./dist/server/index.d.ts"
|
|
22
|
+
},
|
|
23
|
+
"./package.json": "./package.json"
|
|
12
24
|
},
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
25
|
+
|
|
26
|
+
"files": ["dist"],
|
|
27
|
+
|
|
28
|
+
"scripts": {
|
|
29
|
+
"build": "tsc"
|
|
30
|
+
},
|
|
31
|
+
|
|
16
32
|
"dependencies": {
|
|
17
33
|
"openai": "^6.13.0",
|
|
18
34
|
"zod": "^3.23.8"
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
"astro": "^5.0.0",
|
|
22
|
-
"@yysng/astro-boilerplate": "^1.1.36"
|
|
23
|
-
},
|
|
24
|
-
"keywords": [
|
|
25
|
-
"astro",
|
|
26
|
-
"ai",
|
|
27
|
-
"cms",
|
|
28
|
-
"content-engine",
|
|
29
|
-
"cloudflare",
|
|
30
|
-
"ai-editor"
|
|
31
|
-
],
|
|
32
|
-
"license": "MIT"
|
|
33
|
-
}
|
|
35
|
+
}
|
|
36
|
+
}
|
package/README.md
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
export const prerender = false;
|
|
2
|
-
|
|
3
|
-
import * as Engine from "@yysng/astro-boilerplate";
|
|
4
|
-
|
|
5
|
-
const { updateContent } = Engine;
|
|
6
|
-
|
|
7
|
-
// --------------------------------------------------
|
|
8
|
-
// 🧠 ENGINE API (used by npm consumers)
|
|
9
|
-
// --------------------------------------------------
|
|
10
|
-
export async function editContent({ section, content, env = {} }) {
|
|
11
|
-
if (!section || !content) {
|
|
12
|
-
throw new Error("Missing section or content");
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
return updateContent(section, content, env);
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// --------------------------------------------------
|
|
19
|
-
// 🌐 ASTRO ENDPOINT (used by playground / client site)
|
|
20
|
-
// --------------------------------------------------
|
|
21
|
-
export async function POST({ request, locals }) {
|
|
22
|
-
try {
|
|
23
|
-
const body = await request.json();
|
|
24
|
-
const { section, content } = body;
|
|
25
|
-
|
|
26
|
-
const result = await editContent({
|
|
27
|
-
section,
|
|
28
|
-
content,
|
|
29
|
-
env: locals?.runtime?.env
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
return new Response(
|
|
33
|
-
JSON.stringify({ success: true, updated: section, result }),
|
|
34
|
-
{ status: 200 }
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
} catch (error) {
|
|
38
|
-
console.error("AI Edit Error:", error);
|
|
39
|
-
|
|
40
|
-
return new Response(
|
|
41
|
-
JSON.stringify({ error: "Internal error", message: error.message }),
|
|
42
|
-
{ status: 500 }
|
|
43
|
-
);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
export const prerender = false;
|
|
2
|
-
|
|
3
|
-
import OpenAI from "openai";
|
|
4
|
-
|
|
5
|
-
// --------------------------------------------------
|
|
6
|
-
// 🧠 ENGINE API (used by npm consumers)
|
|
7
|
-
// --------------------------------------------------
|
|
8
|
-
export async function generateContent({ instruction, section, env = {} }) {
|
|
9
|
-
if (!instruction || typeof instruction !== "string") {
|
|
10
|
-
throw new Error("Invalid instruction");
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const client = new OpenAI({
|
|
14
|
-
apiKey: env.OPENAI_API_KEY
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
// ------------------------------
|
|
18
|
-
// CTA GENERATION
|
|
19
|
-
// ------------------------------
|
|
20
|
-
if (section === "cta") {
|
|
21
|
-
const prompt = `
|
|
22
|
-
You are an AI editor responsible for updating the CTA section of a website.
|
|
23
|
-
|
|
24
|
-
Rules:
|
|
25
|
-
- Output JSON only
|
|
26
|
-
- Follow the schema exactly
|
|
27
|
-
- Prefer partial updates
|
|
28
|
-
- Do not invent fields
|
|
29
|
-
|
|
30
|
-
Return JSON ONLY in this exact format:
|
|
31
|
-
|
|
32
|
-
{
|
|
33
|
-
"heading": string,
|
|
34
|
-
"description": string,
|
|
35
|
-
"button": {
|
|
36
|
-
"label": string,
|
|
37
|
-
"href": string
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
User instruction:
|
|
42
|
-
${instruction}
|
|
43
|
-
`;
|
|
44
|
-
|
|
45
|
-
const completion = await client.chat.completions.create({
|
|
46
|
-
model: "gpt-4o-mini",
|
|
47
|
-
messages: [{ role: "user", content: prompt }],
|
|
48
|
-
temperature: 0.7
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
return JSON.parse(completion.choices[0].message.content);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// ------------------------------
|
|
55
|
-
// HERO GENERATION
|
|
56
|
-
// ------------------------------
|
|
57
|
-
const heroPrompt = `
|
|
58
|
-
You are an AI editor responsible for updating the hero section of a website.
|
|
59
|
-
|
|
60
|
-
Rules:
|
|
61
|
-
- Output JSON only
|
|
62
|
-
- Follow the schema exactly
|
|
63
|
-
- Prefer partial updates
|
|
64
|
-
- Do not invent fields
|
|
65
|
-
|
|
66
|
-
Return JSON ONLY in this exact format:
|
|
67
|
-
|
|
68
|
-
{
|
|
69
|
-
"title": string,
|
|
70
|
-
"subtitle": string,
|
|
71
|
-
"cta": {
|
|
72
|
-
"label": string,
|
|
73
|
-
"href": string
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
User instruction:
|
|
78
|
-
${instruction}
|
|
79
|
-
`;
|
|
80
|
-
|
|
81
|
-
const completion = await client.chat.completions.create({
|
|
82
|
-
model: "gpt-4o-mini",
|
|
83
|
-
messages: [{ role: "user", content: heroPrompt }],
|
|
84
|
-
temperature: 0.7
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
return JSON.parse(completion.choices[0].message.content);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// --------------------------------------------------
|
|
91
|
-
// 🌐 ASTRO ENDPOINT (used by playground / client site)
|
|
92
|
-
// --------------------------------------------------
|
|
93
|
-
export async function POST({ request, locals }) {
|
|
94
|
-
try {
|
|
95
|
-
const { instruction, section } = await request.json();
|
|
96
|
-
|
|
97
|
-
const result = await generateContent({
|
|
98
|
-
instruction,
|
|
99
|
-
section,
|
|
100
|
-
env: locals?.runtime?.env
|
|
101
|
-
});
|
|
102
|
-
|
|
103
|
-
return new Response(JSON.stringify(result), { status: 200 });
|
|
104
|
-
|
|
105
|
-
} catch (err) {
|
|
106
|
-
console.error("AI generate error:", err);
|
|
107
|
-
return new Response(
|
|
108
|
-
JSON.stringify({ error: "AI generation failed", message: err.message }),
|
|
109
|
-
{ status: 500 }
|
|
110
|
-
);
|
|
111
|
-
}
|
|
112
|
-
}
|
package/src/engine/index.js
DELETED
package/src/index.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./engine/index.js";
|