feat: AI Writer inline — génère du contenu au curseur
- Mode 'write' ajouté à paragraph-refactor.service.ts - Endpoint /api/ai/reformulate étendu (option 'write' + writePrompt) - UI : champ de prompt inline apparaît au curseur après slash menu - Tape / → 'Écrire avec l'IA' → décrit ce que tu veux → Entrée - L'IA génère et insère le contenu à la position du curseur - Pas de migration DB, réutilise l'infra existante - i18n FR/EN
This commit is contained in:
@@ -10,7 +10,7 @@ import { LanguageDetectionService } from './language-detection.service'
|
||||
import { getTagsProvider } from '../factory'
|
||||
import { getSystemConfig } from '@/lib/config'
|
||||
|
||||
export type RefactorMode = 'clarify' | 'shorten' | 'improveStyle' | 'fix_grammar' | 'translate' | 'explain'
|
||||
export type RefactorMode = 'clarify' | 'shorten' | 'improveStyle' | 'fix_grammar' | 'translate' | 'explain' | 'write'
|
||||
|
||||
export interface RefactorOption {
|
||||
mode: RefactorMode
|
||||
@@ -69,24 +69,29 @@ export class ParagraphRefactorService {
|
||||
content: string,
|
||||
mode: RefactorMode,
|
||||
format: 'html' | 'markdown' = 'markdown',
|
||||
targetLanguage?: string
|
||||
targetLanguage?: string,
|
||||
writePrompt?: string
|
||||
): Promise<RefactorResult> {
|
||||
// Validate word count
|
||||
const wordCount = content.split(/\s+/).length
|
||||
if (wordCount < this.MIN_WORDS || wordCount > this.MAX_WORDS) {
|
||||
throw new Error(
|
||||
`Please select ${this.MIN_WORDS}-${this.MAX_WORDS} words to reformulate`
|
||||
)
|
||||
// 'write' mode skips word count validation (generates from prompt, no selection needed)
|
||||
if (mode !== 'write') {
|
||||
const wordCount = content.split(/\s+/).length
|
||||
if (wordCount < this.MIN_WORDS || wordCount > this.MAX_WORDS) {
|
||||
throw new Error(
|
||||
`Please select ${this.MIN_WORDS}-${this.MAX_WORDS} words to reformulate`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Detect language or use provided target language
|
||||
const { language: detectedLanguage } = await this.languageDetection.detectLanguage(content)
|
||||
const { language: detectedLanguage } = mode === 'write'
|
||||
? { language: targetLanguage || 'fr' }
|
||||
: await this.languageDetection.detectLanguage(content)
|
||||
const language = targetLanguage || detectedLanguage
|
||||
|
||||
try {
|
||||
// Build prompts
|
||||
const systemPrompt = this.getSystemPrompt(mode, format)
|
||||
const userPrompt = this.getUserPrompt(mode, content, language, format)
|
||||
const userPrompt = this.getUserPrompt(mode, content, language, format, writePrompt)
|
||||
|
||||
// Get AI provider from factory
|
||||
const config = await getSystemConfig()
|
||||
@@ -99,10 +104,10 @@ export class ParagraphRefactorService {
|
||||
// Calculate word count change
|
||||
const refactoredWordCount = refactored.split(/\s+/).length
|
||||
const wordCountChange = {
|
||||
original: wordCount,
|
||||
original: mode === 'write' ? 0 : content.split(/\s+/).length,
|
||||
refactored: refactoredWordCount,
|
||||
difference: refactoredWordCount - wordCount,
|
||||
percentage: ((refactoredWordCount - wordCount) / wordCount) * 100
|
||||
difference: refactoredWordCount - (mode === 'write' ? 0 : content.split(/\s+/).length),
|
||||
percentage: 0
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -252,7 +257,13 @@ Your goal: Explain the selected text, concept, or word clearly and concisely.
|
||||
Provide context, definitions, or relevant information to help the user understand it.
|
||||
|
||||
CRITICAL LANGUAGE RULE: You MUST explain it in the requested language (which is the user's interface language).
|
||||
Keep it concise but informative.${formatInstruction}`
|
||||
Keep it concise but informative.${formatInstruction}`,
|
||||
|
||||
write: `You are an expert writer and content creator.
|
||||
Your goal: Write new content based on the user's instruction. Be clear, well-structured, and informative.
|
||||
Write naturally in the requested language.
|
||||
|
||||
If context is provided (note title or surrounding text), use it to make the content relevant.${formatInstruction}`
|
||||
}
|
||||
|
||||
return prompts[mode]
|
||||
@@ -261,7 +272,7 @@ Keep it concise but informative.${formatInstruction}`
|
||||
/**
|
||||
* Get mode-specific user prompt
|
||||
*/
|
||||
private getUserPrompt(mode: RefactorMode, content: string, language: string, format: 'html' | 'markdown' = 'markdown'): string {
|
||||
private getUserPrompt(mode: RefactorMode, content: string, language: string, format: 'html' | 'markdown' = 'markdown', writePrompt?: string): string {
|
||||
const instructions = {
|
||||
clarify: `IMPORTANT: The text below is in ${language}. Your response MUST be in ${language}. Do NOT translate to English.
|
||||
|
||||
@@ -283,7 +294,20 @@ Please fix any spelling, grammar, or punctuation errors in this ${language} text
|
||||
Only return the translated text, nothing else.`,
|
||||
|
||||
explain: `Please explain the following text/concept in ${language}.
|
||||
Keep the explanation clear, educational, and concise.`
|
||||
Keep the explanation clear, educational, and concise.`,
|
||||
|
||||
write: `Write the following in ${language}. Only return the content, no meta-commentary.`
|
||||
}
|
||||
|
||||
if (mode === 'write') {
|
||||
const suffix = `CRITICAL: Respond ONLY with the generated content in ${language}. No explanations, no meta-commentary. Format strictly as ${format === 'html' ? 'HTML tags' : 'Markdown'}.`
|
||||
const contextPart = content.trim() ? `\n\nContext (note: "${content.trim().slice(0, 100)}"):\n` : ''
|
||||
return `${instructions.write}
|
||||
|
||||
${writePrompt || 'Write a paragraph about the current topic.'}
|
||||
|
||||
${contextPart}
|
||||
${suffix}`
|
||||
}
|
||||
|
||||
const systemSuffix = mode === 'explain'
|
||||
|
||||
Reference in New Issue
Block a user