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 && (
+
+ )}
+
+ )}
+
+ {hoveredAsset && (
+
+ {hoveredAsset.kind === "image" ? (
+

+ ) : hoveredAsset.kind === "video" ? (
+
+ ) : (
+
+
+
{hoveredAsset.url.split('/').pop()}
+
+ )}
+
+ )}
+
+ {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" ? (
-
- ) : (
-
-
-
{hoveredAsset.url.split('/').pop()}
-
- )}
-
- )}