diff --git a/apps/business/src/components/ProductSeoModal.module.css b/apps/business/src/components/ProductSeoModal.module.css
index ae8da99..9420796 100644
--- a/apps/business/src/components/ProductSeoModal.module.css
+++ b/apps/business/src/components/ProductSeoModal.module.css
@@ -1,9 +1,3 @@
.modal {
width: min(560px, calc(100vw - 32px));
}
-
-.titleRow {
- display: inline-flex;
- align-items: center;
- gap: 8px;
-}
diff --git a/apps/business/src/components/ProductSeoModal.tsx b/apps/business/src/components/ProductSeoModal.tsx
index 59c450d..550b2f1 100644
--- a/apps/business/src/components/ProductSeoModal.tsx
+++ b/apps/business/src/components/ProductSeoModal.tsx
@@ -1,6 +1,6 @@
import { useEffect, useState } from 'react'
import { createPortal } from 'react-dom'
-import { Search, X } from 'lucide-react'
+import { X } from 'lucide-react'
import { ApiError } from '../lib/api'
import { useT } from '../i18n/useT'
import { getProduct, updateProduct } from '../services/productService'
@@ -107,9 +107,8 @@ export function ProductSeoModal({
>
-
-
- {t('products.seo.title')}
+
+ {t('products.seo.title')}
{productName ?
{productName}
: null}
diff --git a/apps/business/src/components/ProductTechnicalInfoModal.tsx b/apps/business/src/components/ProductTechnicalInfoModal.tsx
index fe1acb8..539bc8e 100644
--- a/apps/business/src/components/ProductTechnicalInfoModal.tsx
+++ b/apps/business/src/components/ProductTechnicalInfoModal.tsx
@@ -1,8 +1,11 @@
import { useEffect, useState } from 'react'
import { createPortal } from 'react-dom'
-import { X } from 'lucide-react'
+import { Sparkles, X } from 'lucide-react'
+import { useLocale } from '@meshkee/dashboard-ui'
import { ApiError } from '../lib/api'
+import { useT } from '../i18n/useT'
import {
+ fillProductTechnicalInfoByAi,
getProductTechnicalInfo,
saveProductTechnicalInfo,
valuesToFormState,
@@ -12,8 +15,10 @@ import type {
ProductTechnicalFormValues,
TechnicalFieldType,
} from '../types/technicalForm'
+import { ProductTechnicalValuesAiPromptModal } from './ProductTechnicalValuesAiPromptModal'
import styles from './VariationsModal.module.css'
import fieldStyles from './TechnicalFormModal.module.css'
+import aiStyles from '../styles/ai.module.css'
interface ProductTechnicalInfoModalProps {
open: boolean
@@ -32,12 +37,17 @@ export function ProductTechnicalInfoModal({
categoryId,
onClose,
}: ProductTechnicalInfoModalProps) {
+ const t = useT()
+ const { locale } = useLocale()
+ const isFa = locale === 'fa'
const [mounted, setMounted] = useState(open)
const [closing, setClosing] = useState(false)
const [form, setForm] = useState
(null)
const [values, setValues] = useState({})
const [isLoading, setIsLoading] = useState(false)
const [isSaving, setIsSaving] = useState(false)
+ const [isAiOpen, setIsAiOpen] = useState(false)
+ const [isAiRunning, setIsAiRunning] = useState(false)
const [error, setError] = useState('')
const [infoMessage, setInfoMessage] = useState('')
@@ -45,8 +55,10 @@ export function ProductTechnicalInfoModal({
if (open) {
setMounted(true)
setClosing(false)
+ setIsAiOpen(false)
} else if (mounted) {
setClosing(true)
+ setIsAiOpen(false)
const timer = setTimeout(() => {
setMounted(false)
setClosing(false)
@@ -75,16 +87,16 @@ export function ProductTechnicalInfoModal({
if (data.message) {
setInfoMessage(data.message)
} else if (!categoryId) {
- setInfoMessage('Assign a category to this product before adding technical data.')
+ setInfoMessage(t('products.technical.needCategory'))
} else if (!data.form) {
- setInfoMessage('No technical form is defined for this product category.')
+ setInfoMessage(t('products.technical.noForm'))
}
} catch (err) {
if (err instanceof DOMException && err.name === 'AbortError') return
if (err instanceof ApiError) {
setError(err.message)
} else {
- setError('Unable to load technical data.')
+ setError(t('products.technical.loadError'))
}
} finally {
setIsLoading(false)
@@ -92,13 +104,13 @@ export function ProductTechnicalInfoModal({
}
useEffect(() => {
- if (!mounted || closing) return
+ if (!mounted || closing || isAiOpen) return
const onKey = (e: KeyboardEvent) => {
if (e.key === 'Escape') onClose()
}
document.addEventListener('keydown', onKey)
return () => document.removeEventListener('keydown', onKey)
- }, [mounted, closing, onClose])
+ }, [mounted, closing, isAiOpen, onClose])
if (!mounted) return null
@@ -145,13 +157,26 @@ export function ProductTechnicalInfoModal({
if (err instanceof ApiError) {
setError(err.message)
} else {
- setError('Unable to save technical data.')
+ setError(t('products.technical.saveError'))
}
} finally {
setIsSaving(false)
}
}
+ async function handleAiRun(prompt: string) {
+ setIsAiRunning(true)
+ setError('')
+ try {
+ const result = await fillProductTechnicalInfoByAi(productId, prompt)
+ setValues((prev) => ({ ...prev, ...result.values }))
+ setInfoMessage(t('products.technical.ai.filled'))
+ setIsAiOpen(false)
+ } finally {
+ setIsAiRunning(false)
+ }
+ }
+
function renderField(field: CategoryTechnicalForm['fields'][number]) {
const value = values[field.key]
const type = field.type as TechnicalFieldType
@@ -173,7 +198,7 @@ export function ProductTechnicalInfoModal({
value={typeof value === 'string' ? value : ''}
onChange={(e) => setFieldValue(field.key, e.target.value)}
>
-
+
{field.options.map((option) => (