x
This commit is contained in:
1 parent
230716933e
commit
3bbe2bb35e
16 files changed
+1815
-109
No files matched your search
@@ -34,6 +34,7 @@ import { toolsRoute } from "./routes/tools";
|
||||
import { crmRoute } from "./routes/crm";
|
||||
import { brandKnowledgeRoute } from "./routes/brandKnowledge";
|
||||
import { categorySectionsRoute } from "./routes/categorySections";
|
||||
import { llmPromptsRoute } from "./routes/llmPrompts";
|
||||
const PORT = process.env.PORT ? parseInt(process.env.PORT) : 3051;
|
||||
const CORS_ORIGIN = process.env.CORS_ORIGIN || "http://localhost:5173";
|
||||
const JWT_SECRET = process.env.JWT_SECRET || "homaroma_secret_2026_brand";
|
||||
@@ -141,7 +142,7 @@ const app = new Elysia()
|
||||
return file;
|
||||
})
|
||||
.group("/api", (app) =>
|
||||
app.use(authRoute).use(categoriesRoute).use(categorySectionsRoute).use(productsRoute).use(productAttributesRoute).use(uploadRoute).use(ordersRoute).use(settingsRoute).use(articlesRoute).use(tiktokRoute).use(rawMaterialsRoute).use(shopeeRoute).use(tiktokOrderRoute).use(lazadaOrderRoute).use(customersRoute).use(couponsRoute).use(reviewsRoute).use(promotionsRoute).use(pagesRoute).use(paymentsRoute).use(profileRoute).use(lineRoutes).use(menusRoute).use(newsletterRoute).use(mediaRoute).use(contactsRoute).use(backupRoute).use(businessAnalyticsRoute).use(toolsRoute).use(crmRoute).use(brandKnowledgeRoute)
|
||||
app.use(authRoute).use(categoriesRoute).use(categorySectionsRoute).use(productsRoute).use(productAttributesRoute).use(uploadRoute).use(ordersRoute).use(settingsRoute).use(articlesRoute).use(tiktokRoute).use(rawMaterialsRoute).use(shopeeRoute).use(tiktokOrderRoute).use(lazadaOrderRoute).use(customersRoute).use(couponsRoute).use(reviewsRoute).use(promotionsRoute).use(pagesRoute).use(paymentsRoute).use(profileRoute).use(lineRoutes).use(menusRoute).use(newsletterRoute).use(mediaRoute).use(contactsRoute).use(backupRoute).use(businessAnalyticsRoute).use(toolsRoute).use(crmRoute).use(brandKnowledgeRoute).use(llmPromptsRoute)
|
||||
)
|
||||
.listen({ port: PORT, hostname: "0.0.0.0" });
|
||||
|
||||
|
||||
@@ -0,0 +1,416 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
import { adminOnly } from "../middleware/auth";
|
||||
|
||||
export const DEFAULT_LLM_PROMPTS = [
|
||||
{
|
||||
title: "Mega Menu JSON Tree Generator",
|
||||
category: "navigation",
|
||||
description: "สร้างโครงสร้างเมนู Header Navigation แบบ Multi-level (Array of Nodes) พร้อม labels ทุกภาษา, href, icon และ badges",
|
||||
tags: ["Menu", "JSON", "Navigation", "Multilingual"],
|
||||
isSystem: true,
|
||||
content: `คุณคือ Senior Full-Stack Navigation & UX Architect สำหรับแพลตฟอร์ม e-Commerce หรู (DBK Platform / HomAroma)
|
||||
จงออกแบบโครงสร้างเมนู Mega Menu (Main Navigation) ในรูปแบบ JSON Array ตามสเปกด้านล่างนี้ โดยไม่ต้องใส่ Markdown block ครอบ หรือคำอธิบายเสริมใดๆ ให้ตอบเฉพาะ JSON Array เท่านั้น
|
||||
|
||||
### โครงสร้างข้อมูล Node แต่ละตัว (MenuItem):
|
||||
[
|
||||
{
|
||||
"id": "menu-unique-id", // string เช่น "nav-scents", "nav-candles"
|
||||
"label": "English Display Name",
|
||||
"href": "/shop/category-slug", // URL ภายในเว็บ หรือ # หากมี dropdown
|
||||
"description": "คำบรรยายสั้นๆ อธิบายหมวดหมู่",
|
||||
"target": "_self", // "_self" หรือ "_blank"
|
||||
"badge": "NEW", // ตัวเลือก: null หรือ "NEW", "HOT", "SALE", "BESTSELLER"
|
||||
"icon": "Sparkles", // Lucide icon เช่น "Flame", "Droplets", "Wind", "Sparkles", "Package"
|
||||
"displayMode": "mega", // "mega" (กล่องกว้าง 3-4 คอลัมน์) หรือ "dropdown" (เมนูลงมาธรรมดา)
|
||||
"columns": 3, // จำนวนคอลัมน์ของเมนูย่อย (2, 3 หรือ 4)
|
||||
"translations": {
|
||||
"th": { "label": "ชื่อภาษาไทย", "description": "คำบรรยายภาษาไทย" },
|
||||
"en": { "label": "English Name", "description": "English description" }
|
||||
},
|
||||
"children": [
|
||||
// โครงสร้างลูกแบบเดียวกัน มี children ซ้อนลงไปได้หลายระดับ
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
### ข้อกำหนด:
|
||||
1. รองรับการจัดกลุ่มสินค้าอย่างเป็นระเบียบ แบ่งเป็น Header กลุ่มย่อย และไอเทมลูก
|
||||
2. ทุกรายการควรมี translations ภาษาไทย (th) และอังกฤษ (en) ครบถ้วน
|
||||
3. ตอบกลับเฉพาะ Pure JSON Array ที่ถูกต้องตามมาตรฐาน JSON เท่านั้น`
|
||||
},
|
||||
{
|
||||
title: "Product Catalog & Scent Formulations Generator",
|
||||
category: "catalog",
|
||||
description: "สร้างแคตตาล็อกสินค้าเครื่องหอม เทียนหอม รูมสเปรย์ พร้อม Note กลิ่น (Top/Heart/Base), SKU, ราคา และ Attribute Options",
|
||||
tags: ["Products", "Catalog", "Aromatherapy", "SKU"],
|
||||
isSystem: true,
|
||||
content: `คุณคือ Senior Perfumer และ E-commerce Catalog Specialist เชี่ยวชาญการสร้างข้อมูลผลิตภัณฑ์เครื่องหอมระดับ Luxury & Niche Fragrance
|
||||
จงช่วยสร้างข้อมูลรายการสินค้า (Products) ในรูปแบบ JSON Array เพื่อนำเข้าสู่ระบบหลังบ้าน DBK Platform
|
||||
|
||||
### ข้อมูลสินค้าที่ต้องการในแต่ละ Object:
|
||||
{
|
||||
"name": "ชื่อสินค้าภาษาอังกฤษ",
|
||||
"nameTh": "ชื่อสินค้าภาษาไทย",
|
||||
"slug": "product-url-slug",
|
||||
"sku": "HMA-CAT-001",
|
||||
"categorySlug": "scented-candles",
|
||||
"price": 890,
|
||||
"originalPrice": 1290,
|
||||
"inStock": true,
|
||||
"stockCount": 50,
|
||||
"shortDescription": "คำบรรยายสั้นน่าดึงดูด 1-2 ประโยค",
|
||||
"scentProfile": {
|
||||
"intensity": "Moderate", // Subtle, Moderate, Strong, Intense
|
||||
"family": "Woody & Amber", // Floral, Citrus, Woody, Oriental, Fresh
|
||||
"topNotes": ["Bergamot", "Pink Pepper"],
|
||||
"heartNotes": ["Atlas Cedar", "Smoked Oud"],
|
||||
"baseNotes": ["Ambergris", "Madagascar Vanilla"]
|
||||
},
|
||||
"attributes": [
|
||||
{ "name": "Size / Net Weight", "value": "220g / 7.7 oz" },
|
||||
{ "name": "Burn Time", "value": "Approx. 50-55 Hours" },
|
||||
{ "name": "Wax Type", "value": "100% Natural Soy & Coconut Wax Blend" }
|
||||
],
|
||||
"tags": ["Bestseller", "Relaxation", "Handmade"]
|
||||
}
|
||||
|
||||
### เงื่อนไข:
|
||||
- อารมณ์ของกลิ่นต้องมีความประณีต อธิบายโน้ตกลิ่นชัดเจนและเป็นไปได้จริง
|
||||
- ตอบกลับเฉพาะ JSON Array เท่านั้น`
|
||||
},
|
||||
{
|
||||
title: "Dynamic Theme & Color Tokens Generator",
|
||||
category: "theme",
|
||||
description: "สร้าง Color Palette JSON สำหรับระบบ Dynamic Theme Tokens (Light/Dark Mode, Glows, Borders, Surfaces)",
|
||||
tags: ["Theme", "Colors", "Tokens", "UI/UX"],
|
||||
isSystem: true,
|
||||
content: `คุณคือ Senior UI/UX Designer และ Design System Architect ระดับมืออาชีพ เชี่ยวชาญการออกแบบ Color Palette, Dark/Light Theme System, และ Visual Identity ให้กับร้านค้าออนไลน์ระดับ Luxury, Minimalist, Cyberpunk, หรือ Modern Lifestyle
|
||||
|
||||
กรุณาสร้าง JSON Configuration สำหรับ Theme Tokens ของระบบ e-Commerce ตามสเปกด้านล่าง:
|
||||
|
||||
### สเปก JSON Tokens:
|
||||
{
|
||||
"themeName": "ชื่อธีม (เช่น Cyberpunk Amber, Midnight Velvet, Nordic Forest)",
|
||||
"mode": "dark", // "dark" หรือ "light"
|
||||
"description": "คำอธิบาย Mood & Tone สั้นๆ",
|
||||
"tokens": {
|
||||
"primary": "#06b6d4",
|
||||
"primaryLight": "#22d3ee",
|
||||
"primaryDark": "#0891b2",
|
||||
"secondary": "#a855f7",
|
||||
"background": "#0b0f19",
|
||||
"surface": "#111827",
|
||||
"surfaceAlt": "#1f2937",
|
||||
"text": "#f9fafb",
|
||||
"textMuted": "#9ca3af",
|
||||
"border": "#374151",
|
||||
"glow": "rgba(6, 182, 212, 0.4)",
|
||||
"fontFamilyHeading": "'Plus Jakarta Sans', sans-serif",
|
||||
"fontFamilyBody": "'Inter', sans-serif",
|
||||
"borderRadius": "0.75rem"
|
||||
}
|
||||
}
|
||||
|
||||
### กฎสำคัญ:
|
||||
1. สีต้องมีความกลมกลืน (Harmonious contrast ratio) ผ่านเกณฑ์ WCAG AA สำหรับ Text
|
||||
2. คืนค่าเฉพาะ Valid JSON Object เท่านั้น ห้ามใส่คำบรรยายอื่นนอก JSON`
|
||||
},
|
||||
{
|
||||
title: "Luxury Scent Storytelling & Copywriting",
|
||||
category: "copywriting",
|
||||
description: "เขียนคำบรรยายกลิ่นแบบวรรณกรรม Luxury สร้างภาพจินตนาการ พร้อมเรื่องราว บรรยากาศ (Mood & Atmosphere) และเวลาที่เหมาะสม",
|
||||
tags: ["Copywriting", "Luxury", "Storytelling", "Marketing"],
|
||||
isSystem: true,
|
||||
content: `คุณคือ Creative Director และ Luxury Fragrance Copywriter ระดับสากล (สไตล์ Diptyque, Le Labo, Byredo, Jo Malone)
|
||||
ช่วยเขียนบทบรรยายสินค้ากลิ่นหอมสำหรับ:
|
||||
- **ชื่อกลิ่น**: [ระบุชื่อกลิ่น]
|
||||
- **บุคลิกของกลิ่น**: [ระบุ เช่น อบอุ่น สงบ ลึกลับ สดชื่นยามเช้า]
|
||||
- **กลุ่มเป้าหมาย**: ลูกค้าที่มีรสนิยม มองหาความผ่อนคลายและสร้างบรรยากาศพิเศษในบ้าน
|
||||
|
||||
### โครงสร้างเนื้อหาที่ต้องการ:
|
||||
1. **Hook Title / Headline**: พาดหัวชวนฝัน 1 ประโยคที่สร้างอารมณ์ในทันที
|
||||
2. **Scent Narrative (120-150 คำ)**: เล่าเรื่องบรรยากาศ เวลา ความทรงจำ และความรู้สึกเมื่อกลิ่นนี้ลอยฟุ้งในห้อง
|
||||
3. **Olfactory Pyramid (ปิรามิดกลิ่น)**:
|
||||
- **Top Notes**: (กลิ่นแรกที่สัมผัส - ชัดเจน สดชื่น หรือดึงดูด)
|
||||
- **Heart Notes**: (หัวใจของกลิ่น - ลึกซึ้ง มีเอกลักษณ์)
|
||||
- **Base Notes**: (กลิ่นติดตรึง - อบอุ่น ติดทนนาน)
|
||||
4. **Ideal Setting & Moment**: เวลาและสถานที่ที่เหมาะที่สุด (เช่น ฝนตกยามค่ำคืน, อ่านหนังสือมุมโปรด, ห้องนอนก่อนนอน)
|
||||
5. **Key Benefit & Mood**: ความรู้สึกที่ได้รับ (เช่น Calming, Uplifting, Grounding, Sensual)
|
||||
|
||||
ให้ใช้ภาษาไทยที่สละสลวย มีเสน่ห์ ชวนสัมผัสความอบอุ่น และแปลเวอร์ชันภาษาอังกฤษ (English) แนบมาด้วย`
|
||||
},
|
||||
{
|
||||
title: "CMS Page Builder Section Payloads",
|
||||
category: "cms",
|
||||
description: "สร้าง Payload บล็อกเนื้อหาสำหรับ Page Builder เช่น HeroArticle, EditorialCarousel, ScentBreakdown, QuoteCallout",
|
||||
tags: ["CMS", "PageBuilder", "Payload", "Blocks"],
|
||||
isSystem: true,
|
||||
content: `คุณคือ Headless CMS Architect เชี่ยวชาญการกำหนดโครงสร้าง Dynamic Page Sections สำหรับระบบ HomAroma Page Builder
|
||||
จงสร้าง JSON Array ของ Block Sections สำหรับหน้ารายละเอียดหรือ Landing Page:
|
||||
|
||||
### รูปแบบ Section Type ที่ระบบรองรับ:
|
||||
1. **HeroArticle**: { "type": "HeroArticle", "data": { "title": "...", "subtitle": "...", "ctaText": "...", "ctaLink": "...", "badge": "..." } }
|
||||
2. **ScentNotesBreakdown**: { "type": "ScentNotesBreakdown", "data": { "topNotes": ["..."], "heartNotes": ["..."], "baseNotes": ["..."], "story": "..." } }
|
||||
3. **QuoteCallout**: { "type": "QuoteCallout", "data": { "quote": "...", "author": "...", "role": "..." } }
|
||||
4. **EditorialTile**: { "type": "EditorialTile", "data": { "heading": "...", "paragraph": "...", "alignment": "left" } }
|
||||
5. **Accordion**: { "type": "Accordion", "data": { "items": [{ "question": "...", "answer": "..." }] } }
|
||||
|
||||
ตอบเฉพาะ JSON Array ของ Sections ที่สมบูรณ์ พร้อมรองรับหลายภาษาในแต่ละ Object`
|
||||
},
|
||||
{
|
||||
title: "Multilingual JSON Translation & Localization",
|
||||
category: "translation",
|
||||
description: "แปลภาษา JSON Dictionary หรือ Object จากภาษาไทยเป็นอังกฤษ, จีน, ญี่ปุ่น โดยคงโครงสร้าง Key-Value ครบถ้วน",
|
||||
tags: ["Translation", "i18n", "Localization", "JSON"],
|
||||
isSystem: true,
|
||||
content: `คุณคือ Professional Localization Specialist เชี่ยวชาญงานแปล e-Commerce หรูสำหรับภาษา ไทย (TH), อังกฤษ (EN), จีนตัวย่อ (ZH) และ ญี่ปุ่น (JA)
|
||||
|
||||
### คำสั่งงาน:
|
||||
1. รับ JSON Object ต้นฉบับ
|
||||
2. แปลเฉพาะส่วนของ Value ข้อความ โดย **ห้ามแก้ไข Key** และ **ห้ามเปลี่ยนโครงสร้าง Hierarchy** โดยเด็ดขาด
|
||||
3. สำนวนการแปลต้องสุภาพ เป็นทางการ และมีระดับ (Luxury E-commerce Tone)
|
||||
4. คงตัวแปร placeholder เช่น {{name}}, {{count}}, {price} ไว้ตามเดิม
|
||||
5. ส่งผลลัพธ์กลับมาเป็น JSON Format เดียวกัน 100% ไม่ใส่คำอธิบายใดๆ เสริม`
|
||||
}
|
||||
];
|
||||
|
||||
// Helper to auto-seed initial presets if DB is empty
|
||||
async function ensureDefaultPrompts() {
|
||||
const count = await prisma.llmPrompt.count();
|
||||
if (count === 0) {
|
||||
for (const item of DEFAULT_LLM_PROMPTS) {
|
||||
await prisma.llmPrompt.create({
|
||||
data: {
|
||||
title: item.title,
|
||||
category: item.category,
|
||||
description: item.description,
|
||||
tags: item.tags,
|
||||
content: item.content,
|
||||
isSystem: item.isSystem,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const llmPromptsRoute = new Elysia({ prefix: "/llm-prompts" })
|
||||
// GET all prompts
|
||||
.get("/", async () => {
|
||||
await ensureDefaultPrompts();
|
||||
const prompts = await prisma.llmPrompt.findMany({
|
||||
orderBy: [
|
||||
{ isSystem: "desc" },
|
||||
{ id: "desc" },
|
||||
],
|
||||
});
|
||||
return prompts;
|
||||
})
|
||||
|
||||
// GET prompt by id
|
||||
.get("/:id", async ({ params: { id }, set }) => {
|
||||
const promptId = parseInt(id, 10);
|
||||
if (isNaN(promptId)) {
|
||||
set.status = 400;
|
||||
return { error: "Invalid prompt ID" };
|
||||
}
|
||||
const prompt = await prisma.llmPrompt.findUnique({
|
||||
where: { id: promptId },
|
||||
});
|
||||
if (!prompt) {
|
||||
set.status = 404;
|
||||
return { error: "Prompt not found" };
|
||||
}
|
||||
return prompt;
|
||||
})
|
||||
|
||||
// POST create new prompt
|
||||
.use(adminOnly)
|
||||
.post(
|
||||
"/",
|
||||
async ({ body, set }) => {
|
||||
try {
|
||||
const { title, category, description, tags, content, isSystem } = body;
|
||||
const newPrompt = await prisma.llmPrompt.create({
|
||||
data: {
|
||||
title,
|
||||
category: category || "custom",
|
||||
description: description || null,
|
||||
tags: Array.isArray(tags) ? tags : [],
|
||||
content,
|
||||
isSystem: isSystem || false,
|
||||
},
|
||||
});
|
||||
return { success: true, data: newPrompt };
|
||||
} catch (err: any) {
|
||||
console.error("[LlmPrompts] Create error:", err);
|
||||
set.status = 500;
|
||||
return { success: false, error: err.message || "Failed to create prompt" };
|
||||
}
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
title: t.String({ minLength: 1 }),
|
||||
category: t.Optional(t.String()),
|
||||
description: t.Optional(t.Nullable(t.String())),
|
||||
tags: t.Optional(t.Array(t.String())),
|
||||
content: t.String({ minLength: 1 }),
|
||||
isSystem: t.Optional(t.Boolean()),
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
// PUT update prompt
|
||||
.put(
|
||||
"/:id",
|
||||
async ({ params: { id }, body, set }) => {
|
||||
try {
|
||||
const promptId = parseInt(id, 10);
|
||||
if (isNaN(promptId)) {
|
||||
set.status = 400;
|
||||
return { error: "Invalid prompt ID" };
|
||||
}
|
||||
|
||||
const existing = await prisma.llmPrompt.findUnique({
|
||||
where: { id: promptId },
|
||||
});
|
||||
if (!existing) {
|
||||
set.status = 404;
|
||||
return { error: "Prompt not found" };
|
||||
}
|
||||
|
||||
const updated = await prisma.llmPrompt.update({
|
||||
where: { id: promptId },
|
||||
data: {
|
||||
title: body.title !== undefined ? body.title : existing.title,
|
||||
category: body.category !== undefined ? body.category : existing.category,
|
||||
description: body.description !== undefined ? body.description : existing.description,
|
||||
tags: body.tags !== undefined ? body.tags : existing.tags,
|
||||
content: body.content !== undefined ? body.content : existing.content,
|
||||
},
|
||||
});
|
||||
|
||||
return { success: true, data: updated };
|
||||
} catch (err: any) {
|
||||
console.error("[LlmPrompts] Update error:", err);
|
||||
set.status = 500;
|
||||
return { success: false, error: err.message || "Failed to update prompt" };
|
||||
}
|
||||
},
|
||||
{
|
||||
body: t.Object({
|
||||
title: t.Optional(t.String()),
|
||||
category: t.Optional(t.String()),
|
||||
description: t.Optional(t.Nullable(t.String())),
|
||||
tags: t.Optional(t.Array(t.String())),
|
||||
content: t.Optional(t.String()),
|
||||
}),
|
||||
}
|
||||
)
|
||||
|
||||
// DELETE prompt
|
||||
.delete("/:id", async ({ params: { id }, set }) => {
|
||||
try {
|
||||
const promptId = parseInt(id, 10);
|
||||
if (isNaN(promptId)) {
|
||||
set.status = 400;
|
||||
return { error: "Invalid prompt ID" };
|
||||
}
|
||||
|
||||
await prisma.llmPrompt.delete({
|
||||
where: { id: promptId },
|
||||
});
|
||||
|
||||
return { success: true, message: "Prompt deleted successfully" };
|
||||
} catch (err: any) {
|
||||
console.error("[LlmPrompts] Delete error:", err);
|
||||
set.status = 500;
|
||||
return { success: false, error: err.message || "Failed to delete prompt" };
|
||||
}
|
||||
})
|
||||
|
||||
// POST reset default presets
|
||||
.post("/reset-presets", async ({ set }) => {
|
||||
try {
|
||||
// Upsert default presets
|
||||
for (const item of DEFAULT_LLM_PROMPTS) {
|
||||
const existing = await prisma.llmPrompt.findFirst({
|
||||
where: { title: item.title, isSystem: true },
|
||||
});
|
||||
if (!existing) {
|
||||
await prisma.llmPrompt.create({
|
||||
data: {
|
||||
title: item.title,
|
||||
category: item.category,
|
||||
description: item.description,
|
||||
tags: item.tags,
|
||||
content: item.content,
|
||||
isSystem: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
const allPrompts = await prisma.llmPrompt.findMany({
|
||||
orderBy: [{ isSystem: "desc" }, { id: "desc" }],
|
||||
});
|
||||
return { success: true, data: allPrompts };
|
||||
} catch (err: any) {
|
||||
console.error("[LlmPrompts] Reset presets error:", err);
|
||||
set.status = 500;
|
||||
return { success: false, error: err.message || "Failed to reset presets" };
|
||||
}
|
||||
})
|
||||
|
||||
// POST bulk import
|
||||
.post(
|
||||
"/bulk-import",
|
||||
async ({ body, set }) => {
|
||||
try {
|
||||
const items = body as Array<{
|
||||
title: string;
|
||||
category?: string;
|
||||
description?: string;
|
||||
tags?: string[];
|
||||
content: string;
|
||||
}>;
|
||||
|
||||
if (!Array.isArray(items) || items.length === 0) {
|
||||
set.status = 400;
|
||||
return { success: false, error: "Empty or invalid items array" };
|
||||
}
|
||||
|
||||
const createdItems = [];
|
||||
for (const item of items) {
|
||||
if (!item.title || !item.content) continue;
|
||||
const created = await prisma.llmPrompt.create({
|
||||
data: {
|
||||
title: item.title,
|
||||
category: item.category || "custom",
|
||||
description: item.description || null,
|
||||
tags: Array.isArray(item.tags) ? item.tags : [],
|
||||
content: item.content,
|
||||
isSystem: false,
|
||||
},
|
||||
});
|
||||
createdItems.push(created);
|
||||
}
|
||||
|
||||
return { success: true, count: createdItems.length, data: createdItems };
|
||||
} catch (err: any) {
|
||||
console.error("[LlmPrompts] Bulk import error:", err);
|
||||
set.status = 500;
|
||||
return { success: false, error: err.message || "Failed to import prompts" };
|
||||
}
|
||||
},
|
||||
{
|
||||
body: t.Array(
|
||||
t.Object({
|
||||
title: t.String(),
|
||||
category: t.Optional(t.String()),
|
||||
description: t.Optional(t.Nullable(t.String())),
|
||||
tags: t.Optional(t.Array(t.String())),
|
||||
content: t.String(),
|
||||
})
|
||||
),
|
||||
}
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Elysia, t } from "elysia";
|
||||
import prisma from "../lib/prisma";
|
||||
// Trigger reload: resilient position and contentWidth support
|
||||
// Trigger reload: support landscape and wide aspect ratios for FEATURED_TILES and EDITORIAL_TILE
|
||||
import {
|
||||
LocalizedStringSchema,
|
||||
HeroSectionPayloadSchema,
|
||||
|
||||
Reference in new issue
Block a user