diff --git a/surfsense_backend/app/tasks/document_processors/_save.py b/surfsense_backend/app/tasks/document_processors/_save.py index 3b9616cbd..9456e2f1c 100644 --- a/surfsense_backend/app/tasks/document_processors/_save.py +++ b/surfsense_backend/app/tasks/document_processors/_save.py @@ -10,6 +10,7 @@ from app.utils.document_converters import ( create_document_chunks, embed_text, generate_content_hash, + truncate_for_embedding, ) from ._helpers import ( @@ -73,7 +74,9 @@ async def save_file_document( if should_skip: return doc - document_content = f"File: {file_name}\n\n{markdown_content[:4000]}" + document_content = ( + f"File: {file_name}\n\n{truncate_for_embedding(markdown_content)}" + ) document_embedding = embed_text(document_content) chunks = await create_document_chunks(markdown_content) doc_metadata = {"FILE_NAME": file_name, "ETL_SERVICE": etl_service} diff --git a/surfsense_backend/app/tasks/document_processors/markdown_processor.py b/surfsense_backend/app/tasks/document_processors/markdown_processor.py index 19a4df87d..463951a64 100644 --- a/surfsense_backend/app/tasks/document_processors/markdown_processor.py +++ b/surfsense_backend/app/tasks/document_processors/markdown_processor.py @@ -13,6 +13,7 @@ from app.utils.document_converters import ( create_document_chunks, embed_text, generate_content_hash, + truncate_for_embedding, ) from ._helpers import ( @@ -182,7 +183,9 @@ async def add_received_markdown_file_document( return doc # Content changed - continue to update - summary_content = f"File: {file_name}\n\n{file_in_markdown[:4000]}" + summary_content = ( + f"File: {file_name}\n\n{truncate_for_embedding(file_in_markdown)}" + ) summary_embedding = embed_text(summary_content) # Process chunks diff --git a/surfsense_backend/app/utils/blocknote_to_markdown.py b/surfsense_backend/app/utils/blocknote_to_markdown.py index e26a9f4ee..38393a370 100644 --- a/surfsense_backend/app/utils/blocknote_to_markdown.py +++ b/surfsense_backend/app/utils/blocknote_to_markdown.py @@ -145,7 +145,7 @@ def _render_block( code_text = _render_inline_content(content) if content else "" lines.append(f"{prefix}```{language}") for code_line in code_text.split("\n"): - lines.append(f"{prefix}{code_line}") + lines.append(code_line) lines.append(f"{prefix}```") elif block_type == "table": diff --git a/surfsense_web/components/assistant-ui/inline-mention-editor.tsx b/surfsense_web/components/assistant-ui/inline-mention-editor.tsx index 5fc942e54..ee2d1c873 100644 --- a/surfsense_web/components/assistant-ui/inline-mention-editor.tsx +++ b/surfsense_web/components/assistant-ui/inline-mention-editor.tsx @@ -696,7 +696,9 @@ export const InlineMentionEditor = forwardRef { // Arrow / Enter / Escape navigation for the active picker. const handleKeyDown = useCallback( (e: React.KeyboardEvent) => { + // While an IME composition is active (e.g. confirming a Japanese/Chinese/ + // Korean conversion), let the Enter/Arrow keys reach the IME instead of + // driving picker navigation/selection. + if (e.nativeEvent.isComposing) return; if (showPromptPicker) { if (e.key === "ArrowDown") { e.preventDefault(); diff --git a/surfsense_web/components/free-chat/free-composer.tsx b/surfsense_web/components/free-chat/free-composer.tsx index 162b906ad..d4523a4f9 100644 --- a/surfsense_web/components/free-chat/free-composer.tsx +++ b/surfsense_web/components/free-chat/free-composer.tsx @@ -99,7 +99,9 @@ export const FreeComposer: FC = () => { gate("mention documents"); return; } - if (e.key === "Enter" && !e.shiftKey) { + // Ignore Enter while an IME composition is active (e.g. confirming a + // Japanese/Chinese/Korean conversion) so it doesn't submit the message. + if (e.key === "Enter" && !e.shiftKey && !e.nativeEvent.isComposing) { e.preventDefault(); if (text.trim()) { aui.composer().send(); diff --git a/surfsense_web/lib/apis/notes-api.service.ts b/surfsense_web/lib/apis/notes-api.service.ts deleted file mode 100644 index eac3d96ed..000000000 --- a/surfsense_web/lib/apis/notes-api.service.ts +++ /dev/null @@ -1,147 +0,0 @@ -import { z } from "zod"; -import { ValidationError } from "../error"; -import { baseApiService } from "./base-api.service"; - -// Request/Response schemas -const createNoteRequest = z.object({ - search_space_id: z.number(), - title: z.string().min(1), - source_markdown: z.string().optional(), -}); - -const createNoteResponse = z.object({ - id: z.number(), - title: z.string(), - document_type: z.string(), - content: z.string(), - content_hash: z.string(), - unique_identifier_hash: z.string().nullable(), - document_metadata: z.record(z.string(), z.any()).nullable(), - search_space_id: z.number(), - created_at: z.string(), - updated_at: z.string().nullable(), -}); - -const getNotesRequest = z.object({ - search_space_id: z.number(), - skip: z.number().optional(), - page: z.number().optional(), - page_size: z.number().optional(), -}); - -const noteItem = z.object({ - id: z.number(), - title: z.string(), - document_type: z.string(), - content: z.string(), - content_hash: z.string(), - unique_identifier_hash: z.string().nullable(), - document_metadata: z.record(z.string(), z.any()).nullable(), - search_space_id: z.number(), - created_at: z.string(), - updated_at: z.string().nullable(), -}); - -const getNotesResponse = z.object({ - items: z.array(noteItem), - total: z.number(), - page: z.number(), - page_size: z.number(), - has_more: z.boolean(), -}); - -const deleteNoteRequest = z.object({ - search_space_id: z.number(), - note_id: z.number(), -}); - -const deleteNoteResponse = z.object({ - message: z.string(), - note_id: z.number(), -}); - -// Type exports -export type CreateNoteRequest = z.infer; -export type CreateNoteResponse = z.infer; -export type GetNotesRequest = z.infer; -export type GetNotesResponse = z.infer; -export type NoteItem = z.infer; -export type DeleteNoteRequest = z.infer; -export type DeleteNoteResponse = z.infer; - -class NotesApiService { - /** - * Create a new note - */ - createNote = async (request: CreateNoteRequest) => { - const parsedRequest = createNoteRequest.safeParse(request); - - if (!parsedRequest.success) { - console.error("Invalid request:", parsedRequest.error); - const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", "); - throw new ValidationError(`Invalid request: ${errorMessage}`); - } - - const { search_space_id, title, source_markdown } = parsedRequest.data; - - // Send both title and source_markdown in request body - const body = { - title, - ...(source_markdown !== undefined && { source_markdown }), - }; - - return baseApiService.post( - `/api/v1/search-spaces/${search_space_id}/notes`, - createNoteResponse, - { body } - ); - }; - - /** - * Get list of notes - */ - getNotes = async (request: GetNotesRequest) => { - const parsedRequest = getNotesRequest.safeParse(request); - - if (!parsedRequest.success) { - console.error("Invalid request:", parsedRequest.error); - const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", "); - throw new ValidationError(`Invalid request: ${errorMessage}`); - } - - const { search_space_id, skip, page, page_size } = parsedRequest.data; - - // Build query params - const params = new URLSearchParams(); - if (skip !== undefined) params.append("skip", String(skip)); - if (page !== undefined) params.append("page", String(page)); - if (page_size !== undefined) params.append("page_size", String(page_size)); - - return baseApiService.get( - `/api/v1/search-spaces/${search_space_id}/notes?${params.toString()}`, - getNotesResponse - ); - }; - - /** - * Delete a note - */ - deleteNote = async (request: DeleteNoteRequest) => { - const parsedRequest = deleteNoteRequest.safeParse(request); - - if (!parsedRequest.success) { - console.error("Invalid request:", parsedRequest.error); - const errorMessage = parsedRequest.error.issues.map((issue) => issue.message).join(", "); - throw new ValidationError(`Invalid request: ${errorMessage}`); - } - - const { search_space_id, note_id } = parsedRequest.data; - - return baseApiService.delete( - `/api/v1/search-spaces/${search_space_id}/notes/${note_id}`, - deleteNoteResponse - ); - }; -} - -export const notesApiService = new NotesApiService();