({
+ queryKey: isDocsChunk
+ ? cacheKeys.documents.byChunk(`doc-${chunkId}`)
+ : cacheKeys.documents.byChunk(chunkId.toString()),
+ queryFn: async () => {
+ if (isDocsChunk) {
+ return documentsApiService.getSurfsenseDocByChunk(chunkId);
+ }
+ return documentsApiService.getDocumentByChunk({ chunk_id: chunkId });
+ },
enabled: !!chunkId && open,
staleTime: 5 * 60 * 1000,
});
@@ -325,7 +340,7 @@ export function SourceDetailPanel({
{documentData?.title || title || "Source Document"}
- {documentData
+ {documentData && "document_type" in documentData
? formatDocumentType(documentData.document_type)
: sourceType && formatDocumentType(sourceType)}
{documentData?.chunks && (
@@ -491,7 +506,8 @@ export function SourceDetailPanel({
{/* Document Metadata */}
- {documentData.document_metadata &&
+ {"document_metadata" in documentData &&
+ documentData.document_metadata &&
Object.keys(documentData.document_metadata).length > 0 && (
;
export type DeleteDocumentRequest = z.infer;
export type DeleteDocumentResponse = z.infer;
export type DocumentTypeEnum = z.infer;
+export type SurfsenseDocsChunk = z.infer;
+export type SurfsenseDocsDocument = z.infer;
+export type SurfsenseDocsDocumentWithChunks = z.infer;
+export type GetSurfsenseDocsByChunkRequest = z.infer;
+export type GetSurfsenseDocsByChunkResponse = z.infer;
diff --git a/surfsense_web/lib/apis/base-api.service.ts b/surfsense_web/lib/apis/base-api.service.ts
index ff71fe14c..5849003e2 100644
--- a/surfsense_web/lib/apis/base-api.service.ts
+++ b/surfsense_web/lib/apis/base-api.service.ts
@@ -129,20 +129,24 @@ class BaseApiService {
throw new AppError("Failed to parse response", response.status, response.statusText);
}
+ // Handle 401 first before other error handling - ensures token is cleared and user redirected
+ if (response.status === 401) {
+ handleUnauthorized();
+ throw new AuthenticationError(
+ typeof data === "object" && "detail" in data
+ ? data.detail
+ : "You are not authenticated. Please login again.",
+ response.status,
+ response.statusText
+ );
+ }
+
// For fastapi errors response
if (typeof data === "object" && "detail" in data) {
throw new AppError(data.detail, response.status, response.statusText);
}
switch (response.status) {
- case 401:
- // Use centralized auth handler for 401 responses
- handleUnauthorized();
- throw new AuthenticationError(
- "You are not authenticated. Please login again.",
- response.status,
- response.statusText
- );
case 403:
throw new AuthorizationError(
"You don't have permission to access this resource.",
diff --git a/surfsense_web/lib/apis/documents-api.service.ts b/surfsense_web/lib/apis/documents-api.service.ts
index cf7a4b778..2e7d18e44 100644
--- a/surfsense_web/lib/apis/documents-api.service.ts
+++ b/surfsense_web/lib/apis/documents-api.service.ts
@@ -17,6 +17,7 @@ import {
getDocumentsResponse,
getDocumentTypeCountsRequest,
getDocumentTypeCountsResponse,
+ getSurfsenseDocsByChunkResponse,
type SearchDocumentsRequest,
searchDocumentsRequest,
searchDocumentsResponse,
@@ -209,6 +210,17 @@ class DocumentsApiService {
);
};
+ /**
+ * Get Surfsense documentation by chunk ID
+ * Used for resolving [citation:doc-XXX] citations
+ */
+ getSurfsenseDocByChunk = async (chunkId: number) => {
+ return baseApiService.get(
+ `/api/v1/surfsense-docs/by-chunk/${chunkId}`,
+ getSurfsenseDocsByChunkResponse
+ );
+ };
+
/**
* Update a document
*/
diff --git a/surfsense_web/messages/en.json b/surfsense_web/messages/en.json
index b803d4b69..57f03a0fb 100644
--- a/surfsense_web/messages/en.json
+++ b/surfsense_web/messages/en.json
@@ -28,7 +28,10 @@
"info": "Information",
"required": "Required",
"optional": "Optional",
- "retry": "Retry"
+ "retry": "Retry",
+ "owner": "Owner",
+ "shared": "Shared",
+ "settings": "Settings"
},
"auth": {
"login": "Login",
@@ -77,6 +80,45 @@
"creating_account_btn": "Creating account...",
"redirecting_login": "Redirecting to login page..."
},
+ "searchSpace": {
+ "create_title": "Create Search Space",
+ "create_description": "Create a new search space to organize your knowledge",
+ "name_label": "Name",
+ "name_placeholder": "Enter search space name",
+ "description_label": "Description",
+ "description_placeholder": "What is this search space for?",
+ "create_button": "Create",
+ "creating": "Creating...",
+ "all_search_spaces": "All Search Spaces",
+ "search_spaces_count": "{count, plural, =0 {No search spaces} =1 {1 search space} other {# search spaces}}",
+ "no_search_spaces": "No search spaces yet",
+ "create_first_search_space": "Create your first search space to get started",
+ "members_count": "{count, plural, =1 {1 member} other {# members}}",
+ "create_new_search_space": "Create new search space",
+ "delete_title": "Delete Search Space",
+ "delete_confirm": "Are you sure you want to delete \"{name}\"? This action cannot be undone and will permanently remove all data.",
+ "welcome_title": "Welcome to SurfSense",
+ "welcome_description": "Create your first search space to start organizing your knowledge, connecting sources, and chatting with AI.",
+ "create_first_button": "Create your first search space"
+ },
+ "userSettings": {
+ "title": "User Settings",
+ "description": "Manage your account settings and API access",
+ "back_to_app": "Back to app",
+ "footer": "User Settings",
+ "api_key_nav_label": "API Key",
+ "api_key_nav_description": "Manage your API access token",
+ "api_key_title": "API Key",
+ "api_key_description": "Use this key to authenticate API requests",
+ "api_key_warning_title": "Keep it secret",
+ "api_key_warning_description": "Your API key grants full access to your account. Never share it publicly or commit it to version control.",
+ "your_api_key": "Your API Key",
+ "copied": "Copied!",
+ "copy": "Copy to clipboard",
+ "no_api_key": "No API key found",
+ "usage_title": "How to use",
+ "usage_description": "Include your API key in the Authorization header:"
+ },
"dashboard": {
"title": "Dashboard",
"search_spaces": "Search Spaces",
@@ -624,12 +666,13 @@
"no_archived_chats": "No archived chats",
"error_archiving_chat": "Failed to archive chat",
"new_chat": "New chat",
- "select_workspace": "Select Workspace",
- "invite_members": "Invite members",
- "workspace_settings": "Workspace settings",
- "see_all_workspaces": "See all search spaces",
+ "select_search_space": "Select Search Space",
+ "manage_members": "Manage members",
+ "search_space_settings": "Search Space settings",
+ "see_all_search_spaces": "See all search spaces",
"expand_sidebar": "Expand sidebar",
"collapse_sidebar": "Collapse sidebar",
+ "user_settings": "User settings",
"logout": "Logout"
},
"errors": {
diff --git a/surfsense_web/messages/zh.json b/surfsense_web/messages/zh.json
index fa690bf39..89cb7813a 100644
--- a/surfsense_web/messages/zh.json
+++ b/surfsense_web/messages/zh.json
@@ -28,7 +28,10 @@
"info": "信息",
"required": "必填",
"optional": "可选",
- "retry": "重试"
+ "retry": "重试",
+ "owner": "所有者",
+ "shared": "共享",
+ "settings": "设置"
},
"auth": {
"login": "登录",
@@ -77,6 +80,45 @@
"creating_account_btn": "创建中...",
"redirecting_login": "正在跳转到登录页面..."
},
+ "searchSpace": {
+ "create_title": "创建搜索空间",
+ "create_description": "创建一个新的搜索空间来组织您的知识",
+ "name_label": "名称",
+ "name_placeholder": "输入搜索空间名称",
+ "description_label": "描述",
+ "description_placeholder": "这个搜索空间是做什么的?",
+ "create_button": "创建",
+ "creating": "创建中...",
+ "all_search_spaces": "所有搜索空间",
+ "search_spaces_count": "{count, plural, =0 {没有搜索空间} other {# 个搜索空间}}",
+ "no_search_spaces": "暂无搜索空间",
+ "create_first_search_space": "创建您的第一个搜索空间以开始使用",
+ "members_count": "{count, plural, other {# 位成员}}",
+ "create_new_search_space": "创建新的搜索空间",
+ "delete_title": "删除搜索空间",
+ "delete_confirm": "您确定要删除「{name}」吗?此操作无法撤销,将永久删除所有数据。",
+ "welcome_title": "欢迎使用 SurfSense",
+ "welcome_description": "创建您的第一个搜索空间,开始组织知识、连接数据源并与AI对话。",
+ "create_first_button": "创建第一个搜索空间"
+ },
+ "userSettings": {
+ "title": "用户设置",
+ "description": "管理您的账户设置和API访问",
+ "back_to_app": "返回应用",
+ "footer": "用户设置",
+ "api_key_nav_label": "API密钥",
+ "api_key_nav_description": "管理您的API访问令牌",
+ "api_key_title": "API密钥",
+ "api_key_description": "使用此密钥验证API请求",
+ "api_key_warning_title": "请保密",
+ "api_key_warning_description": "您的API密钥可以完全访问您的账户。请勿公开分享或提交到版本控制。",
+ "your_api_key": "您的API密钥",
+ "copied": "已复制!",
+ "copy": "复制到剪贴板",
+ "no_api_key": "未找到API密钥",
+ "usage_title": "使用方法",
+ "usage_description": "在Authorization请求头中包含您的API密钥:"
+ },
"dashboard": {
"title": "仪表盘",
"search_spaces": "搜索空间",
@@ -618,12 +660,13 @@
"view_all_notes": "查看所有笔记",
"add_note": "添加笔记",
"new_chat": "新对话",
- "select_workspace": "选择工作空间",
- "invite_members": "邀请成员",
- "workspace_settings": "工作空间设置",
- "see_all_workspaces": "查看所有搜索空间",
+ "select_search_space": "选择搜索空间",
+ "manage_members": "管理成员",
+ "search_space_settings": "搜索空间设置",
+ "see_all_search_spaces": "查看所有搜索空间",
"expand_sidebar": "展开侧边栏",
"collapse_sidebar": "收起侧边栏",
+ "user_settings": "用户设置",
"logout": "退出登录"
},
"errors": {