cache sharing for posts. Begin infill implementation.

This commit is contained in:
Michael Freno
2025-12-26 15:14:50 -05:00
parent b412db92e5
commit 13a22bfeb3
7 changed files with 184 additions and 76 deletions

View File

@@ -644,6 +644,15 @@ export default function TextEditor(props: TextEditorProps) {
let isInitialLoad = true; // Flag to prevent capturing history on initial load
let hasAttemptedHistoryLoad = false; // Flag to prevent repeated load attempts
// LLM Infill state
const [currentSuggestion, setCurrentSuggestion] = createSignal<string>("");
const [isInfillLoading, setIsInfillLoading] = createSignal(false);
const [infillConfig, setInfillConfig] = createSignal<{
endpoint: string;
token: string;
} | null>(null);
let infillDebounceTimer: ReturnType<typeof setTimeout> | null = null;
// Force reactive updates for button states
const [editorState, setEditorState] = createSignal(0);
@@ -682,6 +691,67 @@ export default function TextEditor(props: TextEditorProps) {
return `${baseClasses} ${activeClass} ${hoverClass}`.trim();
};
// Fetch infill config on mount (admin-only, desktop-only)
createEffect(async () => {
try {
const config = await api.infill.getConfig.query();
if (config.endpoint && config.token) {
setInfillConfig({ endpoint: config.endpoint, token: config.token });
console.log("✅ Infill enabled for admin");
}
} catch (error) {
console.error("Failed to fetch infill config:", error);
}
});
// Request LLM infill suggestion
const requestInfill = async (): Promise<void> => {
const config = infillConfig();
if (!config) return;
const context = getEditorContext();
if (!context) return;
setIsInfillLoading(true);
try {
const response = await fetch(config.endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${config.token}`
},
body: JSON.stringify({
model: "default",
messages: [
{
role: "user",
content: `Continue writing from this context:\n\nBefore cursor: ${context.prefix}\n\nAfter cursor: ${context.suffix}`
}
],
max_tokens: 100,
temperature: 0.3,
stop: ["\n\n"]
})
});
if (!response.ok) {
throw new Error(`Infill request failed: ${response.status}`);
}
const data = await response.json();
const suggestion = data.choices?.[0]?.message?.content || "";
if (suggestion.trim()) {
setCurrentSuggestion(suggestion.trim());
}
} catch (error) {
console.error("Infill request failed:", error);
setCurrentSuggestion("");
} finally {
setIsInfillLoading(false);
}
};
// Capture history snapshot
const captureHistory = async (editorInstance: any) => {
// Skip if initial load
@@ -856,6 +926,34 @@ export default function TextEditor(props: TextEditorProps) {
}
};
// Extract editor context for LLM infill (512 chars before/after cursor)
const getEditorContext = (): {
prefix: string;
suffix: string;
cursorPos: number;
} | null => {
const instance = editor();
if (!instance) return null;
const { state } = instance;
const cursorPos = state.selection.$anchor.pos;
const text = state.doc.textContent;
if (text.length === 0) return null;
const prefix = text.slice(Math.max(0, cursorPos - 512), cursorPos);
const suffix = text.slice(
cursorPos,
Math.min(text.length, cursorPos + 512)
);
return {
prefix,
suffix,
cursorPos
};
};
const editor = createTiptapEditor(() => ({
element: editorRef,
extensions: [