diff --git a/client/app/page.js b/client/app/page.js index ded1461..458a671 100644 --- a/client/app/page.js +++ b/client/app/page.js @@ -88,6 +88,7 @@ export default function AssistantDashboard() { useEffect(() => { setMounted(true); fetchSessions(); + fetchSkills(); }, []); const fetchSessions = async () => { @@ -110,6 +111,15 @@ export default function AssistantDashboard() { } }; + const fetchSkills = async () => { + try { + const { data } = await axios.get(`${API}/agent-skills`); + setSkills(data); + } catch (err) { + console.error("Failed to fetch skills:", err); + } + }; + const deleteSession = async (sessionId, sessionName) => { if (!window.confirm(`Delete chat "${sessionName || "Untitled"}"? This cannot be undone.`)) return; @@ -126,6 +136,22 @@ export default function AssistantDashboard() { setAttachments(prev => prev.filter(a => a.url !== url)); }; + const selectMention = (item, type) => { + const before = input.substring(0, mentionCursorPos); + const after = input.substring(textareaRef.current.selectionStart); + + if (type === "skill") { + setActiveSkill(item); + setInput(before + after); + } else { + const insertion = `@${item.asset_label || "asset"}`; + setInput(before + insertion + after); + } + + setShowMentionPopup(false); + setTimeout(() => textareaRef.current?.focus(), 10); + }; + const processFile = async (file) => { if (!file) return; setUploading(true); @@ -207,6 +233,7 @@ export default function AssistantDashboard() { }; if (skill) { + url += `&skill=${encodeURIComponent(skill.name)}`; const primaryInputKey = skill.inputs?.[0] || "premise"; await axios.post(`${API}/sessions/${sessionId}/run-skill`, { skill_name: skill.name, @@ -215,6 +242,7 @@ export default function AssistantDashboard() { model: "gpt-4o" }); } else { + url += `&q=${encodeURIComponent(initialMsg)}`; await axios.post(`${API}/sessions/${sessionId}/chat`, { message: initialMsg, messages_snapshot: [userMsg], @@ -223,7 +251,7 @@ export default function AssistantDashboard() { } } - router.push(`/canvas?session=${sessionId}`); + router.push(url); } catch (err) { toast.error("Failed to start session"); } @@ -238,6 +266,9 @@ export default function AssistantDashboard() { } }; + const filteredSkills = skills.filter(s => s.name.toLowerCase().includes(mentionQuery.toLowerCase())); + const filteredAssets = attachments.map((a, i) => ({ ...a, asset_label: `asset_${i+1}` })).filter(a => a.asset_label.includes(mentionQuery.toLowerCase())); + if (!mounted) return null; @@ -264,13 +295,31 @@ export default function AssistantDashboard() { ref={textareaRef} value={input} autoFocus - onChange={(e) => setInput(e.target.value)} + onChange={(e) => { + const val = e.target.value; + const pos = e.target.selectionStart; + setInput(val); + + const lastAtPos = val.lastIndexOf("@", pos - 1); + if (lastAtPos !== -1 && (lastAtPos === 0 || val[lastAtPos - 1] === " ")) { + const query = val.substring(lastAtPos + 1, pos); + if (!query.includes(" ")) { + setMentionQuery(query); + setMentionCursorPos(lastAtPos); + setShowMentionPopup(true); + } else { + setShowMentionPopup(false); + } + } else { + setShowMentionPopup(false); + } + }} onKeyDown={handleKey} placeholder={placeholderText} className="w-full bg-transparent border-none focus:ring-0 text-lg p-4 h-24 resize-none placeholder:text-secondary-text/50 outline-none scrollbar-subtle" />
-
+
)} + - {/* Attachment Preview Bar */} - {attachments.length > 0 && ( -
+ {/* Mention & Attachment Preview Bar */} + {(uploading || attachments.length > 0 || input.includes("@")) && ( +
{attachments.map((att, i) => (
setHoveredAsset(att)} onMouseLeave={() => setHoveredAsset(null)} >
{att.kind === "image" ? : }
- asset_{i+1} - + {`asset_${i+1}`}
))} + + {/* Detection for @asset_N in text */} + {input.match(/@asset_\d+/g)?.map(match => { + const index = parseInt(match.split('_')[1]) - 1; + const asset = attachments[index]; + if (!asset) return null; + return ( +
setHoveredAsset(asset)} + onMouseLeave={() => setHoveredAsset(null)} + > +
+ {asset.kind === "image" ? : } +
+ {match} +
+ ); + })} + + {uploading && ( +
+
+ {uploadProgress}% +
+ )} +
+ )} + + {hoveredAsset && ( +
+ {hoveredAsset.kind === "image" ? ( + + ) : hoveredAsset.kind === "video" ? ( +
+ )} + + {showMentionPopup && ( +
+
+
+ Mentions +
+
+ {filteredSkills.length > 0 && ( +
Skills
+ )} + {filteredSkills.map(skill => ( + + ))} + {filteredSkills.length === 0 && ( +
No matches found
+ )} +
+
+
+ )} +
+
+
+ {showSkillsMenu && ( +
+
setShowSkillsMenu(false)} /> +
+
+
+
+ +
+
+

Agent Skills

+

Power up your creative workflow with specialized AI experts.

+
+
+ +
+
+ {skills.map(s => ( + + ))} +
+
+
+ + Design Protocol v1.2 +
+ +
+
+
+ )} +
+ {activeSkill && ( +
+ + {activeSkill.name} +
)}
+ {attachments.length > 0 && ( +
+ {attachments.map((a, i) => ( +
+ {a.kind === "image" ? :
} +
+ ))} + +
+ )}
- - {hoveredAsset && ( -
- {hoveredAsset.kind === "image" ? ( - - ) : hoveredAsset.kind === "video" ? ( -
- )}