diff --git a/apps/webapp/app/components/conversation/conversation-textarea.client.tsx b/apps/webapp/app/components/conversation/conversation-textarea.client.tsx index c8e2a25d..4d427807 100644 --- a/apps/webapp/app/components/conversation/conversation-textarea.client.tsx +++ b/apps/webapp/app/components/conversation/conversation-textarea.client.tsx @@ -19,6 +19,7 @@ import { } from "../ui/select"; import { createSkillSlashCommand, + SkillMention, SkillSlashPluginKey, } from "./slash-command-extension"; @@ -97,6 +98,7 @@ export function ConversationTextarea({ includeChildren: true, }), History, + SkillMention, createSkillSlashCommand(skillsRef), ], immediatelyRender: false, diff --git a/apps/webapp/app/components/conversation/conversation.client.tsx b/apps/webapp/app/components/conversation/conversation.client.tsx index cc3647f7..76a0e602 100644 --- a/apps/webapp/app/components/conversation/conversation.client.tsx +++ b/apps/webapp/app/components/conversation/conversation.client.tsx @@ -27,6 +27,7 @@ import type { LLMModel } from "./conversation-textarea.client"; import Avatar from "boring-avatars"; import { createSkillSlashCommand, + SkillMention, SkillSlashPluginKey, } from "./slash-command-extension"; @@ -124,6 +125,7 @@ export const ConversationNew = ({ Text, HardBreak.configure({ keepMarks: true }), History, + SkillMention, createSkillSlashCommand(skillsRef), ], immediatelyRender: false, diff --git a/apps/webapp/app/components/conversation/slash-command-extension.ts b/apps/webapp/app/components/conversation/slash-command-extension.ts index f392e935..d2735a4f 100644 --- a/apps/webapp/app/components/conversation/slash-command-extension.ts +++ b/apps/webapp/app/components/conversation/slash-command-extension.ts @@ -1,4 +1,4 @@ -import { Extension } from "@tiptap/core"; +import { Extension, Node, mergeAttributes } from "@tiptap/core"; import { PluginKey } from "@tiptap/pm/state"; import { ReactRenderer } from "@tiptap/react"; import Suggestion from "@tiptap/suggestion"; @@ -92,6 +92,43 @@ const SkillCommandList = forwardRef( ); SkillCommandList.displayName = "SkillCommandList"; +export const SkillMention = Node.create({ + name: "skillMention", + group: "inline", + inline: true, + selectable: false, + atom: true, + + addAttributes() { + return { + slug: { + default: null, + parseHTML: (el) => el.getAttribute("data-slug"), + renderHTML: (attrs) => ({ "data-slug": attrs.slug }), + }, + }; + }, + + parseHTML() { + return [{ tag: 'span[data-type="skill-mention"]' }]; + }, + + renderHTML({ node, HTMLAttributes }) { + return [ + "span", + mergeAttributes({ "data-type": "skill-mention" }, HTMLAttributes, { + class: + "skill-mention rounded px-1.5 py-0.5 text-primary bg-primary/15 font-medium text-sm", + }), + `/${node.attrs.slug}`, + ]; + }, + + renderText({ node }) { + return `/${node.attrs.slug}`; + }, +}); + export const SkillSlashPluginKey = new PluginKey("skillSlashCommand"); export function createSkillSlashCommand( @@ -111,7 +148,10 @@ export function createSkillSlashCommand( .chain() .focus() .deleteRange(range) - .insertContent(`/${props.slug} `) + .insertContent([ + { type: "skillMention", attrs: { slug: props.slug } }, + { type: "text", text: " " }, + ]) .run(); }, items: ({ query }: { query: string }) => {