Skip to content

Commit b0447a8

Browse files
committed
AI incorrect or misleading reporting
1 parent 063d309 commit b0447a8

7 files changed

Lines changed: 117 additions & 2 deletions

File tree

docs/EASYAI_USER_GUIDE.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,34 @@ EasyAI is your AI-powered writing assistant built directly into EasyEditor. Open
200200
201201
---
202202

203+
## Report Inappropriate AI Content
204+
205+
EasyAI includes a built-in reporting feature that lets you flag problematic AI-generated content. Click the 🚩 flag icon in the EasyAI panel header to open the report dialog, select a category, add an optional description, and submit.
206+
207+
### Where Reports Are Stored
208+
209+
Reports are saved locally on your device — nothing is sent to a server.
210+
211+
**Windows (Tauri desktop app):**
212+
213+
| Storage | Location |
214+
|:---|:---|
215+
| localStorage (WebView) | `C:\Users\<username>\AppData\Local\com.easyeditor.editor\EBWebView\Default\Local Storage\` |
216+
| JSON file | `C:\Users\<username>\AppData\Roaming\<username>.Easyeditor\ai-content-reports.json` |
217+
218+
**Linux (Tauri desktop app):**
219+
220+
| Storage | Location |
221+
|:---|:---|
222+
| localStorage (WebView) | `~/.local/share/com.easyeditor.editor/` |
223+
| JSON file | `~/.local/share/com.easyeditor.editor/ai-content-reports.json` |
224+
225+
**Web browser:** Reports are stored in the browser's localStorage only. Use the "Download Reports" button in the EasyAI panel to export them as a JSON file.
226+
227+
> Both stores keep a maximum of 100 reports. When the limit is reached, the oldest report is removed automatically.
228+
229+
---
230+
203231
## Tips for Better Results
204232

205233
- **Be specific***"Create a sequence diagram for the checkout payment flow"* works better than *"Make a diagram"*

src/App.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ const App = () => {
217217
const [showEasyNotesSidebar, setShowEasyNotesSidebar] = useState(false);
218218
const [showEasyAIPanel, setShowEasyAIPanel] = useState(false);
219219
const [lastAIAction, setLastAIAction] = useState<string | null>(null);
220+
const [lastUserPrompt, setLastUserPrompt] = useState<string | null>(null);
221+
const [lastAIResponse, setLastAIResponse] = useState<string | null>(null);
220222
const easyNotesButtonRef = useRef<HTMLButtonElement | null>(null);
221223
const [isEditFull, setIsEditFull] = useState<boolean>(false);
222224
const [isPreviewFull, setIsPreviewFull] = useState<boolean>(false);
@@ -3307,8 +3309,12 @@ const App = () => {
33073309
setShowEasyAIPanel={setShowEasyAIPanel}
33083310
showToast={showToast}
33093311
lastAIAction={lastAIAction}
3312+
lastUserPrompt={lastUserPrompt}
3313+
lastAIResponse={lastAIResponse}
33103314
onActionSelect={async (actionId, promptText) => {
33113315
setLastAIAction(actionId);
3316+
setLastUserPrompt(promptText);
3317+
setLastAIResponse(null);
33123318
// ── Documentation persona with repo scanning ──
33133319
if (actionId === 'documentation') {
33143320
const isTauri = !!(window as any).__TAURI_INTERNALS__;
@@ -3384,6 +3390,7 @@ const App = () => {
33843390

33853391
if (doc) {
33863392
setEditorContent(doc + '\n');
3393+
setLastAIResponse(doc);
33873394
showToast('EasyAI (documentation) — documentation generated.', 'success');
33883395
} else {
33893396
showToast('EasyAI (documentation) — empty response.', 'warning');
@@ -3418,6 +3425,7 @@ const App = () => {
34183425

34193426
try {
34203427
const aiResponse = await queryEasyAI(systemPrompt, promptText);
3428+
setLastAIResponse(aiResponse);
34213429
if (aiResponse.trim()) {
34223430
if (actionId === 'rewrite') {
34233431
// Rewrite replaces the entire editor content

src/components/EasyAIPanel.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,18 @@ interface EasyAIPanelProps {
1212
showToast: (message: string, type?: 'success' | 'error' | 'info' | 'warning') => void;
1313
onActionSelect?: (action: string, prompt: string) => void;
1414
lastAIAction?: string | null;
15+
lastUserPrompt?: string | null;
16+
lastAIResponse?: string | null;
1517
}
1618

1719
const EasyAIPanel: React.FC<EasyAIPanelProps> = ({
1820
showEasyAIPanel,
1921
setShowEasyAIPanel,
2022
showToast,
2123
onActionSelect,
22-
lastAIAction
24+
lastAIAction,
25+
lastUserPrompt,
26+
lastAIResponse
2327
}) => {
2428
const { t } = useLanguage();
2529
const [prompt, setPrompt] = useState('');
@@ -268,6 +272,10 @@ const EasyAIPanel: React.FC<EasyAIPanelProps> = ({
268272
onClose={() => setShowReportModal(false)}
269273
showToast={showToast}
270274
lastAIAction={lastAIAction ?? null}
275+
aiAgent={aiConfig?.agent ?? null}
276+
aiModel={aiConfig?.model ?? null}
277+
lastUserPrompt={lastUserPrompt ?? null}
278+
lastAIResponse={lastAIResponse ?? null}
271279
/>
272280
</div>
273281
);

src/components/ReportContentModal.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,21 @@ interface ReportContentModalProps {
1414
onClose: () => void;
1515
showToast: (message: string, type?: 'success' | 'error' | 'info' | 'warning') => void;
1616
lastAIAction: string | null;
17+
aiAgent: string | null;
18+
aiModel: string | null;
19+
lastUserPrompt: string | null;
20+
lastAIResponse: string | null;
1721
}
1822

1923
const ReportContentModal: React.FC<ReportContentModalProps> = ({
2024
open,
2125
onClose,
2226
showToast,
2327
lastAIAction,
28+
aiAgent,
29+
aiModel,
30+
lastUserPrompt,
31+
lastAIResponse,
2432
}) => {
2533
const { t } = useLanguage();
2634
const [selectedCategory, setSelectedCategory] = useState<string>('');
@@ -64,6 +72,10 @@ const ReportContentModal: React.FC<ReportContentModalProps> = ({
6472
description,
6573
timestamp: new Date().toISOString(),
6674
aiAction: lastAIAction,
75+
aiAgent,
76+
aiModel,
77+
userPrompt: lastUserPrompt,
78+
aiResponse: lastAIResponse,
6779
};
6880

6981
const success = submitReport(entry);

src/components/__tests__/ReportContentModal.test.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ describe('ReportContentModal', () => {
6060
onClose: jest.fn(),
6161
showToast: jest.fn(),
6262
lastAIAction: 'markdown' as string | null,
63+
aiAgent: 'Ollama' as string | null,
64+
aiModel: 'ministral-3:3b' as string | null,
65+
lastUserPrompt: 'Write a getting started guide' as string | null,
66+
lastAIResponse: '# Getting Started\n\nWelcome...' as string | null,
6367
};
6468

6569
beforeEach(() => {

src/components/easyai/__tests__/reportService.property.test.ts

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,30 @@ const aiActionArb = fc.oneof(
2929
fc.constant(null),
3030
);
3131

32+
/** Generate a random AI agent or null. */
33+
const aiAgentArb = fc.oneof(
34+
fc.constantFrom('Ollama', 'Gemini', 'Claude', 'Bedrock'),
35+
fc.constant(null),
36+
);
37+
38+
/** Generate a random AI model or null. */
39+
const aiModelArb = fc.oneof(
40+
fc.constantFrom('ministral-3:3b', 'gemini-2.0-flash', 'claude-sonnet-4-20250514'),
41+
fc.constant(null),
42+
);
43+
44+
/** Generate a random user prompt or null. */
45+
const userPromptArb = fc.oneof(
46+
fc.string({ minLength: 0, maxLength: 200 }),
47+
fc.constant(null),
48+
);
49+
50+
/** Generate a random AI response or null. */
51+
const aiResponseArb = fc.oneof(
52+
fc.string({ minLength: 0, maxLength: 6000 }),
53+
fc.constant(null),
54+
);
55+
3256
// Feature: ai-content-report, Property 2: Description field character limit
3357
describe('Property 2: Description field character limit', () => {
3458
beforeEach(() => {
@@ -57,6 +81,10 @@ describe('Property 2: Description field character limit', () => {
5781
description,
5882
timestamp,
5983
aiAction,
84+
aiAgent: null,
85+
aiModel: null,
86+
userPrompt: null,
87+
aiResponse: null,
6088
};
6189

6290
const result = submitReport(entry);
@@ -114,6 +142,10 @@ describe('Property 3: Report submission round-trip', () => {
114142
description,
115143
timestamp,
116144
aiAction,
145+
aiAgent: null,
146+
aiModel: null,
147+
userPrompt: null,
148+
aiResponse: null,
117149
};
118150

119151
const result = submitReport(entry);
@@ -158,6 +190,10 @@ describe('Property 4: Report log max size invariant', () => {
158190
description: fc.string({ minLength: 0, maxLength: 500 }),
159191
timestamp: timestampArb,
160192
aiAction: aiActionArb,
193+
aiAgent: aiAgentArb,
194+
aiModel: aiModelArb,
195+
userPrompt: userPromptArb,
196+
aiResponse: aiResponseArb,
161197
});
162198

163199
beforeEach(() => {
@@ -256,6 +292,10 @@ describe('Property 5: File persistence round-trip', () => {
256292
description: fc.string({ minLength: 0, maxLength: 500 }),
257293
timestamp: timestampArb,
258294
aiAction: aiActionArb,
295+
aiAgent: aiAgentArb,
296+
aiModel: aiModelArb,
297+
userPrompt: userPromptArb,
298+
aiResponse: aiResponseArb,
259299
});
260300

261301
/** Captured data written by the mocked writeTextFile. */
@@ -359,6 +399,7 @@ describe('Property 5: File persistence round-trip', () => {
359399
const expectedEntries = entries.slice(-100).map((e) => ({
360400
...e,
361401
description: e.description.slice(0, 500),
402+
aiResponse: e.aiResponse ? e.aiResponse.slice(0, 5000) : null,
362403
}));
363404
expect(fileReports).toEqual(expectedEntries);
364405
},
@@ -387,6 +428,10 @@ describe('Property 6: Graceful fallback on file write failure', () => {
387428
description: fc.string({ minLength: 0, maxLength: 500 }),
388429
timestamp: timestampArb,
389430
aiAction: aiActionArb,
431+
aiAgent: aiAgentArb,
432+
aiModel: aiModelArb,
433+
userPrompt: userPromptArb,
434+
aiResponse: aiResponseArb,
390435
});
391436

392437
beforeEach(() => {
@@ -504,6 +549,10 @@ describe('Property 7: Download export content equivalence', () => {
504549
description: fc.string({ minLength: 0, maxLength: 500 }),
505550
timestamp: timestampArb,
506551
aiAction: aiActionArb,
552+
aiAgent: aiAgentArb,
553+
aiModel: aiModelArb,
554+
userPrompt: userPromptArb,
555+
aiResponse: aiResponseArb,
507556
});
508557

509558
let capturedBlobContent: string;

src/components/easyai/reportService.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ export interface ReportEntry {
1111
description: string;
1212
timestamp: string; // ISO 8601 UTC
1313
aiAction: string | null;
14+
aiAgent: string | null; // e.g. "Ollama", "Gemini", "Claude", "Bedrock"
15+
aiModel: string | null; // e.g. "ministral-3:3b", "gemini-2.0-flash"
16+
userPrompt: string | null; // the prompt the user sent
17+
aiResponse: string | null; // the content the AI generated
1418
}
1519

1620
export const REPORT_CATEGORIES = [
@@ -27,6 +31,7 @@ export type ReportCategory = typeof REPORT_CATEGORIES[number];
2731
const STORAGE_KEY = 'easyeditor-ai-reports';
2832
const MAX_ENTRIES = 100;
2933
const MAX_DESCRIPTION_LENGTH = 500;
34+
const MAX_RESPONSE_LENGTH = 5000;
3035
const REPORT_FILENAME = 'ai-content-reports.json';
3136

3237
/**
@@ -68,10 +73,11 @@ export function submitReport(entry: ReportEntry): boolean {
6873
return false;
6974
}
7075

71-
// Truncate description to 500 chars
76+
// Truncate description to 500 chars, aiResponse to 5000 chars
7277
const sanitizedEntry: ReportEntry = {
7378
...entry,
7479
description: entry.description.slice(0, MAX_DESCRIPTION_LENGTH),
80+
aiResponse: entry.aiResponse ? entry.aiResponse.slice(0, MAX_RESPONSE_LENGTH) : null,
7581
};
7682

7783
const reports = getReports();

0 commit comments

Comments
 (0)