mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-23 02:16:08 +00:00
Jules was unable to complete the task in time. Please review the work done so far and provide feedback for Jules to continue.
This commit is contained in:
parent
891257c1e2
commit
755e86559c
14
README.md
14
README.md
@ -106,6 +106,20 @@ project, please check the [project management guide](./PROJECT.md) to get starte
|
||||
- **Integration-ready Docker support** for a hassle-free setup.
|
||||
- **Deploy** directly to **Netlify**
|
||||
|
||||
### AI Coding Assistant (Experimental)
|
||||
|
||||
Bolt.diy includes experimental AI-powered coding assistance:
|
||||
|
||||
* **AI Code Completion:** Suggestions appear automatically as you type or can be explicitly invoked (often Ctrl+Space or Alt+`). AI completions are integrated into the standard completion list.
|
||||
* **AI Code Suggestions/Refactoring:**
|
||||
* Trigger: `Cmd-Alt-r` (macOS) / `Ctrl-Alt-r` (Windows/Linux)
|
||||
* Select a block of code or place your cursor to have the AI analyze it for potential improvements or refactorings.
|
||||
* **AI Bug Fixing:**
|
||||
* Trigger: `Cmd-Alt-f` (macOS) / `Ctrl-Alt-f` (Windows/Linux)
|
||||
* Select code or use on the whole file to ask the AI to find and suggest fixes for bugs.
|
||||
|
||||
Suggestions and bug fixes from the AI assistant will appear as editor diagnostics (inline highlights, gutter icons). You can view them in detail and apply actions from the **Lint Panel** (toggle with `Cmd-Shift-m` / `Ctrl-Shift-m`).
|
||||
|
||||
## Setup
|
||||
|
||||
If you're new to installing software from GitHub, don't worry! If you encounter any issues, feel free to submit an "issue" using the provided links or improve this documentation by forking the repository, editing the instructions, and submitting a pull request. The following instruction will help you get the stable branch up and running on your local machine in no time.
|
||||
|
@ -1,6 +1,9 @@
|
||||
import { acceptCompletion, autocompletion, closeBrackets } from '@codemirror/autocomplete';
|
||||
import { aiCompletionSource } from './aiCompletions';
|
||||
import { triggerAIRefactorCommand, triggerAIBugFixCommand } from './aiLintSource';
|
||||
import { defaultKeymap, history, historyKeymap } from '@codemirror/commands';
|
||||
import { bracketMatching, foldGutter, indentOnInput, indentUnit } from '@codemirror/language';
|
||||
import { linter, lintGutter, lintKeymap } from '@codemirror/lint';
|
||||
import { searchKeymap } from '@codemirror/search';
|
||||
import { Compartment, EditorSelection, EditorState, StateEffect, StateField, type Extension } from '@codemirror/state';
|
||||
import {
|
||||
@ -361,7 +364,10 @@ function newEditorState(
|
||||
...defaultKeymap,
|
||||
...historyKeymap,
|
||||
...searchKeymap,
|
||||
...lintKeymap,
|
||||
{ key: 'Tab', run: acceptCompletion },
|
||||
{ key: 'Mod-Alt-r', run: triggerAIRefactorCommand },
|
||||
{ key: 'Mod-Alt-f', run: triggerAIBugFixCommand },
|
||||
{
|
||||
key: 'Mod-s',
|
||||
preventDefault: true,
|
||||
@ -374,7 +380,11 @@ function newEditorState(
|
||||
]),
|
||||
indentUnit.of('\t'),
|
||||
autocompletion({
|
||||
closeOnBlur: false,
|
||||
closeOnBlur: false, // Existing option
|
||||
override: [aiCompletionSource], // Add our AI source
|
||||
activateOnTyping: true, // Explicitly set or ensure this is the desired behavior
|
||||
// for AI suggestions to trigger during typing.
|
||||
// Our aiCompletionSource also has internal logic to decide when to fire.
|
||||
}),
|
||||
tooltips({
|
||||
position: 'absolute',
|
||||
@ -412,6 +422,10 @@ function newEditorState(
|
||||
return icon;
|
||||
},
|
||||
}),
|
||||
linter(() => [], {
|
||||
delay: 750,
|
||||
}),
|
||||
lintGutter(),
|
||||
...extensions,
|
||||
],
|
||||
});
|
||||
|
85
app/components/editor/codemirror/aiCompletions.ts
Normal file
85
app/components/editor/codemirror/aiCompletions.ts
Normal file
@ -0,0 +1,85 @@
|
||||
// app/components/editor/codemirror/aiCompletions.ts
|
||||
import { CompletionContext, CompletionResult, Completion } from '@codemirror/autocomplete';
|
||||
import { language as languageFacet } from '@codemirror/language';
|
||||
import { EditorState } from '@codemirror/state';
|
||||
import type { AISuggestion, AISuggestionParams } from '~/lib/ai-assistant/types';
|
||||
|
||||
// Helper to get the current language using CodeMirror's language facet
|
||||
function getActualCurrentLanguage(state: EditorState): string {
|
||||
const langConfig = state.facet(languageFacet);
|
||||
// The language facet might hold the Language instance directly, or an array of them.
|
||||
// If it's an array, it's typically the first one that's active.
|
||||
// The exact way to get the name might vary slightly based on CodeMirror version or specific language package structure.
|
||||
if (Array.isArray(langConfig) && langConfig.length > 0) {
|
||||
// @ts-expect-error - name might not be on Language type directly for all lang packages
|
||||
return langConfig[0]?.name?.toLowerCase() || langConfig[0]?.constructor?.name?.toLowerCase() || 'plaintext';
|
||||
} else if (langConfig && typeof langConfig === 'object') {
|
||||
// @ts-expect-error - name might not be on Language type directly for all lang packages
|
||||
return (langConfig as any).name?.toLowerCase() || (langConfig as any).constructor?.name?.toLowerCase() || 'plaintext';
|
||||
}
|
||||
return 'plaintext'; // Default fallback
|
||||
}
|
||||
|
||||
export const aiCompletionSource = async (context: CompletionContext): Promise<CompletionResult | null> => {
|
||||
// Determine the token or text before the cursor to decide if we should complete
|
||||
// Example: complete if explicit, or if there's some text typed
|
||||
const word = context.matchBefore(/\w*/); // Matches a word before the cursor
|
||||
|
||||
// Only trigger completions if explicitly requested, or if there's a word being typed,
|
||||
// or if it's after a character that might solicit a completion (like '.')
|
||||
// This logic can be refined.
|
||||
const shouldTrigger = context.explicit || (word && word.from !== word.to) || context.state.doc.sliceString(context.pos - 1, context.pos) === '.';
|
||||
|
||||
if (!shouldTrigger) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const from = word ? word.from : context.pos;
|
||||
const codeBeforeCursor = context.state.doc.sliceString(0, context.pos);
|
||||
const currentLanguage = getActualCurrentLanguage(context.state);
|
||||
|
||||
const params: AISuggestionParams = {
|
||||
code: codeBeforeCursor, // Send code up to cursor
|
||||
cursorPosition: context.pos,
|
||||
language: currentLanguage,
|
||||
task: 'complete',
|
||||
// fileName: could be passed if available globally or via context
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/ai-assistant', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('AI Completion API error:', response.statusText);
|
||||
return null;
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success && result.suggestions && result.suggestions.length > 0) {
|
||||
const completions: Completion[] = result.suggestions.map((s: AISuggestion) => ({
|
||||
label: s.code || '',
|
||||
apply: s.code, // Can be a function for more complex application logic
|
||||
type: s.type === 'completion' ? 'ai_completion' : s.type, // Custom type for styling
|
||||
detail: s.title,
|
||||
info: s.description, // `info` can render a DOM node or return a promise
|
||||
boost: -1, // AI suggestions might be boosted or de-prioritized as needed
|
||||
}));
|
||||
|
||||
return {
|
||||
from: from, // Start of the text to be replaced by the completion
|
||||
options: completions,
|
||||
// validFor: /^\w*$/, // Example: allow further typing if it's still a word
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching AI completions:', error);
|
||||
return null;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
258
app/components/editor/codemirror/aiLintSource.ts
Normal file
258
app/components/editor/codemirror/aiLintSource.ts
Normal file
@ -0,0 +1,258 @@
|
||||
// app/components/editor/codemirror/aiLintSource.ts
|
||||
import { EditorView } from '@codemirror/view';
|
||||
import { Diagnostic, setDiagnostics } from '@codemirror/lint';
|
||||
import { EditorSelection, EditorState } from '@codemirror/state';
|
||||
import { language as languageFacet } from '@codemirror/language';
|
||||
import type { AISuggestion, AISuggestionParams } from '~/lib/ai-assistant/types';
|
||||
|
||||
// Helper to get the current language using CodeMirror's language facet
|
||||
function getActualCurrentLanguage(state: EditorState): string {
|
||||
const langConfig = state.facet(languageFacet);
|
||||
if (Array.isArray(langConfig) && langConfig.length > 0) {
|
||||
// @ts-expect-error - name might not be on Language type directly for all lang packages
|
||||
return langConfig[0]?.name?.toLowerCase() || langConfig[0]?.constructor?.name?.toLowerCase() || 'plaintext';
|
||||
} else if (langConfig && typeof langConfig === 'object') {
|
||||
// @ts-expect-error - name might not be on Language type directly for all lang packages
|
||||
return (langConfig as any).name?.toLowerCase() || (langConfig as any).constructor?.name?.toLowerCase() || 'plaintext';
|
||||
}
|
||||
return 'plaintext'; // Default fallback
|
||||
}
|
||||
|
||||
// This function will be called by a command to fetch and return diagnostics
|
||||
export async function fetchAIRefactorSuggestions(view: EditorView): Promise<readonly Diagnostic[]> {
|
||||
const { state } = view;
|
||||
const diagnostics: Diagnostic[] = [];
|
||||
const currentLanguage = getActualCurrentLanguage(state);
|
||||
|
||||
// For suggestions, we usually operate on the current selection, or the whole document if no selection
|
||||
let codeToAnalyze = '';
|
||||
let selectionRange: { from: number; to: number } | undefined = undefined;
|
||||
const mainSelection = state.selection.main;
|
||||
|
||||
if (!mainSelection.empty) {
|
||||
codeToAnalyze = state.doc.sliceString(mainSelection.from, mainSelection.to);
|
||||
selectionRange = { from: mainSelection.from, to: mainSelection.to };
|
||||
} else {
|
||||
// If no selection, consider sending the whole document or a relevant block
|
||||
// For simplicity now, let's assume whole document if no selection,
|
||||
// or this could be a user option / smarter context gathering later.
|
||||
codeToAnalyze = state.doc.toString();
|
||||
selectionRange = { from: 0, to: state.doc.length };
|
||||
}
|
||||
|
||||
if (!codeToAnalyze.trim()) {
|
||||
return []; // No code to analyze
|
||||
}
|
||||
|
||||
const params: AISuggestionParams = {
|
||||
code: codeToAnalyze,
|
||||
selection: selectionRange, // Send the selection range if analysis is on selection
|
||||
language: currentLanguage,
|
||||
task: 'suggest_refactor',
|
||||
// fileName: could be passed
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/ai-assistant', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('AI Refactor API error:', response.statusText);
|
||||
return [];
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success && result.suggestions) {
|
||||
result.suggestions.forEach((s: AISuggestion) => {
|
||||
// Adjust 'from' and 'to' if the suggestion is relative to a snippet
|
||||
// For now, assume 's.from' and 's.to' are document-level if selectionRange was for the whole doc,
|
||||
// or relative to the start of the selection if a sub-part of selection is suggested.
|
||||
// This needs careful handling based on how AI returns ranges.
|
||||
// If AI returns ranges relative to the *snippet* sent, and we sent a selection,
|
||||
// then s.from and s.to need to be offset by selectionRange.from.
|
||||
let diagnosticFrom = s.from ?? mainSelection.from;
|
||||
let diagnosticTo = s.to ?? mainSelection.to;
|
||||
|
||||
if (selectionRange && !mainSelection.empty && s.from != null && s.to != null) {
|
||||
diagnosticFrom = selectionRange.from + s.from;
|
||||
diagnosticTo = selectionRange.from + s.to;
|
||||
}
|
||||
|
||||
|
||||
diagnostics.push({
|
||||
from: diagnosticFrom,
|
||||
to: diagnosticTo,
|
||||
severity: 'hint', // 'info' or 'hint' for suggestions
|
||||
message: s.title || s.description || 'AI Suggestion',
|
||||
source: 'AI Assistant',
|
||||
actions: s.code // Only add action if there's code to apply
|
||||
? [
|
||||
{
|
||||
name: `Apply: ${s.title || 'Apply suggestion'}`,
|
||||
apply: (v: EditorView, fromApply: number, toApply: number) => {
|
||||
// The 'from' and 'to' for apply are the diagnostic's range
|
||||
v.dispatch({
|
||||
changes: { from: fromApply, to: toApply, insert: s.code },
|
||||
selection: EditorSelection.cursor(fromApply + (s.code?.length || 0)),
|
||||
scrollIntoView: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
]
|
||||
: [],
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching AI refactor suggestions:', error);
|
||||
}
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
export const triggerAIRefactorCommand = (view: EditorView): boolean => {
|
||||
const loadingDiagnostic: Diagnostic = {
|
||||
from: view.state.selection.main.from,
|
||||
to: view.state.selection.main.to,
|
||||
severity: 'info',
|
||||
message: 'AI Assistant: Analyzing for refactorings...',
|
||||
source: 'AI Assistant',
|
||||
};
|
||||
view.dispatch(setDiagnostics(view.state, [loadingDiagnostic]));
|
||||
|
||||
fetchAIRefactorSuggestions(view)
|
||||
.then(diagnostics => {
|
||||
// Dispatch a transaction to update the diagnostics in the lint state field
|
||||
// Ensure the lint extension is configured to pick these up.
|
||||
// The setDiagnostics effect comes from @codemirror/lint
|
||||
view.dispatch(setDiagnostics(view.state, diagnostics));
|
||||
if (diagnostics.length > 0) {
|
||||
// Optionally, open the lint panel if you have one and it's not open
|
||||
// openLintPanel(view); // This command would need to be imported or available
|
||||
console.log('AI Refactor suggestions loaded.');
|
||||
} else {
|
||||
console.log('No AI Refactor suggestions found.');
|
||||
// Clear previous AI suggestions if any
|
||||
view.dispatch(setDiagnostics(view.state, []));
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error triggering AI Refactor:", error);
|
||||
// Clear diagnostics on error too
|
||||
view.dispatch(setDiagnostics(view.state, []));
|
||||
});
|
||||
return true; // Command successfully initiated
|
||||
};
|
||||
|
||||
export async function fetchAIBugFixSuggestions(view: EditorView): Promise<readonly Diagnostic[]> {
|
||||
const { state } = view;
|
||||
const diagnostics: Diagnostic[] = [];
|
||||
const currentLanguage = getActualCurrentLanguage(state); // Use updated function
|
||||
|
||||
let codeToAnalyze = '';
|
||||
let selectionRange: { from: number; to: number } | undefined = undefined;
|
||||
const mainSelection = state.selection.main;
|
||||
|
||||
if (!mainSelection.empty) {
|
||||
codeToAnalyze = state.doc.sliceString(mainSelection.from, mainSelection.to);
|
||||
selectionRange = { from: mainSelection.from, to: mainSelection.to };
|
||||
} else {
|
||||
codeToAnalyze = state.doc.toString();
|
||||
selectionRange = { from: 0, to: state.doc.length };
|
||||
}
|
||||
|
||||
if (!codeToAnalyze.trim()) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const params: AISuggestionParams = {
|
||||
code: codeToAnalyze,
|
||||
selection: selectionRange,
|
||||
language: currentLanguage,
|
||||
task: 'fix_bug',
|
||||
// fileName: could be passed
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/ai-assistant', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(params),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
console.error('AI Bug Fix API error:', response.statusText);
|
||||
return [];
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success && result.suggestions) {
|
||||
result.suggestions.forEach((s: AISuggestion) => {
|
||||
let diagnosticFrom = s.from ?? mainSelection.from;
|
||||
let diagnosticTo = s.to ?? mainSelection.to;
|
||||
|
||||
if (selectionRange && !mainSelection.empty && s.from != null && s.to != null) {
|
||||
diagnosticFrom = selectionRange.from + s.from;
|
||||
diagnosticTo = selectionRange.from + s.to;
|
||||
}
|
||||
|
||||
diagnostics.push({
|
||||
from: diagnosticFrom,
|
||||
to: diagnosticTo,
|
||||
severity: 'warning', // Severity for bug fixes
|
||||
message: s.title || s.description || 'AI Bug Fix Suggestion',
|
||||
source: 'AI Assistant (Bug Fix)',
|
||||
actions: s.code
|
||||
? [
|
||||
{
|
||||
name: `Apply Fix: ${s.title || 'Accept fix'}`,
|
||||
apply: (v: EditorView, fromApply: number, toApply: number) => {
|
||||
v.dispatch({
|
||||
changes: { from: fromApply, to: toApply, insert: s.code },
|
||||
selection: EditorSelection.cursor(fromApply + (s.code?.length || 0)),
|
||||
scrollIntoView: true,
|
||||
});
|
||||
},
|
||||
},
|
||||
]
|
||||
: [],
|
||||
});
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error fetching AI bug fix suggestions:', error);
|
||||
}
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
export const triggerAIBugFixCommand = (view: EditorView): boolean => {
|
||||
const loadingDiagnostic: Diagnostic = {
|
||||
from: view.state.selection.main.from,
|
||||
to: view.state.selection.main.to,
|
||||
severity: 'info',
|
||||
message: 'AI Assistant: Scanning for bugs...',
|
||||
source: 'AI Assistant',
|
||||
};
|
||||
view.dispatch(setDiagnostics(view.state, [loadingDiagnostic]));
|
||||
|
||||
fetchAIBugFixSuggestions(view)
|
||||
.then(diagnostics => {
|
||||
view.dispatch(setDiagnostics(view.state, diagnostics));
|
||||
if (diagnostics.length > 0) {
|
||||
console.log('AI Bug Fix suggestions loaded.');
|
||||
} else {
|
||||
console.log('No AI Bug Fix suggestions found.');
|
||||
// Optionally clear previous AI bug fix diagnostics if desired
|
||||
// view.dispatch(setDiagnostics(view.state, [])); // Or filter to keep other types
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error("Error triggering AI Bug Fix:", error);
|
||||
view.dispatch(setDiagnostics(view.state, [])); // Clear on error
|
||||
});
|
||||
return true;
|
||||
};
|
89
app/lib/ai-assistant/aiAssistantService.server.ts
Normal file
89
app/lib/ai-assistant/aiAssistantService.server.ts
Normal file
@ -0,0 +1,89 @@
|
||||
// app/lib/ai-assistant/aiAssistantService.server.ts
|
||||
import type { AISuggestionParams, AISuggestionResponse } from './types';
|
||||
// Placeholder for actual LLM call utilities, to be imported later
|
||||
// import { getLlmCompletion } from '~/lib/.server/llm';
|
||||
|
||||
export async function getAISuggestions(
|
||||
params: AISuggestionParams
|
||||
): Promise<AISuggestionResponse> {
|
||||
console.log('AI Assistant Service called with params:', params);
|
||||
|
||||
// TODO: Select LLM provider and model
|
||||
// TODO: Construct prompt based on params.task
|
||||
// TODO: Call LLM API
|
||||
// TODO: Process LLM response
|
||||
|
||||
// Placeholder response for now
|
||||
if (params.task === 'complete') {
|
||||
// Simulate a simple completion
|
||||
if (params.code.endsWith('.')) {
|
||||
return {
|
||||
success: true,
|
||||
suggestions: [
|
||||
{
|
||||
id: 'compl-1',
|
||||
type: 'completion',
|
||||
code: 'log("hello world");',
|
||||
description: 'console.log example',
|
||||
},
|
||||
{
|
||||
id: 'compl-2',
|
||||
type: 'completion',
|
||||
code: 'dir(document);',
|
||||
description: 'console.dir example',
|
||||
}
|
||||
],
|
||||
};
|
||||
}
|
||||
} else if (params.task === 'suggest_refactor') {
|
||||
return {
|
||||
success: true,
|
||||
suggestions: [
|
||||
{
|
||||
id: 'refactor-1',
|
||||
type: 'refactor',
|
||||
title: 'Use const instead of let',
|
||||
description: 'If the variable is not reassigned, use const.',
|
||||
code: 'const myVar = 10;', // Example suggested code
|
||||
from: 0, // Example range
|
||||
to: 10, // Example range
|
||||
}
|
||||
]
|
||||
}
|
||||
} else if (params.task === 'fix_bug') {
|
||||
// Simulate a bug fix suggestion
|
||||
if (params.code.includes('myVar = 10;')) { // Example condition
|
||||
return {
|
||||
success: true,
|
||||
suggestions: [
|
||||
{
|
||||
id: 'fix-1',
|
||||
type: 'fix',
|
||||
title: 'Potential null access',
|
||||
description: 'Variable `myVar` might be null here, causing a runtime error. Consider adding a check.',
|
||||
code: 'if (myVar != null) {\n console.log(myVar);\n} else {\n console.log("myVar is null");\n}', // Example fixed code
|
||||
from: params.code.indexOf('myVar = 10;'), // Placeholder, ideally AI gives specific range
|
||||
to: params.code.indexOf('myVar = 10;') + 'myVar = 10;'.length, // Placeholder
|
||||
}
|
||||
]
|
||||
};
|
||||
}
|
||||
return { // Default if no specific mock bug found
|
||||
success: true,
|
||||
suggestions: [{
|
||||
id: 'fix-generic',
|
||||
type: 'fix',
|
||||
title: 'Generic Fix Example',
|
||||
description: 'This is a generic bug fix suggestion.',
|
||||
code: '// Fixed code example\n' + params.code.replace(/let/g, 'const'), // Simple replacement example
|
||||
from: 0,
|
||||
to: params.code.length,
|
||||
}]
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: 'Task type not yet implemented or no suggestion found.',
|
||||
};
|
||||
}
|
28
app/lib/ai-assistant/types.ts
Normal file
28
app/lib/ai-assistant/types.ts
Normal file
@ -0,0 +1,28 @@
|
||||
// app/lib/ai-assistant/types.ts
|
||||
export interface AISuggestionParams {
|
||||
code: string;
|
||||
cursorPosition?: number;
|
||||
selection?: { from: number; to: number };
|
||||
language: string; // e.g., 'javascript', 'python', 'typescript'
|
||||
task: 'complete' | 'suggest_refactor' | 'fix_bug' | 'explain_code';
|
||||
// Consider adding filename if available, can be useful context for LLM
|
||||
fileName?: string;
|
||||
}
|
||||
|
||||
export interface AISuggestion {
|
||||
id: string; // Unique ID for the suggestion
|
||||
type: 'completion' | 'refactor' | 'fix' | 'explanation';
|
||||
title?: string;
|
||||
description?: string;
|
||||
code?: string; // The suggested code or completion
|
||||
// For fixes/refactors, a diff might be useful in the future
|
||||
// diff?: string;
|
||||
from?: number; // Start of range to replace (if applicable)
|
||||
to?: number; // End of range to replace (if applicable)
|
||||
}
|
||||
|
||||
export interface AISuggestionResponse {
|
||||
success: boolean;
|
||||
suggestions?: AISuggestion[];
|
||||
error?: string;
|
||||
}
|
23
app/routes/api.ai-assistant.ts
Normal file
23
app/routes/api.ai-assistant.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// app/routes/api.ai-assistant.ts
|
||||
import { json, ActionFunctionArgs } from '@remix-run/node'; // or cloudflare/workers
|
||||
import { getAISuggestions } from '~/lib/ai-assistant/aiAssistantService.server';
|
||||
import type { AISuggestionParams } from '~/lib/ai-assistant/types';
|
||||
|
||||
export async function action({ request }: ActionFunctionArgs) {
|
||||
if (request.method !== 'POST') {
|
||||
return json({ success: false, error: 'Invalid request method' }, { status: 405 });
|
||||
}
|
||||
|
||||
try {
|
||||
const params = (await request.json()) as AISuggestionParams;
|
||||
if (!params.code || !params.language || !params.task) {
|
||||
return json({ success: false, error: 'Missing required parameters' }, { status: 400 });
|
||||
}
|
||||
|
||||
const result = await getAISuggestions(params);
|
||||
return json(result);
|
||||
} catch (error: any) {
|
||||
console.error('AI Assistant API Error:', error);
|
||||
return json({ success: false, error: error.message || 'An unexpected error occurred' }, { status: 500 });
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user