fountainjs-editor 0.1.0 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/fountainjs.cjs +113 -1
- package/dist/fountainjs.js +627 -118
- package/package.json +2 -2
- package/src/core/exporters/html-exporter.ts +118 -0
- package/src/core/exporters/json-exporter.ts +47 -0
- package/src/core/exporters/markdown-exporter.ts +83 -0
- package/src/extensions/index.ts +7 -0
- package/src/extensions/nodes/code-block.ts +40 -0
- package/src/extensions/plugins/mcp-integration.ts +267 -0
- package/src/extensions/plugins/syntax-highlight.ts +205 -0
- package/src/index.ts +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fountainjs-editor",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A modular, extensible rich text editor library for React and other frameworks",
|
|
5
5
|
"author": "Your Name <your.email@example.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -73,4 +73,4 @@
|
|
|
73
73
|
"vite": "^4.3.9",
|
|
74
74
|
"vite-tsconfig-paths": "^5.1.4"
|
|
75
75
|
}
|
|
76
|
-
}
|
|
76
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { EditorState } from '../../core/state';
|
|
2
|
+
import { Node } from '../../core/schema/node';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Export editor content to HTML
|
|
6
|
+
* Supports all node types with syntax highlighting for code blocks
|
|
7
|
+
*/
|
|
8
|
+
export class HTMLExporter {
|
|
9
|
+
private highlightCode(code: string, language: string): string {
|
|
10
|
+
// Using a simple approach - in production, use Highlight.js or Prism.js
|
|
11
|
+
// For now, just escape and wrap
|
|
12
|
+
const escaped = this.escapeHtml(code);
|
|
13
|
+
return `<pre><code class="language-${language}">${escaped}</code></pre>`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
private escapeHtml(text: string): string {
|
|
17
|
+
const map: { [key: string]: string } = {
|
|
18
|
+
'&': '&',
|
|
19
|
+
'<': '<',
|
|
20
|
+
'>': '>',
|
|
21
|
+
'"': '"',
|
|
22
|
+
"'": ''',
|
|
23
|
+
};
|
|
24
|
+
return text.replace(/[&<>"']/g, (char) => map[char]);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private nodeToHtml(node: Node): string {
|
|
28
|
+
switch (node.type.name) {
|
|
29
|
+
case 'doc':
|
|
30
|
+
return node.content.map((child) => this.nodeToHtml(child)).join('\n');
|
|
31
|
+
|
|
32
|
+
case 'heading':
|
|
33
|
+
const level = node.attrs.level || 1;
|
|
34
|
+
const headingContent = node.content
|
|
35
|
+
.map((child) => this.nodeToHtml(child))
|
|
36
|
+
.join('');
|
|
37
|
+
return `<h${level}>${headingContent}</h${level}>`;
|
|
38
|
+
|
|
39
|
+
case 'paragraph':
|
|
40
|
+
const pContent = node.content.map((child) => this.nodeToHtml(child)).join('');
|
|
41
|
+
return `<p>${pContent}</p>`;
|
|
42
|
+
|
|
43
|
+
case 'text':
|
|
44
|
+
let text = node.text || '';
|
|
45
|
+
// Apply marks
|
|
46
|
+
if (node.marks) {
|
|
47
|
+
if (node.marks.some((m) => m.type === 'strong')) {
|
|
48
|
+
text = `<strong>${text}</strong>`;
|
|
49
|
+
}
|
|
50
|
+
if (node.marks.some((m) => m.type === 'em')) {
|
|
51
|
+
text = `<em>${text}</em>`;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return text;
|
|
55
|
+
|
|
56
|
+
case 'code-block':
|
|
57
|
+
const code = node.content.map((child) => child.text || '').join('\n');
|
|
58
|
+
const language = node.attrs.language || 'javascript';
|
|
59
|
+
return this.highlightCode(code, language);
|
|
60
|
+
|
|
61
|
+
case 'bullet-list':
|
|
62
|
+
const items = node.content
|
|
63
|
+
.map((child) => this.nodeToHtml(child))
|
|
64
|
+
.join('');
|
|
65
|
+
return `<ul>${items}</ul>`;
|
|
66
|
+
|
|
67
|
+
case 'list-item':
|
|
68
|
+
const liContent = node.content.map((child) => this.nodeToHtml(child)).join('');
|
|
69
|
+
return `<li>${liContent}</li>`;
|
|
70
|
+
|
|
71
|
+
case 'table':
|
|
72
|
+
const rows = node.content
|
|
73
|
+
.map((child) => this.nodeToHtml(child))
|
|
74
|
+
.join('');
|
|
75
|
+
return `<table><tbody>${rows}</tbody></table>`;
|
|
76
|
+
|
|
77
|
+
case 'table-row':
|
|
78
|
+
const cells = node.content
|
|
79
|
+
.map((child) => this.nodeToHtml(child))
|
|
80
|
+
.join('');
|
|
81
|
+
return `<tr>${cells}</tr>`;
|
|
82
|
+
|
|
83
|
+
case 'table-cell':
|
|
84
|
+
const cellContent = node.content.map((child) => this.nodeToHtml(child)).join('');
|
|
85
|
+
return `<td>${cellContent}</td>`;
|
|
86
|
+
|
|
87
|
+
case 'image':
|
|
88
|
+
return `<img src="${node.attrs.src}" alt="${node.attrs.alt || ''}" style="max-width: 100%; border-radius: 8px; margin: 10px 0;" />`;
|
|
89
|
+
|
|
90
|
+
default:
|
|
91
|
+
return '';
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export(state: EditorState): string {
|
|
96
|
+
const htmlContent = this.nodeToHtml(state.doc);
|
|
97
|
+
return `<!DOCTYPE html>
|
|
98
|
+
<html>
|
|
99
|
+
<head>
|
|
100
|
+
<meta charset="UTF-8">
|
|
101
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
102
|
+
<style>
|
|
103
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; line-height: 1.6; max-width: 800px; margin: 0 auto; padding: 20px; }
|
|
104
|
+
h1, h2, h3 { margin-top: 24px; margin-bottom: 16px; }
|
|
105
|
+
code { background: #f5f5f5; padding: 2px 6px; border-radius: 3px; font-family: 'Courier New', monospace; }
|
|
106
|
+
pre { background: #f5f5f5; padding: 12px; border-radius: 6px; overflow-x: auto; }
|
|
107
|
+
table { border-collapse: collapse; width: 100%; margin: 16px 0; }
|
|
108
|
+
table td, table th { border: 1px solid #ddd; padding: 8px; }
|
|
109
|
+
img { max-width: 100%; height: auto; }
|
|
110
|
+
ul, ol { margin: 16px 0; }
|
|
111
|
+
</style>
|
|
112
|
+
</head>
|
|
113
|
+
<body>
|
|
114
|
+
${htmlContent}
|
|
115
|
+
</body>
|
|
116
|
+
</html>`;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { EditorState } from '../../core/state';
|
|
2
|
+
import { Node } from '../../core/schema/node';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Export editor content to JSON format
|
|
6
|
+
* Preserves full structure for reimporting
|
|
7
|
+
*/
|
|
8
|
+
export class JSONExporter {
|
|
9
|
+
private nodeToJSON(node: Node): any {
|
|
10
|
+
const json: any = {
|
|
11
|
+
type: node.type.name,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
if (node.attrs && Object.keys(node.attrs).length > 0) {
|
|
15
|
+
json.attrs = node.attrs;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (node.text) {
|
|
19
|
+
json.text = node.text;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (node.marks && node.marks.length > 0) {
|
|
23
|
+
json.marks = node.marks.map((m) => ({
|
|
24
|
+
type: m.type,
|
|
25
|
+
attrs: m.attrs,
|
|
26
|
+
}));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (node.content && node.content.length > 0) {
|
|
30
|
+
json.content = node.content.map((child) => this.nodeToJSON(child));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return json;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export(state: EditorState): string {
|
|
37
|
+
const json = this.nodeToJSON(state.doc);
|
|
38
|
+
return JSON.stringify(json, null, 2);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Import from JSON (for round-trip serialization)
|
|
43
|
+
*/
|
|
44
|
+
static import(json: string): any {
|
|
45
|
+
return JSON.parse(json);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { EditorState } from '../../core/state';
|
|
2
|
+
import { Node } from '../../core/schema/node';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Export editor content to Markdown
|
|
6
|
+
* Preserves all formatting and supports code blocks
|
|
7
|
+
*/
|
|
8
|
+
export class MarkdownExporter {
|
|
9
|
+
private nodeToMarkdown(node: Node, depth: number = 0): string {
|
|
10
|
+
const indent = ' '.repeat(depth);
|
|
11
|
+
|
|
12
|
+
switch (node.type.name) {
|
|
13
|
+
case 'doc':
|
|
14
|
+
return node.content.map((child) => this.nodeToMarkdown(child, depth)).join('\n\n');
|
|
15
|
+
|
|
16
|
+
case 'heading':
|
|
17
|
+
const level = node.attrs.level || 1;
|
|
18
|
+
const headingContent = node.content
|
|
19
|
+
.map((child) => this.nodeToMarkdown(child, depth))
|
|
20
|
+
.join('');
|
|
21
|
+
return `${'#'.repeat(level)} ${headingContent}`;
|
|
22
|
+
|
|
23
|
+
case 'paragraph':
|
|
24
|
+
return node.content.map((child) => this.nodeToMarkdown(child, depth)).join('');
|
|
25
|
+
|
|
26
|
+
case 'text':
|
|
27
|
+
let text = node.text || '';
|
|
28
|
+
// Apply marks
|
|
29
|
+
if (node.marks) {
|
|
30
|
+
if (node.marks.some((m) => m.type === 'strong')) {
|
|
31
|
+
text = `**${text}**`;
|
|
32
|
+
}
|
|
33
|
+
if (node.marks.some((m) => m.type === 'em')) {
|
|
34
|
+
text = `*${text}*`;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return text;
|
|
38
|
+
|
|
39
|
+
case 'code-block':
|
|
40
|
+
const code = node.content.map((child) => child.text || '').join('\n');
|
|
41
|
+
const language = node.attrs.language || '';
|
|
42
|
+
return `\`\`\`${language}\n${code}\n\`\`\``;
|
|
43
|
+
|
|
44
|
+
case 'bullet-list':
|
|
45
|
+
return node.content
|
|
46
|
+
.map((child) => {
|
|
47
|
+
const content = this.nodeToMarkdown(child, depth + 1);
|
|
48
|
+
return `${indent}- ${content}`;
|
|
49
|
+
})
|
|
50
|
+
.join('\n');
|
|
51
|
+
|
|
52
|
+
case 'list-item':
|
|
53
|
+
return node.content.map((child) => this.nodeToMarkdown(child, depth)).join('');
|
|
54
|
+
|
|
55
|
+
case 'table':
|
|
56
|
+
let table = '';
|
|
57
|
+
const rows = node.content as Node[];
|
|
58
|
+
rows.forEach((row, rowIdx) => {
|
|
59
|
+
const cells = (row.content as Node[])
|
|
60
|
+
.map((cell) => this.nodeToMarkdown(cell, depth))
|
|
61
|
+
.join(' | ');
|
|
62
|
+
table += `| ${cells} |\n`;
|
|
63
|
+
if (rowIdx === 0) {
|
|
64
|
+
table += `| ${cells.split(' | ').map(() => '---').join(' | ')} |\n`;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
return table;
|
|
68
|
+
|
|
69
|
+
case 'table-cell':
|
|
70
|
+
return node.content.map((child) => this.nodeToMarkdown(child, depth)).join('');
|
|
71
|
+
|
|
72
|
+
case 'image':
|
|
73
|
+
return ``;
|
|
74
|
+
|
|
75
|
+
default:
|
|
76
|
+
return '';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export(state: EditorState): string {
|
|
81
|
+
return this.nodeToMarkdown(state.doc);
|
|
82
|
+
}
|
|
83
|
+
}
|
package/src/extensions/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import { tableRow } from './nodes/table-row';
|
|
|
10
10
|
import { tableCell } from './nodes/table-cell';
|
|
11
11
|
import { bulletList } from './nodes/bullet-list';
|
|
12
12
|
import { listItem } from './nodes/list-item';
|
|
13
|
+
import { codeBlock } from './nodes/code-block';
|
|
13
14
|
|
|
14
15
|
// All Mark imports
|
|
15
16
|
import { strong } from './marks/strong';
|
|
@@ -18,6 +19,8 @@ import { em } from './marks/em';
|
|
|
18
19
|
// All Plugin imports
|
|
19
20
|
import { historyPlugin } from './plugins/history';
|
|
20
21
|
import { markdownShortcutsPlugin } from './plugins/markdown-shortcuts';
|
|
22
|
+
import { SyntaxHighlightPlugin } from './plugins/syntax-highlight';
|
|
23
|
+
import { MCPIntegration } from './plugins/mcp-integration';
|
|
21
24
|
|
|
22
25
|
// Core imports for defining the schema
|
|
23
26
|
import { SchemaSpec } from '../core';
|
|
@@ -34,12 +37,15 @@ export { tableRow } from './nodes/table-row';
|
|
|
34
37
|
export { tableCell } from './nodes/table-cell';
|
|
35
38
|
export { bulletList } from './nodes/bullet-list';
|
|
36
39
|
export { listItem } from './nodes/list-item';
|
|
40
|
+
export { codeBlock } from './nodes/code-block';
|
|
37
41
|
|
|
38
42
|
export { strong } from './marks/strong';
|
|
39
43
|
export { em } from './marks/em';
|
|
40
44
|
|
|
41
45
|
export { historyPlugin, undo, redo } from './plugins/history';
|
|
42
46
|
export { markdownShortcutsPlugin } from './plugins/markdown-shortcuts';
|
|
47
|
+
export { SyntaxHighlightPlugin } from './plugins/syntax-highlight';
|
|
48
|
+
export { MCPIntegration, type ContentTransformRequest, type MCPTool, generateContentWithAI } from './plugins/mcp-integration';
|
|
43
49
|
|
|
44
50
|
|
|
45
51
|
// --- Define and Export the Core Schema Spec (ONCE) ---
|
|
@@ -56,6 +62,7 @@ export const CoreSchemaSpec: SchemaSpec = {
|
|
|
56
62
|
table_cell: tableCell,
|
|
57
63
|
bullet_list: bulletList,
|
|
58
64
|
list_item: listItem,
|
|
65
|
+
code_block: codeBlock,
|
|
59
66
|
},
|
|
60
67
|
marks: {
|
|
61
68
|
strong,
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { NodeSpec } from '../../core/schema/node-spec';
|
|
2
|
+
|
|
3
|
+
export const CodeBlockNodeSpec: NodeSpec = {
|
|
4
|
+
name: 'code-block',
|
|
5
|
+
group: 'block',
|
|
6
|
+
atom: false,
|
|
7
|
+
code: true,
|
|
8
|
+
attrs: {
|
|
9
|
+
language: { default: 'javascript' },
|
|
10
|
+
lineNumbers: { default: false },
|
|
11
|
+
},
|
|
12
|
+
parseDOM: [
|
|
13
|
+
{
|
|
14
|
+
tag: 'pre',
|
|
15
|
+
preserveWhitespace: 'full',
|
|
16
|
+
getAttrs(dom: any) {
|
|
17
|
+
return {
|
|
18
|
+
language: dom.getAttribute('data-language') || 'javascript',
|
|
19
|
+
lineNumbers: dom.getAttribute('data-line-numbers') === 'true',
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
toDOM() {
|
|
25
|
+
return [
|
|
26
|
+
'pre',
|
|
27
|
+
{
|
|
28
|
+
'data-language': this.attrs.language,
|
|
29
|
+
'data-line-numbers': this.attrs.lineNumbers ? 'true' : 'false',
|
|
30
|
+
class: `language-${this.attrs.language}`,
|
|
31
|
+
},
|
|
32
|
+
['code', 0],
|
|
33
|
+
];
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const codeBlock = {
|
|
38
|
+
...CodeBlockNodeSpec,
|
|
39
|
+
isInline: false,
|
|
40
|
+
};
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP (Model Context Protocol) Integration
|
|
3
|
+
*
|
|
4
|
+
* Allows FountainJS to work with ANY AI system that supports MCP
|
|
5
|
+
* AI-agnostic, language-agnostic, and framework-agnostic
|
|
6
|
+
*
|
|
7
|
+
* Example MCP servers:
|
|
8
|
+
* - OpenAI (via MCP bridges)
|
|
9
|
+
* - Anthropic Claude
|
|
10
|
+
* - Google Gemini
|
|
11
|
+
* - Open source LLMs
|
|
12
|
+
* - Custom enterprise LLMs
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export interface MCPToolInput {
|
|
16
|
+
[key: string]: string | number | boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface MCPTool {
|
|
20
|
+
name: string;
|
|
21
|
+
description: string;
|
|
22
|
+
inputSchema?: {
|
|
23
|
+
type: 'object';
|
|
24
|
+
properties: { [key: string]: any };
|
|
25
|
+
required: string[];
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface MCPContentBlock {
|
|
30
|
+
type: 'text' | 'tool_use';
|
|
31
|
+
text?: string;
|
|
32
|
+
id?: string;
|
|
33
|
+
name?: string;
|
|
34
|
+
input?: MCPToolInput;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface MCPRequest {
|
|
38
|
+
content: string;
|
|
39
|
+
tools?: MCPTool[];
|
|
40
|
+
systemPrompt?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface MCPResponse {
|
|
44
|
+
content: MCPContentBlock[];
|
|
45
|
+
stopReason: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* AI-agnostic content transformation request
|
|
50
|
+
* Send this to any MCP server, get back improved content
|
|
51
|
+
*/
|
|
52
|
+
export interface ContentTransformRequest {
|
|
53
|
+
content: string;
|
|
54
|
+
contentType: 'markdown' | 'json' | 'html' | 'fountain';
|
|
55
|
+
operation: 'generate' | 'improve' | 'transform' | 'summarize' | 'expand';
|
|
56
|
+
context?: string;
|
|
57
|
+
language?: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* MCP Integration Plugin
|
|
62
|
+
* Works with any AI service that supports Model Context Protocol
|
|
63
|
+
*/
|
|
64
|
+
export class MCPIntegration {
|
|
65
|
+
private mcpServerUrl?: string;
|
|
66
|
+
private tools: MCPTool[] = [];
|
|
67
|
+
|
|
68
|
+
constructor(mcpServerUrl?: string) {
|
|
69
|
+
this.mcpServerUrl = mcpServerUrl;
|
|
70
|
+
this.registerDefaultTools();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
private registerDefaultTools(): void {
|
|
74
|
+
this.tools = [
|
|
75
|
+
{
|
|
76
|
+
name: 'generate_content',
|
|
77
|
+
description: 'Generate new content in specified format',
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
prompt: {
|
|
82
|
+
type: 'string',
|
|
83
|
+
description: 'What to generate',
|
|
84
|
+
},
|
|
85
|
+
format: {
|
|
86
|
+
type: 'string',
|
|
87
|
+
enum: ['markdown', 'html', 'json', 'fountain'],
|
|
88
|
+
description: 'Output format',
|
|
89
|
+
},
|
|
90
|
+
language: {
|
|
91
|
+
type: 'string',
|
|
92
|
+
description: 'Programming language (if code)',
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
required: ['prompt', 'format'],
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'improve_content',
|
|
100
|
+
description: 'Improve existing content',
|
|
101
|
+
inputSchema: {
|
|
102
|
+
type: 'object',
|
|
103
|
+
properties: {
|
|
104
|
+
content: {
|
|
105
|
+
type: 'string',
|
|
106
|
+
description: 'Content to improve',
|
|
107
|
+
},
|
|
108
|
+
aspect: {
|
|
109
|
+
type: 'string',
|
|
110
|
+
enum: ['clarity', 'grammar', 'tone', 'structure'],
|
|
111
|
+
description: 'What to improve',
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
required: ['content', 'aspect'],
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: 'transform_format',
|
|
119
|
+
description: 'Transform content between formats',
|
|
120
|
+
inputSchema: {
|
|
121
|
+
type: 'object',
|
|
122
|
+
properties: {
|
|
123
|
+
content: {
|
|
124
|
+
type: 'string',
|
|
125
|
+
description: 'Content to transform',
|
|
126
|
+
},
|
|
127
|
+
fromFormat: {
|
|
128
|
+
type: 'string',
|
|
129
|
+
enum: ['markdown', 'html', 'json', 'fountain', 'text'],
|
|
130
|
+
},
|
|
131
|
+
toFormat: {
|
|
132
|
+
type: 'string',
|
|
133
|
+
enum: ['markdown', 'html', 'json', 'fountain'],
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
required: ['content', 'fromFormat', 'toFormat'],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Connect to an MCP server
|
|
144
|
+
* Server can be hosted anywhere - local, cloud, enterprise
|
|
145
|
+
*/
|
|
146
|
+
async connectToMCPServer(url: string): Promise<void> {
|
|
147
|
+
this.mcpServerUrl = url;
|
|
148
|
+
// In real implementation, establish WebSocket/HTTP connection
|
|
149
|
+
console.log(`Connected to MCP server: ${url}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Register custom tools for specific AI use cases
|
|
154
|
+
*/
|
|
155
|
+
registerTool(tool: MCPTool): void {
|
|
156
|
+
this.tools.push(tool);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Get available tools for this AI
|
|
161
|
+
*/
|
|
162
|
+
getAvailableTools(): MCPTool[] {
|
|
163
|
+
return this.tools;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Transform content using AI through MCP
|
|
168
|
+
* Works with ANY MCP-compatible AI service
|
|
169
|
+
*/
|
|
170
|
+
async transformContent(request: ContentTransformRequest): Promise<string> {
|
|
171
|
+
if (!this.mcpServerUrl) {
|
|
172
|
+
throw new Error('MCP server not configured. Call connectToMCPServer() first.');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const systemPrompt = this.buildSystemPrompt(request);
|
|
176
|
+
const userPrompt = this.buildUserPrompt(request);
|
|
177
|
+
|
|
178
|
+
const mcpRequest: MCPRequest = {
|
|
179
|
+
content: userPrompt,
|
|
180
|
+
systemPrompt,
|
|
181
|
+
tools: this.tools,
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
// Send to MCP server (implementation depends on server)
|
|
185
|
+
const response = await this.sendToMCP(mcpRequest);
|
|
186
|
+
|
|
187
|
+
// Extract content from MCP response
|
|
188
|
+
return this.extractContent(response);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private buildSystemPrompt(request: ContentTransformRequest): string {
|
|
192
|
+
return `You are a helpful content transformation AI.
|
|
193
|
+
The user has content in ${request.contentType} format.
|
|
194
|
+
Help them ${request.operation} their content.
|
|
195
|
+
${request.language ? `Programming language: ${request.language}` : ''}
|
|
196
|
+
${request.context ? `Context: ${request.context}` : ''}`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
private buildUserPrompt(request: ContentTransformRequest): string {
|
|
200
|
+
switch (request.operation) {
|
|
201
|
+
case 'generate':
|
|
202
|
+
return `Generate new content: ${request.content}`;
|
|
203
|
+
case 'improve':
|
|
204
|
+
return `Improve this content:\n${request.content}`;
|
|
205
|
+
case 'transform':
|
|
206
|
+
return `Transform this content to a better format:\n${request.content}`;
|
|
207
|
+
case 'summarize':
|
|
208
|
+
return `Summarize this content:\n${request.content}`;
|
|
209
|
+
case 'expand':
|
|
210
|
+
return `Expand on this content:\n${request.content}`;
|
|
211
|
+
default:
|
|
212
|
+
return request.content;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
private async sendToMCP(request: MCPRequest): Promise<MCPResponse> {
|
|
217
|
+
// This is a placeholder - real implementation would:
|
|
218
|
+
// 1. Connect to MCP server (WebSocket or HTTP)
|
|
219
|
+
// 2. Send request in MCP format
|
|
220
|
+
// 3. Wait for response
|
|
221
|
+
// 4. Handle tool calls if needed
|
|
222
|
+
|
|
223
|
+
if (!this.mcpServerUrl) {
|
|
224
|
+
throw new Error('MCP server URL not set');
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
try {
|
|
228
|
+
const response = await fetch(`${this.mcpServerUrl}/messages`, {
|
|
229
|
+
method: 'POST',
|
|
230
|
+
headers: { 'Content-Type': 'application/json' },
|
|
231
|
+
body: JSON.stringify(request),
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
if (!response.ok) {
|
|
235
|
+
throw new Error(`MCP server error: ${response.statusText}`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return await response.json();
|
|
239
|
+
} catch (error) {
|
|
240
|
+
console.error('MCP request failed:', error);
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
private extractContent(response: MCPResponse): string {
|
|
246
|
+
const textBlocks = response.content.filter((block) => block.type === 'text');
|
|
247
|
+
return textBlocks.map((block) => block.text || '').join('\n');
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* AI-agnostic content generation
|
|
253
|
+
* Can be used with any MCP-compatible service
|
|
254
|
+
*/
|
|
255
|
+
export async function generateContentWithAI(
|
|
256
|
+
prompt: string,
|
|
257
|
+
mcpServer: MCPIntegration,
|
|
258
|
+
outputFormat: 'markdown' | 'json' | 'html' = 'markdown'
|
|
259
|
+
): Promise<string> {
|
|
260
|
+
const request: ContentTransformRequest = {
|
|
261
|
+
content: prompt,
|
|
262
|
+
contentType: outputFormat,
|
|
263
|
+
operation: 'generate',
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
return mcpServer.transformContent(request);
|
|
267
|
+
}
|