Skip to content

Commit

Permalink
checkpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
birdup000 committed Dec 28, 2024
1 parent 921f35e commit fdddd1c
Show file tree
Hide file tree
Showing 6 changed files with 424 additions and 117 deletions.
174 changes: 57 additions & 117 deletions app/components/AIEnhancedRichTextEditor.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
'use client';

import React, { useMemo, useCallback, useState } from 'react';
import { createEditor, Descendant, Editor, Element as SlateElement, Text, Node as SlateNode, Transforms } from 'slate';
import { Slate, Editable, withReact, RenderElementProps, RenderLeafProps, useSlate } from 'slate-react';
import { createEditor, Descendant, Editor, Element as SlateElement, Node as SlateNode, Text } from 'slate';
import { Slate, Editable, withReact, ReactEditor, RenderElementProps, RenderLeafProps } from 'slate-react';
// Using the same type from EditorConfig
import { CustomEditor } from './EditorConfig';
import isHotkey from 'is-hotkey';
import AGiXT from '../utils/agixt';
import { FormatButton } from './FormatButton';
import { toggleMark } from './editorUtils';

interface AIEnhancedRichTextEditorProps {
placeholder?: string;
Expand Down Expand Up @@ -39,46 +45,16 @@ const HOTKEYS = {
'mod+shift+s': 'strikethrough',
};

const LIST_TYPES = new Set(['bullet-list', 'number-list']);

const toggleBlock = (editor: Editor, format: string) => {
const isActive = isBlockActive(editor, format);
const isList = LIST_TYPES.has(format);

Transforms.unwrapNodes(editor, {
match: n => LIST_TYPES.has(n.type as string),
split: true,
});

Transforms.setNodes(editor, {
type: isActive ? 'paragraph' : isList ? 'list-item' : format,
});

if (!isActive && isList) {
const block = { type: format, children: [] };
Transforms.wrapNodes(editor, block);
}
};

const toggleMark = (editor: Editor, format: string) => {
const isActive = isMarkActive(editor, format);
if (isActive) {
Editor.removeMark(editor, format);
} else {
Editor.addMark(editor, format, true);
}
};

const isBlockActive = (editor: Editor, format: string) => {
const [match] = Editor.nodes(editor, {
match: n => n.type === format,
});
return !!match;
const serialize = (nodes: Descendant[]): string => {
return nodes.map(n => SlateNode.string(n)).join('\n');
};

const isMarkActive = (editor: Editor, format: string) => {
const marks = Editor.marks(editor);
return marks ? marks[format] === true : false;
const deserialize = (text: string): Descendant[] => {
const lines = text.split('\n');
return lines.map(line => ({
type: 'paragraph',
children: [{ text: line }],
}));
};

const AIEnhancedRichTextEditor: React.FC<AIEnhancedRichTextEditorProps> = ({
Expand All @@ -87,14 +63,13 @@ const AIEnhancedRichTextEditor: React.FC<AIEnhancedRichTextEditorProps> = ({
onChange,
onCreateTask,
}) => {
const editor = useMemo(() => withReact(createEditor()), []);
const editor = useMemo(() => withReact(createEditor()), []) as CustomEditor;
const [editorValue, setEditorValue] = useState<Descendant[]>(() =>
deserialize(initialContent)
);
const [aiSuggestions, setAiSuggestions] = useState<string[]>([]);
const [isProcessing, setIsProcessing] = useState(false);

// AI-powered suggestions
const generateSuggestions = async (content: string) => {
setIsProcessing(true);
try {
Expand Down Expand Up @@ -170,102 +145,67 @@ const AIEnhancedRichTextEditor: React.FC<AIEnhancedRichTextEditorProps> = ({
const content = serialize(value);
onChange?.(content);

// Generate AI suggestions when content changes
if (content.length > 50) { // Only trigger for substantial content
if (content.length > 50) {
generateSuggestions(content);
}
};

const serialize = (nodes: Descendant[]): string => {
return nodes.map(n => SlateNode.string(n)).join('\n');
};

const deserialize = (text: string): Descendant[] => {
const lines = text.split('\n');
return lines.map(line => ({
type: 'paragraph',
children: [{ text: line }],
}));
};

const FormatButton = ({ format, icon, isBlock = false }) => {
const editor = useSlate();
const isActive = isBlock ? isBlockActive(editor, format) : isMarkActive(editor, format);

return (
<button
className={`p-2 rounded ${isActive ? 'bg-gray-700' : 'hover:bg-gray-700'}`}
onMouseDown={(e) => {
e.preventDefault();
if (isBlock) {
toggleBlock(editor, format);
} else {
toggleMark(editor, format);
}
}}
>
{icon}
</button>
);
};

return (
<div className="bg-[#2A2A2A] rounded-lg p-4">
<div className="flex flex-wrap gap-2 mb-4 border-b border-gray-700 pb-2">
<FormatButton format={MARK_TYPES.bold} icon="B" />
<FormatButton format={MARK_TYPES.italic} icon="I" />
<FormatButton format={MARK_TYPES.underline} icon="U" />
<FormatButton format={MARK_TYPES.code} icon="<>" />
<FormatButton format={MARK_TYPES.strikethrough} icon="S" />
<div className="border-l border-gray-700 mx-2" />
<FormatButton format={ELEMENT_TYPES.heading1} icon="H1" isBlock />
<FormatButton format={ELEMENT_TYPES.heading2} icon="H2" isBlock />
<FormatButton format={ELEMENT_TYPES.heading3} icon="H3" isBlock />
<div className="border-l border-gray-700 mx-2" />
<FormatButton format={ELEMENT_TYPES.bulletList} icon="•" isBlock />
<FormatButton format={ELEMENT_TYPES.numberList} icon="1." isBlock />
<FormatButton format={ELEMENT_TYPES.blockquote} icon=""" isBlock />
<FormatButton format={ELEMENT_TYPES.codeBlock} icon="⌘" isBlock />
</div>

<Slate
editor={editor}
value={editorValue}
initialValue={editorValue}
onChange={handleChange}
>
<div className="flex flex-wrap gap-2 mb-4 border-b border-gray-700 pb-2">
<FormatButton format={MARK_TYPES.bold} icon="B" />
<FormatButton format={MARK_TYPES.italic} icon="I" />
<FormatButton format={MARK_TYPES.underline} icon="U" />
<FormatButton format={MARK_TYPES.code} icon="<>" />
<FormatButton format={MARK_TYPES.strikethrough} icon="S" />
<div className="border-l border-gray-700 mx-2" />
<FormatButton format={ELEMENT_TYPES.heading1} icon="H1" isBlock />
<FormatButton format={ELEMENT_TYPES.heading2} icon="H2" isBlock />
<FormatButton format={ELEMENT_TYPES.heading3} icon="H3" isBlock />
<div className="border-l border-gray-700 mx-2" />
<FormatButton format={ELEMENT_TYPES.bulletList} icon="•" isBlock />
<FormatButton format={ELEMENT_TYPES.numberList} icon="1." isBlock />
<FormatButton format={ELEMENT_TYPES.blockquote} icon=""" isBlock />
<FormatButton format={ELEMENT_TYPES.codeBlock} icon="⌘" isBlock />
</div>

<Editable
className="min-h-[200px] text-gray-200 focus:outline-none"
placeholder={placeholder}
renderElement={renderElement}
renderLeaf={renderLeaf}
onKeyDown={handleKeyDown}
/>
</Slate>

{/* AI Suggestions Panel */}
{aiSuggestions.length > 0 && (
<div className="mt-4 border-t border-gray-700 pt-4">
<h4 className="text-sm font-medium text-gray-300 mb-2">AI Suggestions</h4>
<div className="space-y-2">
{aiSuggestions.map((suggestion, index) => (
<div
key={index}
className="flex items-center justify-between bg-gray-800/50 p-2 rounded"
>
<span className="text-sm text-gray-300">{suggestion}</span>
<button
onClick={() => onCreateTask?.(suggestion, '')}
className="text-xs bg-indigo-600/50 hover:bg-indigo-600 px-2 py-1 rounded transition-colors"
{aiSuggestions.length > 0 && (
<div className="mt-4 border-t border-gray-700 pt-4">
<h4 className="text-sm font-medium text-gray-300 mb-2">AI Suggestions</h4>
<div className="space-y-2">
{aiSuggestions.map((suggestion, index) => (
<div
key={index}
className="flex items-center justify-between bg-gray-800/50 p-2 rounded"
>
Create Task
</button>
</div>
))}
<span className="text-sm text-gray-300">{suggestion}</span>
<button
onClick={() => onCreateTask?.(suggestion, '')}
className="text-xs bg-indigo-600/50 hover:bg-indigo-600 px-2 py-1 rounded transition-colors"
>
Create Task
</button>
</div>
))}
</div>
</div>
</div>
)}
)}
</Slate>
</div>
);
};

export default AIEnhancedRichTextEditor;
export default React.memo(AIEnhancedRichTextEditor);
Loading

0 comments on commit fdddd1c

Please sign in to comment.