fix(chart): transaction mapping fix + add cache for suggestions
- Fix transaction.mapping.map usage - it returns a position, not mapping - Add 5-minute in-memory cache for chart suggestions (max 50 entries) - Cache key based on content hash + selection - Significantly improves UX when re-analyzing same content Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -297,7 +297,7 @@ export const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorPro
|
|||||||
try {
|
try {
|
||||||
console.log('[handleSelectChart] Inserting chart type:', chartContent.split('\n')[0])
|
console.log('[handleSelectChart] Inserting chart type:', chartContent.split('\n')[0])
|
||||||
|
|
||||||
const { from, to } = editor.state.selection
|
const { from } = editor.state.selection
|
||||||
const { tr } = editor.state
|
const { tr } = editor.state
|
||||||
const { schema } = editor.state
|
const { schema } = editor.state
|
||||||
|
|
||||||
@@ -313,17 +313,21 @@ export const RichTextEditor = forwardRef<RichTextEditorHandle, RichTextEditorPro
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Replace selection with the chart node
|
// Insert the chart node at current position
|
||||||
const transaction = tr.replaceWith(from, to, chartNode)
|
const transaction = tr.insert(from, chartNode)
|
||||||
|
|
||||||
|
// Get the position after the inserted chart
|
||||||
|
const insertPos = transaction.mapping.map(from)
|
||||||
|
const chartSize = chartNode.nodeSize
|
||||||
|
|
||||||
// Add a paragraph after for continued typing
|
// Add a paragraph after for continued typing
|
||||||
const paragraph = schema.nodes.paragraph.create()
|
const paragraph = schema.nodes.paragraph.create()
|
||||||
transaction.insert(transaction.mapping.map(to).map(to), paragraph)
|
transaction.insert(insertPos + chartSize, paragraph)
|
||||||
|
|
||||||
editor.view.dispatch(transaction)
|
editor.view.dispatch(transaction)
|
||||||
editor.chain().focus().run()
|
editor.chain().focus().run()
|
||||||
|
|
||||||
console.log('[handleSelectChart] Chart inserted')
|
console.log('[handleSelectChart] Chart inserted successfully')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[handleSelectChart] Failed:', error)
|
console.error('[handleSelectChart] Failed:', error)
|
||||||
toast.error('Failed to insert chart: ' + (error as Error).message)
|
toast.error('Failed to insert chart: ' + (error as Error).message)
|
||||||
|
|||||||
@@ -26,12 +26,55 @@ export interface SuggestChartsRequest {
|
|||||||
noteId?: string
|
noteId?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Simple in-memory cache with 5-minute TTL
|
||||||
|
const CACHE_TTL = 5 * 60 * 1000 // 5 minutes
|
||||||
|
const cache = new Map<string, { data: SuggestChartsResponse; timestamp: number }>()
|
||||||
|
|
||||||
|
function getCacheKey(content: string, selection: string | null | undefined): string {
|
||||||
|
// Use content hash + selection as cache key
|
||||||
|
const textToHash = selection ? content + ':::' + selection : content
|
||||||
|
// Simple hash function
|
||||||
|
let hash = 0
|
||||||
|
for (let i = 0; i < textToHash.length; i++) {
|
||||||
|
hash = ((hash << 5) - hash) + textToHash.charCodeAt(i)
|
||||||
|
hash = hash & hash // Convert to 32bit integer
|
||||||
|
}
|
||||||
|
return String(hash)
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCached(key: string): SuggestChartsResponse | null {
|
||||||
|
const entry = cache.get(key)
|
||||||
|
if (!entry) return null
|
||||||
|
if (Date.now() - entry.timestamp > CACHE_TTL) {
|
||||||
|
cache.delete(key)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
console.log('[chart-suggestion] Cache hit for key:', key)
|
||||||
|
return entry.data
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCached(key: string, data: SuggestChartsResponse): void {
|
||||||
|
cache.set(key, { data, timestamp: Date.now() })
|
||||||
|
// Limit cache size to 50 entries
|
||||||
|
if (cache.size > 50) {
|
||||||
|
const firstKey = cache.keys().next().value
|
||||||
|
cache.delete(firstKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Call the AI chart suggestions API
|
* Call the AI chart suggestions API
|
||||||
* @param request - The request parameters
|
* @param request - The request parameters
|
||||||
* @returns Promise with the chart suggestions
|
* @returns Promise with the chart suggestions
|
||||||
*/
|
*/
|
||||||
export async function suggestCharts(request: SuggestChartsRequest): Promise<SuggestChartsResponse> {
|
export async function suggestCharts(request: SuggestChartsRequest): Promise<SuggestChartsResponse> {
|
||||||
|
// Check cache first
|
||||||
|
const cacheKey = getCacheKey(request.content || '', request.selection)
|
||||||
|
const cached = getCached(cacheKey)
|
||||||
|
if (cached) {
|
||||||
|
return cached
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/ai/suggest-charts', {
|
const response = await fetch('/api/ai/suggest-charts', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
@@ -56,8 +99,14 @@ export async function suggestCharts(request: SuggestChartsRequest): Promise<Sugg
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await response.json()
|
const data = await response.json() as SuggestChartsResponse
|
||||||
return data as SuggestChartsResponse
|
|
||||||
|
// Cache successful responses
|
||||||
|
if (!data.error && !data.quotaExceeded) {
|
||||||
|
setCached(cacheKey, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[suggestCharts] Network error:', error)
|
console.error('[suggestCharts] Network error:', error)
|
||||||
return {
|
return {
|
||||||
|
|||||||
Reference in New Issue
Block a user