mirror of
https://git.meshkee.com/Meshkee/dashboards.git
synced 2026-08-11 22:30:58 +04:30
Normalize AI product HTML for the rich text description editor.
Sanitize model output to an allowlisted fragment and convert plain/markdown fallbacks so description text stays inside the advanced editor. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
8d59166fe8
commit
404c008ced
@@ -60,7 +60,7 @@ export function buildProductDescriptionAiPrompt(ctx: {
|
||||
const category = ctx.categoryName || '—'
|
||||
|
||||
if (ctx.locale === 'fa') {
|
||||
return `یک توضیحات فنی محصول به صورت HTML بنویس.
|
||||
return `یک توضیحات فنی محصول به صورت HTML برای ادیتور متن پیشرفته بنویس.
|
||||
|
||||
نام محصول (فارسی): ${nameFa}
|
||||
نام محصول (انگلیسی): ${nameEn}
|
||||
@@ -77,10 +77,11 @@ export function buildProductDescriptionAiPrompt(ctx: {
|
||||
- از دعوت به خرید، تخفیف، «بهترین انتخاب»، و جملات بازاریابی مبهم پرهیز کن.
|
||||
- مشخصات ساختگی ننویس؛ اگر جزئیات دقیق نداری، روی تعریف محصول و کاربردهای معمول تمرکز کن.
|
||||
- ۲ تا ۴ پاراگراف کوتاه به فارسی.
|
||||
- فقط HTML با تگهای <p>، <ul>، <li>، <strong> و <em> برگردان.`
|
||||
- خروجی باید HTML خام باشد (نه markdown، نه کد بلوک، نه تگهای escapeشده) تا داخل ادیتور پیشرفته قرار بگیرد.
|
||||
- فقط تگهای <p>، <ul>، <li>، <strong> و <em> مجاز است.`
|
||||
}
|
||||
|
||||
return `Write a technical product description as HTML.
|
||||
return `Write a technical product description as HTML for the advanced rich-text editor.
|
||||
|
||||
Product name (FA): ${nameFa}
|
||||
Product name (EN): ${nameEn}
|
||||
@@ -97,7 +98,8 @@ Requirements:
|
||||
- Avoid CTAs, discounts, "best choice", and vague marketing claims.
|
||||
- Do not invent specs; if details are unknown, focus on what the product is and typical uses.
|
||||
- 2–4 short paragraphs in Persian (Farsi).
|
||||
- Return HTML only using <p>, <ul>, <li>, <strong>, and <em>.`
|
||||
- Return raw HTML (not markdown, not code fences, not escaped entities) so it renders inside the advanced editor.
|
||||
- HTML only using <p>, <ul>, <li>, <strong>, and <em>.`
|
||||
}
|
||||
|
||||
interface ProductFieldAiPromptModalProps {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
.wrapper {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
border: 1px solid rgba(148, 163, 184, 0.35);
|
||||
border-radius: var(--radius-sm);
|
||||
background: rgba(255, 255, 255, 0.7);
|
||||
overflow: hidden;
|
||||
transition: border-color 0.2s, box-shadow 0.2s;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.wrapper:focus-within {
|
||||
@@ -45,15 +49,28 @@
|
||||
|
||||
.editorShell {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.editor {
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
min-width: 0;
|
||||
min-height: 140px;
|
||||
padding: var(--field-padding-y) var(--field-padding-x);
|
||||
padding-bottom: 40px;
|
||||
font-size: var(--field-font-size);
|
||||
line-height: 1.5;
|
||||
color: var(--text-primary);
|
||||
outline: none;
|
||||
box-sizing: border-box;
|
||||
overflow-x: hidden;
|
||||
overflow-wrap: break-word;
|
||||
word-break: break-word;
|
||||
unicode-bidi: isolate;
|
||||
}
|
||||
|
||||
.editor:empty::before {
|
||||
@@ -62,9 +79,15 @@
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.editor > * {
|
||||
max-width: 100%;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.editor ul,
|
||||
.editor ol {
|
||||
padding-left: 1.5em;
|
||||
padding-inline-start: 1.5em;
|
||||
padding-inline-end: 0;
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useRef, useEffect, useState, useCallback } from 'react'
|
||||
import { Bold, Italic, Underline, List, ListOrdered, ImagePlus } from 'lucide-react'
|
||||
import { useLocale } from '@meshkee/dashboard-ui'
|
||||
import { uploadMediaFiles } from '../services/mediaService'
|
||||
import styles from './RichTextEditor.module.css'
|
||||
|
||||
@@ -20,6 +21,7 @@ export function RichTextEditor({
|
||||
allowImages = false,
|
||||
editorMinHeight = 140,
|
||||
}: RichTextEditorProps) {
|
||||
const { locale, dir } = useLocale()
|
||||
const editorShellRef = useRef<HTMLDivElement>(null)
|
||||
const editorRef = useRef<HTMLDivElement>(null)
|
||||
const fileInputRef = useRef<HTMLInputElement>(null)
|
||||
@@ -203,8 +205,8 @@ export function RichTextEditor({
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.wrapper}>
|
||||
<div className={styles.toolbar}>
|
||||
<div className={styles.wrapper} dir={dir} lang={locale}>
|
||||
<div className={styles.toolbar} dir="ltr">
|
||||
<button type="button" onClick={() => exec('bold')} title="Bold" aria-label="Bold">
|
||||
<Bold size={16} />
|
||||
</button>
|
||||
@@ -256,6 +258,8 @@ export function RichTextEditor({
|
||||
contentEditable
|
||||
role="textbox"
|
||||
aria-multiline="true"
|
||||
dir={dir}
|
||||
lang={locale}
|
||||
data-placeholder={placeholder}
|
||||
onInput={syncChange}
|
||||
suppressContentEditableWarning
|
||||
|
||||
@@ -82,6 +82,8 @@
|
||||
.fieldWithAi {
|
||||
position: relative;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.aiFillAnchor {
|
||||
|
||||
@@ -352,6 +352,7 @@ export function AddNewProductPage() {
|
||||
value={description}
|
||||
onChange={setDescription}
|
||||
placeholder={t('products.form.descriptionPlaceholder')}
|
||||
editorMinHeight={180}
|
||||
/>
|
||||
<span className={styles.aiFillAnchor}>
|
||||
<Tooltip label={t('products.form.aiFill')}>
|
||||
|
||||
Reference in New Issue
Block a user