feat: 智能识别多配方逐条编辑、椰子油单位识别、名称修复
Some checks failed
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Failing after 6s
Test / e2e-test (push) Has been skipped
Test / build-check (push) Successful in 4s
PR Preview / test (pull_request) Failing after 6s
PR Preview / deploy-preview (pull_request) Has been skipped
Some checks failed
PR Preview / teardown-preview (pull_request) Has been skipped
Test / unit-test (push) Failing after 6s
Test / e2e-test (push) Has been skipped
Test / build-check (push) Successful in 4s
PR Preview / test (pull_request) Failing after 6s
PR Preview / deploy-preview (pull_request) Has been skipped
- 多配方识别后逐条填入完整编辑表单,保存后自动加载下一条 - 队列指示条可切换/删除/全部保存,表单修改实时同步 - 椰子油写滴数→单次模式,写ml→对应容量模式 - 2字以下不做编辑距离模糊匹配,避免"美容"→"宽容" - 首个非精油带数字的词识别为配方名(如"美容1"→名称"美容") - 无名配方留空,点击直接输入 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -86,7 +86,8 @@ export function findOil(input, oilNames) {
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Edit distance fuzzy match
|
||||
// 5. Edit distance fuzzy match (only for 3+ char inputs to avoid false positives)
|
||||
if (trimmed.length < 3) return null
|
||||
let bestMatch = null
|
||||
let bestDist = Infinity
|
||||
for (const name of oilNames) {
|
||||
@@ -158,9 +159,11 @@ export function parseOilChunk(text, oilNames) {
|
||||
let amount = parseFloat(match[2])
|
||||
const unit = match[3] || ''
|
||||
|
||||
const isMl = unit && (unit.toLowerCase() === 'ml' || unit === '毫升')
|
||||
let drops = amount
|
||||
// Convert ml to drops
|
||||
if (unit && (unit.toLowerCase() === 'ml' || unit === '毫升')) {
|
||||
amount = Math.round(amount * 20)
|
||||
if (isMl) {
|
||||
drops = Math.round(amount * 20)
|
||||
}
|
||||
|
||||
// Try greedy match on the name part
|
||||
@@ -170,14 +173,18 @@ export function parseOilChunk(text, oilNames) {
|
||||
for (let i = 0; i < matched.length - 1; i++) {
|
||||
results.push({ oil: matched[i], drops: 1 })
|
||||
}
|
||||
results.push({ oil: matched[matched.length - 1], drops: amount })
|
||||
const item = { oil: matched[matched.length - 1], drops }
|
||||
if (isMl) { item._ml = amount }
|
||||
results.push(item)
|
||||
} else {
|
||||
// Try findOil as fallback
|
||||
const found = findOil(namePart, oilNames)
|
||||
if (found) {
|
||||
results.push({ oil: found, drops: amount })
|
||||
const item = { oil: found, drops }
|
||||
if (isMl) { item._ml = amount }
|
||||
results.push(item)
|
||||
} else if (namePart) {
|
||||
results.push({ oil: namePart, drops: amount, notFound: true })
|
||||
results.push({ oil: namePart, drops, notFound: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -291,7 +298,7 @@ export function parseSingleBlock(raw, oilNames) {
|
||||
}
|
||||
|
||||
return {
|
||||
name: name || '未命名配方',
|
||||
name: name || '',
|
||||
ingredients: deduped,
|
||||
notFound
|
||||
}
|
||||
@@ -358,19 +365,23 @@ export function parseMultiRecipes(raw, oilNames) {
|
||||
|
||||
for (const part of parts) {
|
||||
const hasNumber = /\d/.test(part)
|
||||
const textPart = part.replace(/\d+\.?\d*/g, '').trim()
|
||||
const hasOil = oilNames.some(oil => part.includes(oil)) ||
|
||||
Object.keys(OIL_HOMOPHONES).some(alias => part.includes(alias))
|
||||
// Also check fuzzy: 3+ char parts
|
||||
const fuzzyOil = !hasOil && part.replace(/\d+\.?\d*/g, '').length >= 2 &&
|
||||
findOil(part.replace(/\d+\.?\d*/g, '').trim(), oilNames)
|
||||
const fuzzyOil = !hasOil && textPart.length >= 2 &&
|
||||
findOil(textPart, oilNames)
|
||||
// First part only: has number but text is not any oil → likely a name like "美容1"
|
||||
const isFirstNameWithNumber = !current.foundOil && current.nameParts.length === 0 &&
|
||||
current.ingredientParts.length === 0 && hasNumber && !hasOil && !fuzzyOil && textPart.length >= 2
|
||||
|
||||
if (current.foundOil && !hasOil && !fuzzyOil && !hasNumber && part.length >= 2) {
|
||||
// New recipe starts
|
||||
recipes.push(current)
|
||||
current = { nameParts: [], ingredientParts: [], foundOil: false }
|
||||
current.nameParts.push(part)
|
||||
} else if (!current.foundOil && !hasOil && !fuzzyOil && !hasNumber) {
|
||||
current.nameParts.push(part)
|
||||
} else if ((!current.foundOil && !hasOil && !fuzzyOil && !hasNumber) || isFirstNameWithNumber) {
|
||||
current.nameParts.push(isFirstNameWithNumber ? textPart : part)
|
||||
} else {
|
||||
current.foundOil = true
|
||||
current.ingredientParts.push(part)
|
||||
@@ -401,7 +412,7 @@ export function parseMultiRecipes(raw, oilNames) {
|
||||
}
|
||||
}
|
||||
return {
|
||||
name: r.nameParts.join(' ') || '未命名配方',
|
||||
name: r.nameParts.join(' ') || '',
|
||||
ingredients: deduped,
|
||||
notFound,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user