Skip to content

Commit 5f1cd8c

Browse files
committed
EasyAi beta created and backend integrated self hosting only at the moment
1 parent aa0a4a5 commit 5f1cd8c

11 files changed

Lines changed: 567 additions & 210 deletions

File tree

src/App.tsx

Lines changed: 59 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ import AboutModal from './components/AboutModal';
101101
import LicenseModal from './components/LicenseModal';
102102
import EasyNotesSidebar from './components/EasyNotesSidebar';
103103
import EasyAIPanel from './components/EasyAIPanel';
104-
import { buildSystemPrompt } from './components/easyai/aiPersonas';
104+
import { buildSystemPrompt, parseFixTarget, extractBlock, extractTable } from './components/easyai/aiPersonas';
105+
import { queryEasyAI } from './components/easyai/aiService';
105106
import FeaturesModal from './components/FeaturesModal';
106107
import ThemeModal from './components/ThemeModal';
107108
import ImportThemeModal from './components/ImportThemeModal';
@@ -3223,24 +3224,74 @@ const App = () => {
32233224
showEasyAIPanel={showEasyAIPanel}
32243225
setShowEasyAIPanel={setShowEasyAIPanel}
32253226
showToast={showToast}
3226-
onActionSelect={(actionId, promptText) => {
3227-
const systemPrompt = buildSystemPrompt(actionId, editorContent);
3227+
onActionSelect={async (actionId, promptText) => {
3228+
const systemPrompt = buildSystemPrompt(actionId, editorContent, promptText);
32283229
if (!systemPrompt) {
32293230
showToast(`Unknown EasyAI action: ${actionId}`, 'error');
32303231
return;
32313232
}
32323233

3233-
// Log the constructed prompt for debugging until AI backend is wired
3234+
// Log the constructed prompt for debugging
32343235
console.log(`[EasyAI] Action: ${actionId}`);
32353236
console.log(`[EasyAI] User Prompt: ${promptText}`);
32363237
console.log(`[EasyAI] System Prompt:\n${systemPrompt}`);
32373238

3238-
// TODO: Send systemPrompt + promptText to AI backend and append response to editor
3239-
// For now, inject a placeholder showing the prompt was built successfully
3240-
const stubContent = `\n\n<!-- EasyAI Action: ${actionId} -->\n<!-- User Prompt: ${promptText} -->\n<!-- System prompt built (${systemPrompt.length} chars) — AI backend not yet connected -->\n\n`;
3239+
// Keep the debug comment in editor so user sees what was sent
3240+
const stubContent = `\n\n<!-- EasyAI Action: ${actionId} -->\n<!-- User Prompt: ${promptText} -->\n<!-- System prompt built (${systemPrompt.length} chars) -->\n\n`;
32413241
setEditorContent(prev => prev + stubContent);
3242-
showToast(`EasyAI (${actionId}) prompt ready — AI backend not yet connected.`, 'info');
3242+
showToast(`EasyAI (${actionId}) — sending to AI backend...`, 'info');
32433243
setShowEasyAIPanel(false);
3244+
3245+
try {
3246+
const aiResponse = await queryEasyAI(systemPrompt, promptText);
3247+
if (aiResponse.trim()) {
3248+
if (actionId === 'rewrite') {
3249+
// Rewrite replaces the entire editor content
3250+
setEditorContent(aiResponse + '\n');
3251+
} else if (actionId === 'fix-code') {
3252+
// Fix-code: replace the targeted block in-place
3253+
const { target } = parseFixTarget(promptText);
3254+
let extracted: { block: string; start: number; end: number } | null = null;
3255+
3256+
if (target === 'plantuml') {
3257+
extracted = extractBlock(editorContent, 'plantuml');
3258+
} else if (target === 'mermaid') {
3259+
extracted = extractBlock(editorContent, 'mermaid');
3260+
} else if (target === 'table') {
3261+
extracted = extractTable(editorContent);
3262+
} else if (target === 'code') {
3263+
const codeRegex = /(```(?!plantuml|mermaid)[a-zA-Z]*\n[\s\S]*?```)/i;
3264+
const match = editorContent.match(codeRegex);
3265+
if (match && match.index !== undefined) {
3266+
extracted = { block: match[1], start: match.index, end: match.index + match[1].length };
3267+
}
3268+
}
3269+
3270+
if (target && target !== 'all' && target !== 'markdown' && target !== 'language' && extracted) {
3271+
// Strip the stub comment we appended, then replace the targeted block
3272+
setEditorContent(prev => {
3273+
const withoutStub = prev.replace(stubContent, '');
3274+
const fixedResponse = aiResponse.trim();
3275+
return withoutStub.substring(0, extracted!.start) + fixedResponse + withoutStub.substring(extracted!.end);
3276+
});
3277+
} else if (target === 'all' || target === 'language' || target === 'markdown') {
3278+
// Model returns the full document with only targeted content fixed
3279+
setEditorContent(aiResponse.trim() + '\n');
3280+
} else {
3281+
// No /fix directive or block not found — append the help/response
3282+
setEditorContent(prev => prev + aiResponse + '\n');
3283+
}
3284+
} else {
3285+
setEditorContent(prev => prev + aiResponse + '\n');
3286+
}
3287+
showToast(`EasyAI (${actionId}) — response received.`, 'success');
3288+
} else {
3289+
showToast(`EasyAI (${actionId}) — empty response from AI.`, 'warning');
3290+
}
3291+
} catch (err: any) {
3292+
console.error('[EasyAI] Backend error:', err);
3293+
showToast(`EasyAI error: ${err.message || 'Connection failed'}`, 'error');
3294+
}
32443295
}}
32453296
/>
32463297

src/components/AboutModal.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@ export function AboutModal({ open, onClose }: AboutModalProps) {
4343
<span className="badge">{t('about.badge_markdown')}</span>
4444
<span className="badge">{t('about.badge_templates')}</span>
4545
<span className="badge">{t('about.badge_mermaid')}</span>
46+
<span className="badge">{t('about.badge_import_docx')}</span>
4647
<span className="badge">{t('about.badge_export')}</span>
47-
<span className="badge">{t('about.badge_hosted')}</span>
48-
<span className="badge">{t('about.badge_git')}</span>
49-
<span className="badge">{t('about.badge_cloud')}</span>
48+
<span className="badge">{t('about.badge_easygit')}</span>
49+
<span className="badge">{t('about.badge_easynotes')}</span>
50+
<span className="badge">{t('about.badge_easyai')}</span>
5051
<a href="https://easyeditor.co.uk/" target="_blank" rel="noopener noreferrer" style={{ textDecoration: 'none' }}>
5152
<span className="badge">{t('about.badge_roadmap')}</span>
5253
</a>

src/components/EasyAIPanel.tsx

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useState, useRef, useEffect } from 'react';
22
import { FaRobot, FaTimes } from 'react-icons/fa';
33
import { useLanguage } from '../i18n/LanguageContext';
44
import { getPersonaDescription } from './easyai/aiPersonas';
5+
import { loadEasyAIConfig, EasyAIConfig } from './easyai/aiService';
56

67
interface EasyAIPanelProps {
78
showEasyAIPanel: boolean;
@@ -18,9 +19,17 @@ const EasyAIPanel: React.FC<EasyAIPanelProps> = ({
1819
}) => {
1920
const { t } = useLanguage();
2021
const [prompt, setPrompt] = useState('');
22+
const [aiConfig, setAiConfig] = useState<EasyAIConfig | null>(null);
2123
const textareaRef = useRef<HTMLTextAreaElement>(null);
2224
const panelRef = useRef<HTMLDivElement>(null);
2325

26+
// Load AI config when panel opens
27+
useEffect(() => {
28+
if (showEasyAIPanel) {
29+
loadEasyAIConfig().then(setAiConfig).catch(() => setAiConfig(null));
30+
}
31+
}, [showEasyAIPanel]);
32+
2433
// Auto-resize textarea
2534
useEffect(() => {
2635
if (textareaRef.current) {
@@ -48,7 +57,7 @@ const EasyAIPanel: React.FC<EasyAIPanelProps> = ({
4857
return () => document.removeEventListener('mousedown', handleClickOutside);
4958
}, [showEasyAIPanel, setShowEasyAIPanel]);
5059

51-
const panelWidth = 400; // Single panel width like EasyNotes
60+
const panelWidth = 480; // Single panel width like EasyNotes
5261

5362
const actionButtons = [
5463
{ id: 'markdown', label: t('easyai.markdown') },
@@ -63,14 +72,14 @@ const EasyAIPanel: React.FC<EasyAIPanelProps> = ({
6372

6473
const handleActionClick = (actionId: string) => {
6574
if (!prompt.trim()) {
66-
showToast('Please enter a requirement for EasyAI first.', 'warning');
75+
showToast(t('easyai.toast_empty_prompt'), 'warning');
6776
return;
6877
}
6978

7079
if (onActionSelect) {
7180
onActionSelect(actionId, prompt);
7281
} else {
73-
showToast(`Selected Action: ${actionId}. Logic not yet bound to editor.`, 'info');
82+
showToast(t('easyai.toast_action_not_bound').replace('{{action}}', actionId), 'info');
7483
}
7584
};
7685

@@ -103,11 +112,18 @@ const EasyAIPanel: React.FC<EasyAIPanelProps> = ({
103112
boxSizing: 'border-box'
104113
}}>
105114
{/* Header */}
106-
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '20px' }}>
107-
<h2 style={{ margin: 0, fontSize: '1.5rem', display: 'flex', alignItems: 'center' }}>
108-
<FaRobot style={{ marginRight: '10px' }} />
109-
EasyAI
110-
</h2>
115+
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: '20px' }}>
116+
<div>
117+
<h2 style={{ margin: 0, fontSize: '1.5rem', display: 'flex', alignItems: 'center' }}>
118+
<FaRobot style={{ marginRight: '10px' }} />
119+
EasyAI (Beta)
120+
</h2>
121+
{aiConfig && (
122+
<p style={{ margin: '4px 0 0 0', fontSize: '0.75rem', color: 'var(--color-text-secondary, #888)', display: 'flex', alignItems: 'center', gap: '4px' }}>
123+
<span style={{ color: '#48bb78' }}></span> {aiConfig.agent} ({aiConfig.agent === 'Ollama' ? `host: ${aiConfig.host.replace(/^https?:\/\//, '')}, ` : ''}model: {aiConfig.model})
124+
</p>
125+
)}
126+
</div>
111127
<button
112128
onClick={() => setShowEasyAIPanel(false)}
113129
style={{
@@ -131,7 +147,7 @@ const EasyAIPanel: React.FC<EasyAIPanelProps> = ({
131147
rows={5}
132148
value={prompt}
133149
onChange={(e) => setPrompt(e.target.value)}
134-
placeholder="Ask EasyAI your requirements here... (e.g. Generate a sequence diagram for a login flow)"
150+
placeholder={t('easyai.prompt_placeholder')}
135151
style={{
136152
width: '100%',
137153
backgroundColor: 'var(--bg-primary-light)',

src/components/LicenseModal.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -326,14 +326,14 @@ export function LicenseModal({ open, onClose, showToast }: LicenseModalProps) {
326326
<div className="about-card">
327327
<h3>{t('about.easyai_credits')}</h3>
328328
<div style={{ marginTop: '10px' }}>
329-
<p style={{ fontSize: '0.9em', margin: '4px 0' }}><strong>{t('about.credits_agent')}</strong> {isLicenseValid ? (import.meta.env.VITE_PREMIUM_AGENT || 'Gemini') : agent}</p>
330-
<p style={{ fontSize: '0.9em', margin: '4px 0' }}><strong>{t('about.credits_model')}</strong> {isLicenseValid ? (import.meta.env.VITE_PREMIUM_MODEL || 'gemini-3.1-flash-lite-preview') : model}</p>
329+
<p style={{ fontSize: '0.9em', margin: '4px 0' }}><strong>{t('about.credits_agent')}</strong> {isLicenseValid ? (import.meta.env.VITE_PREMIUM_AGENT || 'Ollama') : agent}</p>
330+
<p style={{ fontSize: '0.9em', margin: '4px 0' }}><strong>{t('about.credits_model')}</strong> {isLicenseValid ? (import.meta.env.VITE_PREMIUM_MODEL || 'ministral-3:3b') : model}</p>
331331
<p style={{ fontSize: '0.9em', margin: '4px 0' }}><strong>{t('about.credits_monthly')}</strong> {monthlyCredits !== null ? monthlyCredits : t('about.query_built')}</p>
332332
<p style={{ fontSize: '0.9em', margin: '4px 0' }}><strong>{t('about.credits_topup')}</strong> {topUpCredits !== null ? topUpCredits : t('about.query_built')}</p>
333333
<p style={{ fontSize: '0.9em', margin: '4px 0' }}><strong>{t('about.credits_used')}</strong> {usedCredits !== null ? usedCredits : t('about.query_built')}</p>
334334
<p style={{ fontSize: '0.9em', margin: '4px 0' }}><strong>{t('about.credits_balance')}</strong> {balanceCredits !== null ? balanceCredits : t('about.query_built')}</p>
335335
<a
336-
href="https://buy.stripe.com/cNi14ng486TTfaK78LdZ602"
336+
// href="https://buy.stripe.com/cNi14ng486TTfaK78LdZ602"
337337
target="_blank"
338338
rel="noopener noreferrer"
339339
className="btn secondary"

0 commit comments

Comments
 (0)