mirror of
https://github.com/eliasstepanik/core.git
synced 2026-01-11 16:38:27 +00:00
Feat: sdk for integrations
This commit is contained in:
parent
54e535d57d
commit
2396c0ea57
2
.gitignore
vendored
2
.gitignore
vendored
@ -45,3 +45,5 @@ registry/
|
||||
|
||||
.cursor
|
||||
CLAUDE.md
|
||||
|
||||
.claude
|
||||
@ -1,14 +1,10 @@
|
||||
import { EditorContent, useEditor } from "@tiptap/react";
|
||||
|
||||
import React, { useEffect } from "react";
|
||||
import { Document } from "@tiptap/extension-document";
|
||||
import HardBreak from "@tiptap/extension-hard-break";
|
||||
import { History } from "@tiptap/extension-history";
|
||||
import { Paragraph } from "@tiptap/extension-paragraph";
|
||||
import { Text } from "@tiptap/extension-text";
|
||||
import { useEffect } from "react";
|
||||
import { UserTypeEnum } from "@core/types";
|
||||
import { type ConversationHistory } from "@core/database";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { extensionsForConversation } from "./editor-extensions";
|
||||
|
||||
interface AIConversationItemProps {
|
||||
conversationHistory: ConversationHistory;
|
||||
@ -24,14 +20,7 @@ export const ConversationItem = ({
|
||||
const id = `a${conversationHistory.id.replace(/-/g, "")}`;
|
||||
|
||||
const editor = useEditor({
|
||||
extensions: [
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
HardBreak.configure({
|
||||
keepMarks: true,
|
||||
}),
|
||||
],
|
||||
extensions: [...extensionsForConversation],
|
||||
editable: false,
|
||||
content: conversationHistory.message,
|
||||
});
|
||||
@ -54,7 +43,7 @@ export const ConversationItem = ({
|
||||
isUser && "bg-primary/20 max-w-[500px] rounded-md p-3",
|
||||
)}
|
||||
>
|
||||
<EditorContent editor={editor} />
|
||||
<EditorContent editor={editor} className="editor-container" />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { useFetcher } from "@remix-run/react";
|
||||
import { useFetcher, useNavigate } from "@remix-run/react";
|
||||
import { useEffect, useState, useCallback, useRef } from "react";
|
||||
import {
|
||||
List,
|
||||
@ -7,7 +7,7 @@ import {
|
||||
type ListRowRenderer,
|
||||
} from "react-virtualized";
|
||||
import { format } from "date-fns";
|
||||
import { MessageSquare, Clock } from "lucide-react";
|
||||
import { MessageSquare, Clock, Plus } from "lucide-react";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { Button } from "../ui";
|
||||
|
||||
@ -40,10 +40,13 @@ type ConversationListResponse = {
|
||||
|
||||
export const ConversationList = ({
|
||||
currentConversationId,
|
||||
showNewConversationCTA,
|
||||
}: {
|
||||
currentConversationId?: string;
|
||||
showNewConversationCTA?: boolean;
|
||||
}) => {
|
||||
const fetcher = useFetcher<ConversationListResponse>();
|
||||
const navigate = useNavigate();
|
||||
const [conversations, setConversations] = useState<ConversationItem[]>([]);
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
const [hasNextPage, setHasNextPage] = useState(true);
|
||||
@ -155,7 +158,7 @@ export const ConversationList = ({
|
||||
|
||||
return (
|
||||
<div key={key} style={style}>
|
||||
<div className="p-2">
|
||||
<div className="p-1">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className={cn(
|
||||
@ -194,6 +197,19 @@ export const ConversationList = ({
|
||||
|
||||
return (
|
||||
<div className="flex h-full flex-col">
|
||||
{showNewConversationCTA && (
|
||||
<div className="flex items-center justify-start p-1 pb-0">
|
||||
<Button
|
||||
variant="ghost"
|
||||
className="w-full justify-start gap-2 py-4"
|
||||
onClick={() => {
|
||||
navigate("/home/conversation");
|
||||
}}
|
||||
>
|
||||
<Plus size={14} /> New conversation
|
||||
</Button>
|
||||
</div>
|
||||
)}
|
||||
{/* <div className="border-b">
|
||||
<Input
|
||||
type="text"
|
||||
|
||||
@ -43,9 +43,7 @@ export function ConversationTextarea({
|
||||
return;
|
||||
}
|
||||
|
||||
const data = isLoading
|
||||
? {}
|
||||
: { message: text, title: text, conversationId };
|
||||
const data = isLoading ? {} : { message: text, conversationId };
|
||||
|
||||
submit(data as any, {
|
||||
action: isLoading
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { EditorRoot, EditorContent, Placeholder } from "novel";
|
||||
import { useState, useRef, useCallback } from "react";
|
||||
import { Form, useNavigate, useSubmit } from "@remix-run/react";
|
||||
import { Form, useSubmit } from "@remix-run/react";
|
||||
import { cn } from "~/lib/utils";
|
||||
import { Document } from "@tiptap/extension-document";
|
||||
import HardBreak from "@tiptap/extension-hard-break";
|
||||
@ -21,6 +21,7 @@ export const ConversationNew = ({
|
||||
user: { name: string | null };
|
||||
}) => {
|
||||
const [content, setContent] = useState("");
|
||||
const [title, setTitle] = useState("");
|
||||
const editorRef = useRef<any>(null);
|
||||
|
||||
const submit = useSubmit();
|
||||
@ -31,13 +32,15 @@ export const ConversationNew = ({
|
||||
if (!content.trim()) return;
|
||||
|
||||
submit(
|
||||
{ message: content, title: content },
|
||||
{ message: content, title },
|
||||
{
|
||||
action: "/home/conversation",
|
||||
method: "post",
|
||||
},
|
||||
);
|
||||
e.preventDefault();
|
||||
setContent("");
|
||||
setTitle("");
|
||||
},
|
||||
[content],
|
||||
);
|
||||
@ -71,9 +74,13 @@ export const ConversationNew = ({
|
||||
<div className="flex h-full w-full flex-col items-start justify-start overflow-y-auto p-4">
|
||||
<div className="flex w-full flex-col items-center">
|
||||
<div className="w-full max-w-[90ch]">
|
||||
<h1 className="mx-1 mb-4 text-left text-[32px] font-medium">
|
||||
<h1 className="mx-1 text-left text-[32px] font-medium">
|
||||
Hello <span className="text-primary">{user.name}</span>
|
||||
</h1>
|
||||
|
||||
<p className="text-muted-foreground mx-1 mb-4">
|
||||
Demo UI: basic conversation to showcase memory integration.
|
||||
</p>
|
||||
<div className="bg-background-3 border-border rounded-lg border-1 py-2">
|
||||
<EditorRoot>
|
||||
<EditorContent
|
||||
@ -82,7 +89,7 @@ export const ConversationNew = ({
|
||||
extensions={[
|
||||
Placeholder.configure({
|
||||
placeholder: () => {
|
||||
return "Ask sol...";
|
||||
return "Ask CORE...";
|
||||
},
|
||||
includeChildren: true,
|
||||
}),
|
||||
@ -113,6 +120,7 @@ export const ConversationNew = ({
|
||||
);
|
||||
|
||||
setContent("");
|
||||
setTitle("");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@ -125,7 +133,9 @@ export const ConversationNew = ({
|
||||
)}
|
||||
onUpdate={({ editor }: { editor: any }) => {
|
||||
const html = editor.getHTML();
|
||||
const text = editor.getText();
|
||||
setContent(html);
|
||||
setTitle(text);
|
||||
}}
|
||||
/>
|
||||
</EditorRoot>
|
||||
|
||||
148
apps/webapp/app/components/conversation/editor-extensions.tsx
Normal file
148
apps/webapp/app/components/conversation/editor-extensions.tsx
Normal file
@ -0,0 +1,148 @@
|
||||
import { cx } from "class-variance-authority";
|
||||
import {
|
||||
StarterKit,
|
||||
TiptapLink,
|
||||
HorizontalRule,
|
||||
Placeholder,
|
||||
HighlightExtension,
|
||||
AIHighlight,
|
||||
} from "novel";
|
||||
|
||||
import CodeBlockLowlight from "@tiptap/extension-code-block-lowlight";
|
||||
import Heading from "@tiptap/extension-heading";
|
||||
import Table from "@tiptap/extension-table";
|
||||
import TableCell from "@tiptap/extension-table-cell";
|
||||
import TableHeader from "@tiptap/extension-table-header";
|
||||
import TableRow from "@tiptap/extension-table-row";
|
||||
import { all, createLowlight } from "lowlight";
|
||||
import { mergeAttributes, type Extension } from "@tiptap/react";
|
||||
|
||||
// create a lowlight instance with all languages loaded
|
||||
export const lowlight = createLowlight(all);
|
||||
|
||||
const tiptapLink = TiptapLink.configure({
|
||||
HTMLAttributes: {
|
||||
class: cx("text-primary cursor-pointer"),
|
||||
},
|
||||
});
|
||||
|
||||
const horizontalRule = HorizontalRule.configure({
|
||||
HTMLAttributes: {
|
||||
class: cx("my-2 border-t border-muted-foreground"),
|
||||
},
|
||||
});
|
||||
|
||||
const heading = Heading.extend({
|
||||
renderHTML({ node, HTMLAttributes }) {
|
||||
const hasLevel = this.options.levels.includes(node.attrs.level);
|
||||
const level: 1 | 2 | 3 = hasLevel
|
||||
? node.attrs.level
|
||||
: this.options.levels[0];
|
||||
const levelMap = { 1: "text-2xl", 2: "text-xl", 3: "text-lg" };
|
||||
|
||||
return [
|
||||
`h${level}`,
|
||||
mergeAttributes(this.options.HTMLAttributes, HTMLAttributes, {
|
||||
class: `heading-node h${node.attrs.level}-style ${levelMap[level]} my-[1rem] font-medium`,
|
||||
}),
|
||||
0,
|
||||
];
|
||||
},
|
||||
}).configure({ levels: [1, 2, 3] });
|
||||
|
||||
const defaultPlaceholder = Placeholder.configure({
|
||||
placeholder: ({ node }) => {
|
||||
if (node.type.name === "heading") {
|
||||
return `Heading ${node.attrs.level}`;
|
||||
}
|
||||
if (node.type.name === "image" || node.type.name === "table") {
|
||||
return "";
|
||||
}
|
||||
if (node.type.name === "codeBlock") {
|
||||
return "Type in your code here...";
|
||||
}
|
||||
|
||||
return "";
|
||||
},
|
||||
includeChildren: true,
|
||||
});
|
||||
|
||||
export const getPlaceholder = (placeholder: string | Extension) => {
|
||||
if (!placeholder) {
|
||||
return defaultPlaceholder;
|
||||
}
|
||||
|
||||
if (typeof placeholder === "string") {
|
||||
return Placeholder.configure({
|
||||
placeholder: () => {
|
||||
return placeholder;
|
||||
},
|
||||
includeChildren: true,
|
||||
});
|
||||
}
|
||||
|
||||
return placeholder;
|
||||
};
|
||||
|
||||
export const starterKit = StarterKit.configure({
|
||||
heading: false,
|
||||
history: false,
|
||||
bulletList: {
|
||||
HTMLAttributes: {
|
||||
class: cx("list-disc list-outside pl-4 leading-1 my-1 mb-1.5"),
|
||||
},
|
||||
},
|
||||
orderedList: {
|
||||
HTMLAttributes: {
|
||||
class: cx("list-decimal list-outside pl-4 leading-1 my-1"),
|
||||
},
|
||||
},
|
||||
listItem: {
|
||||
HTMLAttributes: {
|
||||
class: cx("mt-1.5"),
|
||||
},
|
||||
},
|
||||
blockquote: {
|
||||
HTMLAttributes: {
|
||||
class: cx("border-l-4 border-gray-400 dark:border-gray-500"),
|
||||
},
|
||||
},
|
||||
paragraph: {
|
||||
HTMLAttributes: {
|
||||
class: cx("leading-[24px] mt-[1rem] paragraph-node"),
|
||||
},
|
||||
},
|
||||
codeBlock: false,
|
||||
code: {
|
||||
HTMLAttributes: {
|
||||
class: cx(
|
||||
"rounded bg-grayAlpha-100 text-[#BF4594] px-1.5 py-1 font-mono font-medium border-none",
|
||||
),
|
||||
spellcheck: "false",
|
||||
},
|
||||
},
|
||||
horizontalRule: false,
|
||||
dropcursor: {
|
||||
color: "#DBEAFE",
|
||||
width: 4,
|
||||
},
|
||||
gapcursor: false,
|
||||
});
|
||||
|
||||
export const extensionsForConversation = [
|
||||
starterKit,
|
||||
tiptapLink,
|
||||
horizontalRule,
|
||||
heading,
|
||||
AIHighlight,
|
||||
HighlightExtension,
|
||||
Table.configure({
|
||||
resizable: true,
|
||||
}),
|
||||
TableRow,
|
||||
TableHeader,
|
||||
TableCell,
|
||||
CodeBlockLowlight.configure({
|
||||
lowlight,
|
||||
}),
|
||||
];
|
||||
@ -1,13 +1,8 @@
|
||||
import { EditorContent, useEditor } from "@tiptap/react";
|
||||
import React from "react";
|
||||
import { Document } from "@tiptap/extension-document";
|
||||
import HardBreak from "@tiptap/extension-hard-break";
|
||||
import { History } from "@tiptap/extension-history";
|
||||
import { Paragraph } from "@tiptap/extension-paragraph";
|
||||
import { Text } from "@tiptap/extension-text";
|
||||
|
||||
import { useTriggerStream } from "./use-trigger-stream";
|
||||
import { Placeholder } from "novel";
|
||||
import { extensionsForConversation } from "./editor-extensions";
|
||||
|
||||
interface StreamingConversationProps {
|
||||
runId: string;
|
||||
@ -41,15 +36,7 @@ export const StreamingConversation = ({
|
||||
];
|
||||
|
||||
const messagesEditor = useEditor({
|
||||
extensions: [
|
||||
Placeholder,
|
||||
Document,
|
||||
Paragraph,
|
||||
Text,
|
||||
HardBreak.configure({
|
||||
keepMarks: true,
|
||||
}),
|
||||
],
|
||||
extensions: [...extensionsForConversation],
|
||||
editable: false,
|
||||
content: "",
|
||||
});
|
||||
@ -94,7 +81,7 @@ export const StreamingConversation = ({
|
||||
{message ? (
|
||||
<EditorContent
|
||||
editor={messagesEditor}
|
||||
className="text-foreground"
|
||||
className="text-foreground editor-container"
|
||||
/>
|
||||
) : (
|
||||
<div className="text-foreground italic">{loadingText}</div>
|
||||
|
||||
@ -179,7 +179,8 @@ export const Graph = forwardRef<GraphRef, GraphProps>(
|
||||
relations: [],
|
||||
relationData: [],
|
||||
label: "",
|
||||
color: theme.link.stroke,
|
||||
color: "#0000001A",
|
||||
labelColor: "#0000001A",
|
||||
size: 1,
|
||||
};
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ import { LLMMappings, LLMModelEnum } from "@core/types";
|
||||
import {
|
||||
type CoreMessage,
|
||||
type LanguageModelV1,
|
||||
embed,
|
||||
generateText,
|
||||
streamText,
|
||||
} from "ai";
|
||||
@ -77,3 +78,29 @@ export async function makeModelCall(
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
export async function getEmbedding(text: string) {
|
||||
const ollamaUrl = env.OLLAMA_URL;
|
||||
|
||||
if (!ollamaUrl) {
|
||||
// Use OpenAI embedding model when explicitly requested
|
||||
const { embedding } = await embed({
|
||||
model: openai.embedding("text-embedding-3-small"),
|
||||
value: text,
|
||||
});
|
||||
return embedding;
|
||||
}
|
||||
|
||||
// Default to using Ollama
|
||||
const model = env.EMBEDDING_MODEL;
|
||||
|
||||
const ollama = createOllama({
|
||||
baseURL: ollamaUrl,
|
||||
});
|
||||
const { embedding } = await embed({
|
||||
model: ollama.embedding(model),
|
||||
value: text,
|
||||
});
|
||||
|
||||
return embedding;
|
||||
}
|
||||
|
||||
@ -138,7 +138,10 @@ export default function SingleConversation() {
|
||||
collapsedSize={16}
|
||||
className="border-border h-[calc(100vh_-_60px)] min-w-[200px] border-r-1"
|
||||
>
|
||||
<ConversationList currentConversationId={conversationId} />
|
||||
<ConversationList
|
||||
currentConversationId={conversationId}
|
||||
showNewConversationCTA
|
||||
/>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle className="w-1" />
|
||||
|
||||
|
||||
@ -41,8 +41,8 @@ export default function Home() {
|
||||
<SidebarInset className="bg-background h-[100vh] py-2 pr-2">
|
||||
<div className="bg-background-2 h-full rounded-md">
|
||||
<SiteHeader />
|
||||
<div className="flex flex-1 flex-col">
|
||||
<div className="@container/main flex flex-1 flex-col gap-2">
|
||||
<div className="flex h-[calc(100vh_-_60px)] flex-col">
|
||||
<div className="@container/main flex h-full flex-col gap-2">
|
||||
<div className="flex h-full flex-col">
|
||||
<Outlet />
|
||||
</div>
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import { type CoreMessage, embed } from "ai";
|
||||
import { type CoreMessage } from "ai";
|
||||
import {
|
||||
type AddEpisodeParams,
|
||||
type EntityNode,
|
||||
@ -35,39 +34,16 @@ import {
|
||||
saveTriple,
|
||||
searchStatementsByEmbedding,
|
||||
} from "./graphModels/statement";
|
||||
import { makeModelCall } from "~/lib/model.server";
|
||||
import { getEmbedding, makeModelCall } from "~/lib/model.server";
|
||||
import { Apps, getNodeTypes, getNodeTypesString } from "~/utils/presets/nodes";
|
||||
import { normalizePrompt } from "./prompts";
|
||||
import { env } from "~/env.server";
|
||||
import { createOllama } from "ollama-ai-provider";
|
||||
|
||||
// Default number of previous episodes to retrieve for context
|
||||
const DEFAULT_EPISODE_WINDOW = 5;
|
||||
|
||||
export class KnowledgeGraphService {
|
||||
async getEmbedding(text: string, useOpenAI = false) {
|
||||
if (useOpenAI) {
|
||||
// Use OpenAI embedding model when explicitly requested
|
||||
const { embedding } = await embed({
|
||||
model: openai.embedding("text-embedding-3-small"),
|
||||
value: text,
|
||||
});
|
||||
return embedding;
|
||||
}
|
||||
|
||||
// Default to using Ollama
|
||||
const ollamaUrl = env.OLLAMA_URL;
|
||||
const model = env.EMBEDDING_MODEL;
|
||||
|
||||
const ollama = createOllama({
|
||||
baseURL: ollamaUrl,
|
||||
});
|
||||
const { embedding } = await embed({
|
||||
model: ollama.embedding(model),
|
||||
value: text,
|
||||
});
|
||||
|
||||
return embedding;
|
||||
async getEmbedding(text: string) {
|
||||
return getEmbedding(text);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import { openai } from "@ai-sdk/openai";
|
||||
import type { StatementNode } from "@core/types";
|
||||
import { embed } from "ai";
|
||||
import { logger } from "./logger.service";
|
||||
import { applyCrossEncoderReranking, applyWeightedRRF } from "./search/rerank";
|
||||
import {
|
||||
@ -9,6 +7,7 @@ import {
|
||||
performBM25Search,
|
||||
performVectorSearch,
|
||||
} from "./search/utils";
|
||||
import { getEmbedding } from "~/lib/model.server";
|
||||
|
||||
/**
|
||||
* SearchService provides methods to search the reified + temporal knowledge graph
|
||||
@ -16,12 +15,7 @@ import {
|
||||
*/
|
||||
export class SearchService {
|
||||
async getEmbedding(text: string) {
|
||||
const { embedding } = await embed({
|
||||
model: openai.embedding("text-embedding-3-small"),
|
||||
value: text,
|
||||
});
|
||||
|
||||
return embedding;
|
||||
return getEmbedding(text);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -363,8 +363,107 @@
|
||||
@apply text-base;
|
||||
}
|
||||
|
||||
p.is-editor-empty {
|
||||
p.is-editor-empty:before {
|
||||
@apply text-muted-foreground;
|
||||
|
||||
font-size: 14px !important;
|
||||
content: attr(data-placeholder);
|
||||
float: left;
|
||||
height: 0;
|
||||
pointer-events: none;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.title-bar-sigma {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
.quick .header {
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
.editor-container p {
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
.editor-container .heading-node:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.editor-container .paragraph-node:first-child {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.list-item--task {
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
.tasks-component ul {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.ProseMirror:not(.dragging) .ProseMirror-selectednode {
|
||||
@apply bg-grayAlpha-100;
|
||||
|
||||
outline: none !important;
|
||||
transition: background-color 0.2s;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.ProseMirror:not(.dragging) .ProseMirror-selectednode.node-skill {
|
||||
@apply !bg-transparent !pt-2;
|
||||
}
|
||||
|
||||
.prosemirror-dropcursor-block {
|
||||
@apply !bg-primary/50;
|
||||
height: 2px !important;
|
||||
}
|
||||
|
||||
.drag-handle {
|
||||
position: fixed;
|
||||
opacity: 1;
|
||||
transition: opacity ease-in 0.2s;
|
||||
border-radius: 0.25rem;
|
||||
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' style='fill: rgba(0, 0, 0, 0.5)'%3E%3Cpath d='M3,2 C2.44771525,2 2,1.55228475 2,1 C2,0.44771525 2.44771525,0 3,0 C3.55228475,0 4,0.44771525 4,1 C4,1.55228475 3.55228475,2 3,2 Z M3,6 C2.44771525,6 2,5.55228475 2,5 C2,4.44771525 2.44771525,4 3,4 C3.55228475,4 4,4.44771525 4,5 C4,5.55228475 3.55228475,6 3,6 Z M3,10 C2.44771525,10 2,9.55228475 2,9 C2,8.44771525 2.44771525,8 3,8 C3.55228475,8 4,8.44771525 4,9 C4,9.55228475 3.55228475,10 3,10 Z M7,2 C6.44771525,2 6,1.55228475 6,1 C6,0.44771525 6.44771525,0 7,0 C7.55228475,0 8,0.44771525 8,1 C8,1.55228475 7.55228475,2 7,2 Z M7,6 C6.44771525,6 6,5.55228475 6,5 C6,4.44771525 6.44771525,4 7,4 C7.55228475,4 8,4.44771525 8,5 C8,5.55228475 7.55228475,6 7,6 Z M7,10 C6.44771525,10 6,9.55228475 6,9 C6,8.44771525 6.44771525,8 7,8 C7.55228475,8 8,8.44771525 8,9 C8,9.55228475 7.55228475,10 7,10 Z'%3E%3C/path%3E%3C/svg%3E");
|
||||
background-size: calc(0.5em + 0.375rem) calc(0.5em + 0.375rem);
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
width: 1.2rem;
|
||||
height: 1.5rem;
|
||||
z-index: 50;
|
||||
cursor: grab;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--novel-stone-100);
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
&:active {
|
||||
background-color: var(--novel-stone-200);
|
||||
transition: background-color 0.2s;
|
||||
cursor: grabbing;
|
||||
}
|
||||
|
||||
&.hide {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 600px) {
|
||||
display: none;
|
||||
pointer-events: none;
|
||||
}
|
||||
}
|
||||
|
||||
.dark .drag-handle {
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 10 10' style='fill: rgba(255,255,255, 0.5)'%3E%3Cpath d='M3,2 C2.44771525,2 2,1.55228475 2,1 C2,0.44771525 2.44771525,0 3,0 C3.55228475,0 4,0.44771525 4,1 C4,1.55228475 3.55228475,2 3,2 Z M3,6 C2.44771525,6 2,5.55228475 2,5 C2,4.44771525 2.44771525,4 3,4 C3.55228475,4 4,4.44771525 4,5 C4,5.55228475 3.55228475,6 3,6 Z M3,10 C2.44771525,10 2,9.55228475 2,9 C2,8.44771525 2.44771525,8 3,8 C3.55228475,8 4,8.44771525 4,9 C4,9.55228475 3.55228475,10 3,10 Z M7,2 C6.44771525,2 6,1.55228475 6,1 C6,0.44771525 6.44771525,0 7,0 C7.55228475,0 8,0.44771525 8,1 C8,1.55228475 7.55228475,2 7,2 Z M7,6 C6.44771525,6 6,5.55228475 6,5 C6,4.44771525 6.44771525,4 7,4 C7.55228475,4 8,4.44771525 8,5 C8,5.55228475 7.55228475,6 7,6 Z M7,10 C6.44771525,10 6,9.55228475 6,9 C6,8.44771525 6.44771525,8 7,8 C7.55228475,8 8,8.44771525 8,9 C8,9.55228475 7.55228475,10 7,10 Z'%3E%3C/path%3E%3C/svg%3E");
|
||||
}
|
||||
|
||||
|
||||
@ -23,7 +23,7 @@ export const createConversationTitle = task({
|
||||
() => {},
|
||||
undefined,
|
||||
"",
|
||||
LLMMappings.CLAUDESONNET,
|
||||
LLMMappings.GPT41,
|
||||
);
|
||||
|
||||
for await (const chunk of gen) {
|
||||
|
||||
@ -59,6 +59,13 @@
|
||||
"@tiptap/extension-history": "^2.11.9",
|
||||
"@tiptap/extension-paragraph": "^2.11.9",
|
||||
"@tiptap/extension-text": "^2.11.9",
|
||||
"@tiptap/extension-table": "2.11.9",
|
||||
"@tiptap/extension-table-cell": "2.11.9",
|
||||
"@tiptap/extension-heading": "2.11.9",
|
||||
"@tiptap/extension-table-header": "2.11.9",
|
||||
"@tiptap/extension-table-row": "2.11.9",
|
||||
"@tiptap/extension-code-block": "2.11.9",
|
||||
"@tiptap/extension-code-block-lowlight": "^2.11.9",
|
||||
"@tiptap/starter-kit": "2.11.9",
|
||||
"@tiptap/react": "^2.11.9",
|
||||
"@tiptap/pm": "^2.11.9",
|
||||
@ -89,6 +96,7 @@
|
||||
"isbot": "^4.1.0",
|
||||
"jose": "^5.2.3",
|
||||
"lucide-react": "^0.511.0",
|
||||
"lowlight": "^3.3.0",
|
||||
"morgan": "^1.10.0",
|
||||
"nanoid": "3.3.8",
|
||||
"neo4j-driver": "^5.28.1",
|
||||
|
||||
@ -1,21 +0,0 @@
|
||||
{
|
||||
"name": "@echo/core-types",
|
||||
"version": "1.0.0",
|
||||
"description": "Core types for Echo integrations",
|
||||
"main": "dist/index.js",
|
||||
"types": "dist/index.d.ts",
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"dev": "tsc --watch"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^5.0.0"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1 +0,0 @@
|
||||
export * from './integration';
|
||||
@ -1,64 +0,0 @@
|
||||
export enum IntegrationEventType {
|
||||
/**
|
||||
* Setting up or creating an integration account
|
||||
*/
|
||||
SETUP = "setup",
|
||||
|
||||
/**
|
||||
* Processing incoming data from the integration
|
||||
*/
|
||||
PROCESS = "process",
|
||||
|
||||
/**
|
||||
* Identifying which account a webhook belongs to
|
||||
*/
|
||||
IDENTIFY = "identify",
|
||||
|
||||
/**
|
||||
* Scheduled synchronization of data
|
||||
*/
|
||||
SYNC = "sync",
|
||||
}
|
||||
|
||||
export interface IntegrationEventPayload {
|
||||
event: IntegrationEventType;
|
||||
[x: string]: any;
|
||||
}
|
||||
|
||||
export interface Spec {
|
||||
name: string;
|
||||
key: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
mcp?: {
|
||||
command: string;
|
||||
args: string[];
|
||||
env: Record<string, string>;
|
||||
};
|
||||
auth?: {
|
||||
OAuth2?: {
|
||||
token_url: string;
|
||||
authorization_url: string;
|
||||
scopes: string[];
|
||||
scope_identifier?: string;
|
||||
scope_separator?: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
export interface Config {
|
||||
access_token: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface Identifier {
|
||||
id: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export type MessageType = 'spec' | 'data' | 'identifier';
|
||||
|
||||
export interface Message {
|
||||
type: MessageType;
|
||||
data: any;
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"lib": ["ES2020"],
|
||||
"declaration": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["src/**/*", "*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
1
integrations/slack/.gitignore
vendored
1
integrations/slack/.gitignore
vendored
@ -1,2 +1,3 @@
|
||||
bin
|
||||
node_modules
|
||||
|
||||
|
||||
@ -66,6 +66,6 @@
|
||||
"commander": "^12.0.0",
|
||||
"openai": "^4.0.0",
|
||||
"react-query": "^3.39.3",
|
||||
"@echo/core-types": "workspace:*"
|
||||
"@redplanethq/sdk": "0.1.0"
|
||||
}
|
||||
}
|
||||
17
integrations/slack/pnpm-lock.yaml
generated
17
integrations/slack/pnpm-lock.yaml
generated
@ -8,6 +8,9 @@ importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
'@redplanethq/sdk':
|
||||
specifier: 0.1.0
|
||||
version: 0.1.0
|
||||
axios:
|
||||
specifier: ^1.7.9
|
||||
version: 1.9.0
|
||||
@ -489,6 +492,10 @@ packages:
|
||||
resolution: {integrity: sha512-ROFF39F6ZrnzSUEmQQZUar0Jt4xVoP9WnDRdWwF4NNcXs3xBTLgBUDoOwW141y1jP+S8nahIbdxbFC7IShw9Iw==}
|
||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||
|
||||
'@redplanethq/sdk@0.1.0':
|
||||
resolution: {integrity: sha512-RmPfT9XESjTSMLlAMkolZEF28PvGo5hlwrG75JQy1tAZkvaTHzC7A2mEAMbsBvOMrJuUztL3NtCmVF//C/C/+A==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
|
||||
'@rollup/plugin-commonjs@28.0.3':
|
||||
resolution: {integrity: sha512-pyltgilam1QPdn+Zd9gaCfOLcnjMEJ9gV+bTw6/r73INdvzf1ah9zLIJBm+kW7R6IUFIQ1YO+VqZtYxZNWFPEQ==}
|
||||
engines: {node: '>=16.0.0 || 14 >= 14.17'}
|
||||
@ -901,6 +908,10 @@ packages:
|
||||
resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
commander@14.0.0:
|
||||
resolution: {integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
commander@2.20.3:
|
||||
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||
|
||||
@ -2894,6 +2905,10 @@ snapshots:
|
||||
|
||||
'@pkgr/core@0.2.4': {}
|
||||
|
||||
'@redplanethq/sdk@0.1.0':
|
||||
dependencies:
|
||||
commander: 14.0.0
|
||||
|
||||
'@rollup/plugin-commonjs@28.0.3(rollup@4.40.2)':
|
||||
dependencies:
|
||||
'@rollup/pluginutils': 5.1.4(rollup@4.40.2)
|
||||
@ -3320,6 +3335,8 @@ snapshots:
|
||||
|
||||
commander@12.1.0: {}
|
||||
|
||||
commander@14.0.0: {}
|
||||
|
||||
commander@2.20.3: {}
|
||||
|
||||
commander@4.1.1: {}
|
||||
|
||||
@ -1,65 +0,0 @@
|
||||
# IntegrationCLI Base Class
|
||||
|
||||
This is a common CLI base class that can be moved to the SDK and used by all integrations.
|
||||
|
||||
## Usage
|
||||
|
||||
### 1. Create your integration-specific CLI class:
|
||||
|
||||
```typescript
|
||||
import { IntegrationCLI, IntegrationEventPayload } from './common/IntegrationCLI';
|
||||
|
||||
export class MyIntegrationCLI extends IntegrationCLI {
|
||||
constructor() {
|
||||
super('my-integration', '1.0.0');
|
||||
}
|
||||
|
||||
protected async handleEvent(eventPayload: IntegrationEventPayload): Promise<any> {
|
||||
// Your integration-specific logic here
|
||||
return await processMyIntegrationEvent(eventPayload);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Create your CLI entry point:
|
||||
|
||||
```typescript
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { MyIntegrationCLI } from './MyIntegrationCLI';
|
||||
|
||||
const cli = new MyIntegrationCLI();
|
||||
cli.parse();
|
||||
```
|
||||
|
||||
### 3. Update your package.json:
|
||||
|
||||
```json
|
||||
{
|
||||
"bin": {
|
||||
"my-integration": "./dist/cli.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^12.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Available Commands
|
||||
|
||||
The base class provides these commands automatically:
|
||||
|
||||
- `account create --oauth-response <json> --integration-definition <json>`
|
||||
- `account delete --account-id <id>`
|
||||
- `process --event-data <json> --integration-account <json>`
|
||||
- `identify --webhook-data <json>`
|
||||
- `sync --integration-account <json>`
|
||||
|
||||
## Moving to SDK
|
||||
|
||||
To move this to the SDK:
|
||||
|
||||
1. Move `IntegrationCLI.ts` to `@redplanethq/sol-sdk/src/cli/`
|
||||
2. Export it from the SDK's index
|
||||
3. Update imports in integrations to use the SDK version
|
||||
4. Add commander as a dependency to the SDK
|
||||
@ -2,23 +2,24 @@
|
||||
|
||||
import { integrationCreate } from './account-create';
|
||||
import { createActivityEvent } from './create-activity';
|
||||
import { IntegrationCLI } from './common/IntegrationCLI';
|
||||
import { IntegrationEventPayload, Spec } from '@echo/core-types';
|
||||
import {
|
||||
IntegrationCLI,
|
||||
IntegrationEventPayload,
|
||||
IntegrationEventType,
|
||||
Spec,
|
||||
} from '@redplanethq/sdk';
|
||||
|
||||
export async function run(eventPayload: IntegrationEventPayload) {
|
||||
switch (eventPayload.event) {
|
||||
case 'SETUP':
|
||||
case IntegrationEventType.SETUP:
|
||||
return await integrationCreate(eventPayload.eventBody, eventPayload.integrationDefinition);
|
||||
|
||||
case 'IDENTIFY':
|
||||
case IntegrationEventType.IDENTIFY:
|
||||
return eventPayload.eventBody.event.user;
|
||||
|
||||
case 'PROCESS':
|
||||
case IntegrationEventType.PROCESS:
|
||||
return createActivityEvent(eventPayload.eventBody, eventPayload.config);
|
||||
|
||||
case 'SYNC':
|
||||
return { message: 'Scheduled sync completed successfully' };
|
||||
|
||||
default:
|
||||
return {
|
||||
message: `The event payload type is ${eventPayload.event}`,
|
||||
@ -38,45 +39,45 @@ class SlackCLI extends IntegrationCLI {
|
||||
|
||||
protected async getSpec(): Promise<Spec> {
|
||||
return {
|
||||
name: "Slack extension",
|
||||
key: "slack",
|
||||
description: "Connect your workspace to Slack. Run your workflows from slack bookmarks",
|
||||
icon: "slack",
|
||||
name: 'Slack extension',
|
||||
key: 'slack',
|
||||
description: 'Connect your workspace to Slack. Run your workflows from slack bookmarks',
|
||||
icon: 'slack',
|
||||
mcp: {
|
||||
command: "npx",
|
||||
args: ["-y", "@modelcontextprotocol/server-slack"],
|
||||
command: 'npx',
|
||||
args: ['-y', '@modelcontextprotocol/server-slack'],
|
||||
env: {
|
||||
"SLACK_BOT_TOKEN": "${config:access_token}",
|
||||
"SLACK_TEAM_ID": "${config:team_id}",
|
||||
"SLACK_CHANNEL_IDS": "${config:channel_ids}"
|
||||
}
|
||||
SLACK_BOT_TOKEN: '${config:access_token}',
|
||||
SLACK_TEAM_ID: '${config:team_id}',
|
||||
SLACK_CHANNEL_IDS: '${config:channel_ids}',
|
||||
},
|
||||
},
|
||||
auth: {
|
||||
OAuth2: {
|
||||
token_url: "https://slack.com/api/oauth.v2.access",
|
||||
authorization_url: "https://slack.com/oauth/v2/authorize",
|
||||
token_url: 'https://slack.com/api/oauth.v2.access',
|
||||
authorization_url: 'https://slack.com/oauth/v2/authorize',
|
||||
scopes: [
|
||||
"stars:read",
|
||||
"team:read",
|
||||
"stars:write",
|
||||
"users:read",
|
||||
"channels:read",
|
||||
"groups:read",
|
||||
"im:read",
|
||||
"im:history",
|
||||
"mpim:read",
|
||||
"mpim:write",
|
||||
"mpim:history",
|
||||
"channels:history",
|
||||
"chat:write",
|
||||
"reactions:read",
|
||||
"reactions:write",
|
||||
"users.profile:read"
|
||||
'stars:read',
|
||||
'team:read',
|
||||
'stars:write',
|
||||
'users:read',
|
||||
'channels:read',
|
||||
'groups:read',
|
||||
'im:read',
|
||||
'im:history',
|
||||
'mpim:read',
|
||||
'mpim:write',
|
||||
'mpim:history',
|
||||
'channels:history',
|
||||
'chat:write',
|
||||
'reactions:read',
|
||||
'reactions:write',
|
||||
'users.profile:read',
|
||||
],
|
||||
scope_identifier: "user_scope",
|
||||
scope_separator: ","
|
||||
}
|
||||
}
|
||||
scope_identifier: 'user_scope',
|
||||
scope_separator: ',',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
203
packages/sdk/README.md
Normal file
203
packages/sdk/README.md
Normal file
@ -0,0 +1,203 @@
|
||||
# Echo SDK
|
||||
|
||||
The Echo SDK provides tools and utilities for building integrations with the Echo platform.
|
||||
|
||||
## Integration System
|
||||
|
||||
The Echo integration system uses a CLI-based approach where each integration is a command-line tool that responds to specific events. This makes integrations portable, testable, and easy to debug.
|
||||
|
||||
### Integration Event Types
|
||||
|
||||
Each integration CLI handles 5 core event types:
|
||||
|
||||
#### 1. `spec`
|
||||
Returns the integration's metadata and configuration.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
my-integration spec
|
||||
```
|
||||
|
||||
**Returns:** Integration specification including name, description, auth config, etc.
|
||||
|
||||
#### 2. `setup`
|
||||
Processes authentication data and returns tokens/credentials to be saved.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
my-integration setup --event-body '{"code":"oauth_code","state":"state"}' --integration-definition '{}'
|
||||
```
|
||||
|
||||
**Returns:** Configuration data (tokens, credentials) to be stored for the account.
|
||||
|
||||
#### 3. `identify`
|
||||
Extracts accountId from webhook data to route webhooks to the correct account.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
my-integration identify --webhook-data '{"team_id":"T123","event":{}}'
|
||||
```
|
||||
|
||||
**Returns:** Account identifier for webhook routing.
|
||||
|
||||
#### 4. `process`
|
||||
Handles webhook events and returns activity data.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
my-integration process --event-data '{"type":"reaction_added","reaction":"=M"}' --config '{"access_token":"token"}'
|
||||
```
|
||||
|
||||
**Returns:** Activity messages representing user actions.
|
||||
|
||||
#### 5. `sync`
|
||||
Performs scheduled data synchronization for integrations that don't support webhooks.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
my-integration sync --config '{"access_token":"token","last_sync":"2023-01-01T00:00:00Z"}'
|
||||
```
|
||||
|
||||
**Returns:** Activity messages and updated state for next sync.
|
||||
|
||||
### Message Types
|
||||
|
||||
All integration responses are wrapped in a `Message` object with a `type` field:
|
||||
|
||||
- **`spec`** - Integration metadata and configuration
|
||||
- **`activity`** - User actions/events from the integration
|
||||
- **`state`** - Sync state for polling integrations
|
||||
- **`identifier`** - Account identification for webhook routing
|
||||
|
||||
### Building an Integration
|
||||
|
||||
1. **Install the SDK:**
|
||||
```bash
|
||||
npm install @echo/core-sdk
|
||||
```
|
||||
|
||||
2. **Create your integration class:**
|
||||
```typescript
|
||||
import { IntegrationCLI } from '@echo/core-sdk';
|
||||
|
||||
class MyIntegration extends IntegrationCLI {
|
||||
constructor() {
|
||||
super('my-integration', '1.0.0');
|
||||
}
|
||||
|
||||
protected async handleEvent(eventPayload: IntegrationEventPayload): Promise<any> {
|
||||
switch (eventPayload.event) {
|
||||
case 'SETUP':
|
||||
return this.handleSetup(eventPayload);
|
||||
case 'PROCESS':
|
||||
return this.handleProcess(eventPayload);
|
||||
case 'IDENTIFY':
|
||||
return this.handleIdentify(eventPayload);
|
||||
case 'SYNC':
|
||||
return this.handleSync(eventPayload);
|
||||
default:
|
||||
throw new Error(`Unknown event type: ${eventPayload.event}`);
|
||||
}
|
||||
}
|
||||
|
||||
protected async getSpec(): Promise<Spec> {
|
||||
return {
|
||||
name: 'My Integration',
|
||||
key: 'my-integration',
|
||||
description: 'Integration with My Service',
|
||||
icon: 'https://example.com/icon.png',
|
||||
auth: {
|
||||
OAuth2: {
|
||||
token_url: 'https://api.example.com/oauth/token',
|
||||
authorization_url: 'https://api.example.com/oauth/authorize',
|
||||
scopes: ['read', 'write']
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private async handleSetup(eventPayload: IntegrationEventPayload): Promise<any> {
|
||||
// Process OAuth response and return tokens to save
|
||||
const { code } = eventPayload.eventBody;
|
||||
// Exchange code for tokens...
|
||||
return {
|
||||
access_token: 'token',
|
||||
refresh_token: 'refresh_token',
|
||||
expires_at: Date.now() + 3600000
|
||||
};
|
||||
}
|
||||
|
||||
private async handleProcess(eventPayload: IntegrationEventPayload): Promise<any> {
|
||||
// Handle webhook events
|
||||
const { eventData } = eventPayload.eventBody;
|
||||
// Process event and return activity...
|
||||
return {
|
||||
type: 'message',
|
||||
user: 'user123',
|
||||
content: 'Hello world',
|
||||
timestamp: new Date()
|
||||
};
|
||||
}
|
||||
|
||||
private async handleIdentify(eventPayload: IntegrationEventPayload): Promise<any> {
|
||||
// Extract account ID from webhook
|
||||
const { team_id } = eventPayload.eventBody;
|
||||
return { id: team_id };
|
||||
}
|
||||
|
||||
private async handleSync(eventPayload: IntegrationEventPayload): Promise<any> {
|
||||
// Perform scheduled sync
|
||||
const { config } = eventPayload;
|
||||
// Fetch data since last sync...
|
||||
return {
|
||||
activities: [/* activity data */],
|
||||
state: { last_sync: new Date().toISOString() }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// CLI entry point
|
||||
const integration = new MyIntegration();
|
||||
integration.parse();
|
||||
```
|
||||
|
||||
3. **Build and package your integration:**
|
||||
```bash
|
||||
npm run build
|
||||
npm pack
|
||||
```
|
||||
|
||||
### Integration Development
|
||||
|
||||
The `IntegrationCLI` base class provides:
|
||||
|
||||
- **Automatic CLI setup** with all required commands
|
||||
- **JSON input/output handling** for all event types
|
||||
- **Error handling** with proper exit codes
|
||||
- **Consistent message formatting** for all responses
|
||||
|
||||
### Testing
|
||||
|
||||
Test your integration by running commands directly:
|
||||
|
||||
```bash
|
||||
# Test spec
|
||||
node dist/index.js spec
|
||||
|
||||
# Test setup
|
||||
node dist/index.js setup --event-body '{"code":"test"}' --integration-definition '{}'
|
||||
|
||||
# Test webhook processing
|
||||
node dist/index.js process --event-data '{"type":"test"}' --config '{"token":"test"}'
|
||||
```
|
||||
|
||||
### Best Practices
|
||||
|
||||
1. **Always validate input data** before processing
|
||||
2. **Handle errors gracefully** with meaningful error messages
|
||||
3. **Use consistent data structures** for activities
|
||||
4. **Include proper timestamps** in all activity data
|
||||
5. **Store minimal state** for sync operations
|
||||
6. **Test all event types** thoroughly
|
||||
|
||||
For more examples, see the integrations in the `integrations/` directory.
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "@redplanethq/sol-sdk",
|
||||
"version": "0.2.18",
|
||||
"description": "Sol Node.JS SDK",
|
||||
"name": "@redplanethq/sdk",
|
||||
"version": "0.1.0",
|
||||
"description": "CORE Node.JS SDK",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"module": "./dist/index.mjs",
|
||||
@ -29,6 +29,7 @@
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "14.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@core/types": "workspace:*",
|
||||
@ -44,6 +45,5 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
"packageManager": "pnpm@10.3.0"
|
||||
}
|
||||
}
|
||||
@ -1 +1,2 @@
|
||||
export * from '@core/types';
|
||||
export * from './integrations';
|
||||
|
||||
1
packages/sdk/src/integrations/index.ts
Normal file
1
packages/sdk/src/integrations/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export { IntegrationCLI } from './integration_cli';
|
||||
@ -2,10 +2,9 @@ import { Command } from 'commander';
|
||||
import {
|
||||
IntegrationEventPayload,
|
||||
Spec,
|
||||
Config,
|
||||
Identifier,
|
||||
Message
|
||||
} from '@echo/core-types';
|
||||
Message,
|
||||
IntegrationEventType,
|
||||
} from '@core/types';
|
||||
|
||||
export abstract class IntegrationCLI {
|
||||
protected program: Command;
|
||||
@ -32,28 +31,35 @@ export abstract class IntegrationCLI {
|
||||
}
|
||||
|
||||
private setupAccountCommands(): void {
|
||||
const accountCmd = this.program
|
||||
.command('account')
|
||||
.description(`Manage ${this.integrationName} integration accounts`);
|
||||
|
||||
accountCmd
|
||||
.command('create')
|
||||
.description(`Create a new ${this.integrationName} integration account`)
|
||||
.requiredOption('--oauth-response <response>', 'OAuth response JSON')
|
||||
this.program
|
||||
.command('setup')
|
||||
.description(`Set up a new ${this.integrationName} integration account`)
|
||||
.requiredOption(
|
||||
'--event-body <body>',
|
||||
'Event body JSON (e.g. OAuth response or setup data)',
|
||||
)
|
||||
.requiredOption(
|
||||
'--integration-definition <definition>',
|
||||
'Integration definition JSON',
|
||||
)
|
||||
.action(async (options) => {
|
||||
try {
|
||||
const oauthResponse = JSON.parse(options.oauthResponse);
|
||||
const integrationDefinition = JSON.parse(options.integrationDefinition);
|
||||
const eventBody = JSON.parse(options.eventBody);
|
||||
const integrationDefinition = JSON.parse(
|
||||
options.integrationDefinition,
|
||||
);
|
||||
|
||||
const result = await this.handleEvent({
|
||||
event: 'INTEGRATION_ACCOUNT_CREATED',
|
||||
eventBody: { oauthResponse },
|
||||
const messages: Message[] = await this.handleEvent({
|
||||
event: IntegrationEventType.SETUP,
|
||||
eventBody,
|
||||
integrationDefinition,
|
||||
});
|
||||
|
||||
console.log('Account created successfully:', JSON.stringify(result, null, 2));
|
||||
for (const message of messages) {
|
||||
console.log(JSON.stringify(message, null, 2));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error creating account:', error);
|
||||
console.error('Error during setup:', error);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
@ -70,17 +76,15 @@ export abstract class IntegrationCLI {
|
||||
const eventData = JSON.parse(options.eventData);
|
||||
const config = JSON.parse(options.config);
|
||||
|
||||
const result = await this.handleEvent({
|
||||
event: 'PROCESS',
|
||||
const messages: Message[] = await this.handleEvent({
|
||||
event: IntegrationEventType.PROCESS,
|
||||
eventBody: { eventData },
|
||||
config,
|
||||
});
|
||||
|
||||
const message: Message = {
|
||||
type: 'data',
|
||||
data: result
|
||||
};
|
||||
for (const message of messages) {
|
||||
console.log(JSON.stringify(message, null, 2));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error processing data:', error);
|
||||
process.exit(1);
|
||||
@ -95,16 +99,14 @@ export abstract class IntegrationCLI {
|
||||
try {
|
||||
const webhookData = JSON.parse(options.webhookData);
|
||||
|
||||
const result = await this.handleEvent({
|
||||
event: 'IDENTIFY',
|
||||
const messages: Message[] = await this.handleEvent({
|
||||
event: IntegrationEventType.IDENTIFY,
|
||||
eventBody: webhookData,
|
||||
});
|
||||
|
||||
const message: Message = {
|
||||
type: 'identifier',
|
||||
data: result
|
||||
};
|
||||
for (const message of messages) {
|
||||
console.log(JSON.stringify(message, null, 2));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error identifying account:', error);
|
||||
process.exit(1);
|
||||
@ -121,8 +123,9 @@ export abstract class IntegrationCLI {
|
||||
const spec = await this.getSpec();
|
||||
const message: Message = {
|
||||
type: 'spec',
|
||||
data: spec
|
||||
data: spec,
|
||||
};
|
||||
// For spec, we keep the single message output for compatibility
|
||||
console.log(JSON.stringify(message, null, 2));
|
||||
} catch (error) {
|
||||
console.error('Error getting spec:', error);
|
||||
@ -136,21 +139,22 @@ export abstract class IntegrationCLI {
|
||||
.command('sync')
|
||||
.description('Perform scheduled sync')
|
||||
.requiredOption('--config <config>', 'Integration configuration JSON')
|
||||
.option('--state <state>', 'Integration state JSON', '{}')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
const config = JSON.parse(options.config);
|
||||
const state = options.state ? JSON.parse(options.state) : {};
|
||||
|
||||
const result = await this.handleEvent({
|
||||
event: 'SYNC',
|
||||
const messages: Message[] = await this.handleEvent({
|
||||
event: IntegrationEventType.SYNC,
|
||||
eventBody: {},
|
||||
config,
|
||||
state,
|
||||
});
|
||||
|
||||
const message: Message = {
|
||||
type: 'data',
|
||||
data: result
|
||||
};
|
||||
for (const message of messages) {
|
||||
console.log(JSON.stringify(message, null, 2));
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error during sync:', error);
|
||||
process.exit(1);
|
||||
@ -161,8 +165,11 @@ export abstract class IntegrationCLI {
|
||||
/**
|
||||
* Abstract method that must be implemented by each integration
|
||||
* This method should handle the integration-specific logic for each event type
|
||||
* and return an array of Message objects.
|
||||
*/
|
||||
protected abstract handleEvent(eventPayload: IntegrationEventPayload): Promise<any>;
|
||||
protected abstract handleEvent(
|
||||
eventPayload: IntegrationEventPayload,
|
||||
): Promise<Message[]>;
|
||||
|
||||
/**
|
||||
* Abstract method that must be implemented by each integration
|
||||
@ -1,70 +1,63 @@
|
||||
import { Spec } from "./oauth";
|
||||
import { APIKeyParams, OAuth2Params } from "./oauth";
|
||||
|
||||
export enum IntegrationPayloadEventType {
|
||||
export enum IntegrationEventType {
|
||||
/**
|
||||
* When a webhook is received, this event is triggered to identify which integration
|
||||
* account the webhook belongs to
|
||||
* Processes authentication data and returns tokens/credentials to be saved
|
||||
*/
|
||||
IDENTIFY_WEBHOOK_ACCOUNT = "identify_webhook_account",
|
||||
SETUP = "setup",
|
||||
|
||||
/**
|
||||
* Lifecycle events for integration accounts
|
||||
* Processing incoming data from the integration
|
||||
*/
|
||||
INTEGRATION_ACCOUNT_CREATED = "integration_account_created",
|
||||
PROCESS = "process",
|
||||
|
||||
/**
|
||||
* When data is received from the integration source (e.g. new Slack message)
|
||||
* Identifying which account a webhook belongs to
|
||||
*/
|
||||
INTEGRATION_DATA_RECEIVED = "integration_data_received",
|
||||
IDENTIFY = "identify",
|
||||
|
||||
/**
|
||||
* For integrations without webhook support, this event is triggered at the
|
||||
* configured frequency to sync data
|
||||
* Scheduled synchronization of data
|
||||
*/
|
||||
SCHEDULED_SYNC = "scheduled_sync",
|
||||
SYNC = "sync",
|
||||
|
||||
/**
|
||||
* For returning integration metadata/config
|
||||
*/
|
||||
SPEC = "spec",
|
||||
}
|
||||
|
||||
export interface IntegrationEventPayload {
|
||||
event: IntegrationPayloadEventType;
|
||||
event: IntegrationEventType;
|
||||
[x: string]: any;
|
||||
}
|
||||
|
||||
export interface Activity {
|
||||
id: string;
|
||||
type: string;
|
||||
timestamp: string;
|
||||
data: any;
|
||||
export class Spec {
|
||||
name: string;
|
||||
key: string;
|
||||
description: string;
|
||||
icon: string;
|
||||
mcp?: {
|
||||
command: string;
|
||||
args: string[];
|
||||
env: Record<string, string>;
|
||||
};
|
||||
auth?: Record<string, OAuth2Params | APIKeyParams>;
|
||||
}
|
||||
|
||||
export interface IntegrationAccountConfig {
|
||||
export interface Config {
|
||||
access_token: string;
|
||||
team_id?: string;
|
||||
channel_ids?: string;
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export interface IntegrationAccountIdentifier {
|
||||
identifier: string;
|
||||
type: string;
|
||||
export interface Identifier {
|
||||
id: string;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
export interface IntegrationAccountSettings {
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export type MessageType =
|
||||
| "Spec"
|
||||
| "Activity"
|
||||
| "IntegrationAccountConfig"
|
||||
| "IntegrationAccountIdentifier"
|
||||
| "IntegrationAccountSettings";
|
||||
export type MessageType = "spec" | "activity" | "state" | "identifier";
|
||||
|
||||
export interface Message {
|
||||
type: MessageType;
|
||||
data:
|
||||
| Spec
|
||||
| Activity
|
||||
| IntegrationAccountConfig
|
||||
| IntegrationAccountIdentifier
|
||||
| IntegrationAccountSettings;
|
||||
data: any;
|
||||
}
|
||||
|
||||
@ -18,8 +18,3 @@ export class APIKeyParams {
|
||||
"header_name": string;
|
||||
"format": string;
|
||||
}
|
||||
|
||||
export class Spec {
|
||||
auth: Record<string, OAuth2Params | APIKeyParams>;
|
||||
other_data?: any;
|
||||
}
|
||||
|
||||
106
pnpm-lock.yaml
generated
106
pnpm-lock.yaml
generated
@ -156,18 +156,39 @@ importers:
|
||||
'@tanstack/react-table':
|
||||
specifier: ^8.13.2
|
||||
version: 8.21.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@tiptap/extension-code-block':
|
||||
specifier: 2.11.9
|
||||
version: 2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
|
||||
'@tiptap/extension-code-block-lowlight':
|
||||
specifier: ^2.11.9
|
||||
version: 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/extension-code-block@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)(highlight.js@11.11.1)(lowlight@3.3.0)
|
||||
'@tiptap/extension-document':
|
||||
specifier: ^2.11.9
|
||||
version: 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
|
||||
'@tiptap/extension-hard-break':
|
||||
specifier: ^2.11.9
|
||||
version: 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
|
||||
'@tiptap/extension-heading':
|
||||
specifier: 2.11.9
|
||||
version: 2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
|
||||
'@tiptap/extension-history':
|
||||
specifier: ^2.11.9
|
||||
version: 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
|
||||
'@tiptap/extension-paragraph':
|
||||
specifier: ^2.11.9
|
||||
version: 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
|
||||
'@tiptap/extension-table':
|
||||
specifier: 2.11.9
|
||||
version: 2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
|
||||
'@tiptap/extension-table-cell':
|
||||
specifier: 2.11.9
|
||||
version: 2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
|
||||
'@tiptap/extension-table-header':
|
||||
specifier: 2.11.9
|
||||
version: 2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
|
||||
'@tiptap/extension-table-row':
|
||||
specifier: 2.11.9
|
||||
version: 2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
|
||||
'@tiptap/extension-text':
|
||||
specifier: ^2.11.9
|
||||
version: 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
|
||||
@ -258,6 +279,9 @@ importers:
|
||||
jose:
|
||||
specifier: ^5.2.3
|
||||
version: 5.10.0
|
||||
lowlight:
|
||||
specifier: ^3.3.0
|
||||
version: 3.3.0
|
||||
lucide-react:
|
||||
specifier: ^0.511.0
|
||||
version: 0.511.0(react@18.3.1)
|
||||
@ -275,7 +299,7 @@ importers:
|
||||
version: 1.0.4
|
||||
novel:
|
||||
specifier: ^1.0.2
|
||||
version: 1.0.2(@tiptap/extension-code-block@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0))(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
version: 1.0.2(@tiptap/extension-code-block@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0))(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
ollama-ai-provider:
|
||||
specifier: 1.2.0
|
||||
version: 1.2.0(zod@3.23.8)
|
||||
@ -461,12 +485,6 @@ importers:
|
||||
specifier: ^4.2.1
|
||||
version: 4.3.2(typescript@5.8.3)(vite@6.3.5(@types/node@18.19.115)(jiti@2.4.2)(lightningcss@1.30.1)(terser@5.42.0)(yaml@2.8.0))
|
||||
|
||||
core/types:
|
||||
devDependencies:
|
||||
typescript:
|
||||
specifier: ^5.0.0
|
||||
version: 5.8.3
|
||||
|
||||
packages/database:
|
||||
dependencies:
|
||||
'@prisma/client':
|
||||
@ -521,6 +539,10 @@ importers:
|
||||
version: 18.2.69
|
||||
|
||||
packages/sdk:
|
||||
dependencies:
|
||||
commander:
|
||||
specifier: 14.0.0
|
||||
version: 14.0.0
|
||||
devDependencies:
|
||||
'@core/types':
|
||||
specifier: workspace:*
|
||||
@ -3769,8 +3791,8 @@ packages:
|
||||
highlight.js: ^11
|
||||
lowlight: ^2 || ^3
|
||||
|
||||
'@tiptap/extension-code-block@2.25.0':
|
||||
resolution: {integrity: sha512-T4kXbZNZ/NyklzQ/FWmUnjD4hgmJPrIBazzCZ/E/rF/Ag2IvUsztBT0PN3vTa+DAZ+IbM61TjlIpyJs1R7OdbQ==}
|
||||
'@tiptap/extension-code-block@2.11.9':
|
||||
resolution: {integrity: sha512-brwvt/SdP65DpchPv5rkhjEjjIIgE1+9ySw8kCTiyXWUrmZA0kK/iwp5zPpHfsoWT8Sa9+fh2uraVZGOF9Dcqw==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
'@tiptap/pm': ^2.7.0
|
||||
@ -3814,8 +3836,8 @@ packages:
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
|
||||
'@tiptap/extension-heading@2.25.0':
|
||||
resolution: {integrity: sha512-IrRKRRr7Bhpnq5aue1v5/e5N/eNdVV/THsgqqpLZO48pgN8Wv+TweOZe1Ntg/v8L4QSBC8iGMxxhiJZT8AzSkA==}
|
||||
'@tiptap/extension-heading@2.11.9':
|
||||
resolution: {integrity: sha512-Z84Vbw26bnMyIyZ7hc8/xXDD5uAcr4GA1zs0HPs4Er9wROOqkZnlgE54LaObXn2YbMKuDZ24cmCU8LFy0etN+w==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
|
||||
@ -3878,6 +3900,27 @@ packages:
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
|
||||
'@tiptap/extension-table-cell@2.11.9':
|
||||
resolution: {integrity: sha512-YlM7y4UlAcHZuW8p6gkAi1DJa4Vc/8F5BiL2fiW/lot2awE05mI14jjpCZLqJ2wrO9aLguOJbN2VRXEFcTQO7Q==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
|
||||
'@tiptap/extension-table-header@2.11.9':
|
||||
resolution: {integrity: sha512-6bLZDywhLaBlgy4Zp26yB28256F2lyjgoUO90w1doU4c19qlS1pkAwt3clYNlqQgMVVVjIZObbt8gZYma/8svA==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
|
||||
'@tiptap/extension-table-row@2.11.9':
|
||||
resolution: {integrity: sha512-so/rP4KTabeoQtvnPFYrVFqMi/QJAihBa5InZPDEjT4pue0yPQbOnTcGRgKiMNYLIEwAC9nw6i1zWlkY5Eic+w==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
|
||||
'@tiptap/extension-table@2.11.9':
|
||||
resolution: {integrity: sha512-4VWflJs7B9hgt1uG0SUdFtXJHlHbggIUtjX0tqd1BU9AsYspPXREALicG8Rz9Dm0eOX6dR30+I3LvL3K15XhTA==}
|
||||
peerDependencies:
|
||||
'@tiptap/core': ^2.7.0
|
||||
'@tiptap/pm': ^2.7.0
|
||||
|
||||
'@tiptap/extension-task-item@2.25.0':
|
||||
resolution: {integrity: sha512-8F7Z7jbsyGrPLHQCn+n39zdqIgxwR1kJ1nL5ZwhEW3ZhJgkFF0WMJSv36mwIJwL08p8um/c6g72AYB/e8CD7eA==}
|
||||
peerDependencies:
|
||||
@ -5036,6 +5079,10 @@ packages:
|
||||
resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
|
||||
engines: {node: '>=16'}
|
||||
|
||||
commander@14.0.0:
|
||||
resolution: {integrity: sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
commander@2.20.3:
|
||||
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||
|
||||
@ -13534,15 +13581,15 @@ snapshots:
|
||||
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
|
||||
'@tiptap/pm': 2.25.0
|
||||
|
||||
'@tiptap/extension-code-block-lowlight@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/extension-code-block@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)(highlight.js@11.11.1)(lowlight@3.3.0)':
|
||||
'@tiptap/extension-code-block-lowlight@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/extension-code-block@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)(highlight.js@11.11.1)(lowlight@3.3.0)':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
|
||||
'@tiptap/extension-code-block': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
|
||||
'@tiptap/extension-code-block': 2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
|
||||
'@tiptap/pm': 2.25.0
|
||||
highlight.js: 11.11.1
|
||||
lowlight: 3.3.0
|
||||
|
||||
'@tiptap/extension-code-block@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)':
|
||||
'@tiptap/extension-code-block@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
|
||||
'@tiptap/pm': 2.25.0
|
||||
@ -13580,7 +13627,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
|
||||
|
||||
'@tiptap/extension-heading@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))':
|
||||
'@tiptap/extension-heading@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
|
||||
|
||||
@ -13633,6 +13680,23 @@ snapshots:
|
||||
dependencies:
|
||||
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
|
||||
|
||||
'@tiptap/extension-table-cell@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
|
||||
|
||||
'@tiptap/extension-table-header@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
|
||||
|
||||
'@tiptap/extension-table-row@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
|
||||
|
||||
'@tiptap/extension-table@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
|
||||
'@tiptap/pm': 2.25.0
|
||||
|
||||
'@tiptap/extension-task-item@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)':
|
||||
dependencies:
|
||||
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
|
||||
@ -13698,12 +13762,12 @@ snapshots:
|
||||
'@tiptap/extension-bold': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
|
||||
'@tiptap/extension-bullet-list': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
|
||||
'@tiptap/extension-code': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
|
||||
'@tiptap/extension-code-block': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
|
||||
'@tiptap/extension-code-block': 2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
|
||||
'@tiptap/extension-document': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
|
||||
'@tiptap/extension-dropcursor': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
|
||||
'@tiptap/extension-gapcursor': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
|
||||
'@tiptap/extension-hard-break': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
|
||||
'@tiptap/extension-heading': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
|
||||
'@tiptap/extension-heading': 2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
|
||||
'@tiptap/extension-history': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
|
||||
'@tiptap/extension-horizontal-rule': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
|
||||
'@tiptap/extension-italic': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
|
||||
@ -14003,7 +14067,7 @@ snapshots:
|
||||
|
||||
'@types/hast@3.0.4':
|
||||
dependencies:
|
||||
'@types/unist': 2.0.11
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
'@types/http-errors@2.0.5': {}
|
||||
|
||||
@ -15084,6 +15148,8 @@ snapshots:
|
||||
|
||||
commander@11.1.0: {}
|
||||
|
||||
commander@14.0.0: {}
|
||||
|
||||
commander@2.20.3: {}
|
||||
|
||||
commander@4.1.1: {}
|
||||
@ -18311,12 +18377,12 @@ snapshots:
|
||||
|
||||
normalize.css@8.0.1: {}
|
||||
|
||||
novel@1.0.2(@tiptap/extension-code-block@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0))(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
novel@1.0.2(@tiptap/extension-code-block@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0))(@types/react-dom@18.3.7(@types/react@18.2.69))(@types/react@18.2.69)(highlight.js@11.11.1)(lowlight@3.3.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1):
|
||||
dependencies:
|
||||
'@radix-ui/react-slot': 1.2.3(@types/react@18.2.69)(react@18.3.1)
|
||||
'@tiptap/core': 2.25.0(@tiptap/pm@2.25.0)
|
||||
'@tiptap/extension-character-count': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
|
||||
'@tiptap/extension-code-block-lowlight': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/extension-code-block@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)(highlight.js@11.11.1)(lowlight@3.3.0)
|
||||
'@tiptap/extension-code-block-lowlight': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/extension-code-block@2.11.9(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)(highlight.js@11.11.1)(lowlight@3.3.0)
|
||||
'@tiptap/extension-color': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/extension-text-style@2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0)))
|
||||
'@tiptap/extension-highlight': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))
|
||||
'@tiptap/extension-horizontal-rule': 2.25.0(@tiptap/core@2.25.0(@tiptap/pm@2.25.0))(@tiptap/pm@2.25.0)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user