diff --git a/package.json b/package.json index 4275a59b..de30340c 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "api:start": "node api/server.js" }, "dependencies": { + "@axe-core/react": "^4.11.3", "@sentry/browser": "8.54.0", "@sentry/react": "8.54.0", "@tensorflow/tfjs-node": "^4.22.0", @@ -62,6 +63,8 @@ "d3-shape": "^3.2.0", "d3-time-format": "^4.1.0", "d3-zoom": "^3.0.0", + "date-fns": "^3.6.0", + "express": "^4.18.2", "i18next": "^23.15.1", "i18next-browser-languagedetector": "^8.0.0", "idb": "^8.0.3", diff --git a/src/components/templates/TemplateRecommender.jsx b/src/components/templates/TemplateRecommender.jsx new file mode 100644 index 00000000..e7aadca2 --- /dev/null +++ b/src/components/templates/TemplateRecommender.jsx @@ -0,0 +1,153 @@ +import React, { useEffect, useMemo, useState } from 'react' +import { + recommendTemplates, + requirementSignature, +} from '../../lib/templateRecommendation' +import { templateFeedbackStore } from '../../lib/templateFeedbackStore' +import TemplateCustomizer from './TemplateCustomizer' + +/** + * TemplateRecommender — Issue #563 + * + * Lets a user describe what they want to build, shows ranked contract-template + * suggestions with the reasons each was picked, and hands the chosen template + * off to the existing TemplateCustomizer. Choosing a template records feedback + * so future suggestions for similar needs improve. + */ +export default function TemplateRecommender() { + const [description, setDescription] = useState('') + const [category, setCategory] = useState('') + const [tagsInput, setTagsInput] = useState('') + const [results, setResults] = useState([]) + const [chosen, setChosen] = useState(null) + const [searched, setSearched] = useState(false) + + // Warm the feedback store once so getBoost has data to read. + useEffect(() => { + templateFeedbackStore.initialize().catch(() => { + // Non-fatal: recommender still works without persisted feedback. + }) + }, []) + + const requirement = useMemo(() => { + const tags = tagsInput + .split(',') + .map((t) => t.trim()) + .filter(Boolean) + return { + description: description.trim() || undefined, + category: category || undefined, + tags: tags.length ? tags : undefined, + } + }, [description, category, tagsInput]) + + const handleRecommend = () => { + const ranked = recommendTemplates(requirement, { + boost: templateFeedbackStore.getBoost, + limit: 5, + }) + setResults(ranked) + setSearched(true) + setChosen(null) + } + + const handleChoose = (template) => { + const signature = requirementSignature(requirement) + templateFeedbackStore.recordChoice(signature, template.id).catch(() => { + // Non-fatal: choosing still proceeds even if persistence fails. + }) + setChosen(template) + } + + if (chosen) { + return ( +
+ + +
+ ) + } + + return ( +
+

Find the right contract template

+

Describe what you want to build and we'll suggest a starting point.

+ + +