Skip to content

Commit b98c312

Browse files
committed
Merge remote-tracking branch 'upstream/main' into feature/blocknote-editor
2 parents f8e4926 + 0e9efd6 commit b98c312

File tree

81 files changed

+8958
-2369
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

81 files changed

+8958
-2369
lines changed

README.md

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -38,19 +38,24 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7
3838
## Key Features
3939

4040
### 💡 **Idea**:
41-
Have your own highly customizable private NotebookLM and Perplexity integrated with external sources.
41+
- Have your own highly customizable private NotebookLM and Perplexity integrated with external sources.
4242
### 📁 **Multiple File Format Uploading Support**
43-
Save content from your own personal files *(Documents, images, videos and supports **50+ file extensions**)* to your own personal knowledge base .
43+
- Save content from your own personal files *(Documents, images, videos and supports **50+ file extensions**)* to your own personal knowledge base .
4444
### 🔍 **Powerful Search**
45-
Quickly research or find anything in your saved content .
45+
- Quickly research or find anything in your saved content .
4646
### 💬 **Chat with your Saved Content**
47-
Interact in Natural Language and get cited answers.
47+
- Interact in Natural Language and get cited answers.
4848
### 📄 **Cited Answers**
49-
Get Cited answers just like Perplexity.
49+
- Get Cited answers just like Perplexity.
5050
### 🔔 **Privacy & Local LLM Support**
51-
Works Flawlessly with Ollama local LLMs.
51+
- Works Flawlessly with Ollama local LLMs.
5252
### 🏠 **Self Hostable**
53-
Open source and easy to deploy locally.
53+
- Open source and easy to deploy locally.
54+
### 👥 **Team Collaboration with RBAC**
55+
- Role-Based Access Control for Search Spaces
56+
- Invite team members with customizable roles (Owner, Admin, Editor, Viewer)
57+
- Granular permissions for documents, chats, connectors, and settings
58+
- Share knowledge bases securely within your organization
5459
### 🎙️ Podcasts
5560
- Blazingly fast podcast generation agent. (Creates a 3-minute podcast in under 20 seconds.)
5661
- Convert your chat conversations into engaging audio content

README.zh-CN.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,25 +39,31 @@ https://github.com/user-attachments/assets/a0a16566-6967-4374-ac51-9b3e07fbecd7
3939
## 核心功能
4040

4141
### 💡 **理念**:
42-
拥有您自己的高度可定制的私有 NotebookLM 和 Perplexity,并与外部数据源集成。
42+
- 拥有您自己的高度可定制的私有 NotebookLM 和 Perplexity,并与外部数据源集成。
4343

4444
### 📁 **支持多种文件格式上传**
45-
将您个人文件中的内容(文档、图像、视频,支持 **50+ 种文件扩展名**)保存到您自己的个人知识库。
45+
- 将您个人文件中的内容(文档、图像、视频,支持 **50+ 种文件扩展名**)保存到您自己的个人知识库。
4646

4747
### 🔍 **强大的搜索功能**
48-
快速研究或查找已保存内容中的任何信息。
48+
- 快速研究或查找已保存内容中的任何信息。
4949

5050
### 💬 **与已保存内容对话**
51-
使用自然语言交互并获得引用答案。
51+
- 使用自然语言交互并获得引用答案。
5252

5353
### 📄 **引用答案**
54-
像 Perplexity 一样获得带引用的答案。
54+
- 像 Perplexity 一样获得带引用的答案。
5555

5656
### 🔔 **隐私保护与本地 LLM 支持**
57-
完美支持 Ollama 本地大语言模型。
57+
- 完美支持 Ollama 本地大语言模型。
5858

5959
### 🏠 **可自托管**
60-
开源且易于本地部署。
60+
- 开源且易于本地部署。
61+
62+
### 👥 **团队协作与 RBAC**
63+
- 搜索空间的基于角色的访问控制
64+
- 使用可自定义的角色(所有者、管理员、编辑者、查看者)邀请团队成员
65+
- 对文档、聊天、连接器和设置的细粒度权限控制
66+
- 在组织内安全共享知识库
6167

6268
### 🎙️ **播客功能**
6369
- 超快速播客生成代理(在 20 秒内创建 3 分钟播客)

surfsense_backend/alembic/versions/36_remove_fk_constraints_for_global_llm_configs.py

Lines changed: 59 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88

99
from collections.abc import Sequence
1010

11+
from sqlalchemy import text
12+
1113
from alembic import op
1214

1315
# revision identifiers, used by Alembic.
@@ -17,57 +19,76 @@
1719
depends_on: str | Sequence[str] | None = None
1820

1921

22+
def constraint_exists(connection, table_name: str, constraint_name: str) -> bool:
23+
"""Check if a constraint exists on the given table."""
24+
result = connection.execute(
25+
text(
26+
"""
27+
SELECT 1 FROM information_schema.table_constraints
28+
WHERE table_name = :table_name AND constraint_name = :constraint_name
29+
"""
30+
),
31+
{"table_name": table_name, "constraint_name": constraint_name},
32+
)
33+
return result.fetchone() is not None
34+
35+
2036
def upgrade() -> None:
2137
"""
2238
Remove foreign key constraints on LLM preference columns to allow global configs (negative IDs).
2339
2440
Global LLM configs use negative IDs and don't exist in the llm_configs table,
2541
so we need to remove the foreign key constraints that were preventing their use.
2642
"""
27-
# Drop the foreign key constraints
28-
op.drop_constraint(
43+
connection = op.get_bind()
44+
45+
# Drop the foreign key constraints if they exist
46+
constraints_to_drop = [
2947
"user_search_space_preferences_long_context_llm_id_fkey",
30-
"user_search_space_preferences",
31-
type_="foreignkey",
32-
)
33-
op.drop_constraint(
3448
"user_search_space_preferences_fast_llm_id_fkey",
35-
"user_search_space_preferences",
36-
type_="foreignkey",
37-
)
38-
op.drop_constraint(
3949
"user_search_space_preferences_strategic_llm_id_fkey",
40-
"user_search_space_preferences",
41-
type_="foreignkey",
42-
)
50+
]
51+
52+
for constraint_name in constraints_to_drop:
53+
if constraint_exists(
54+
connection, "user_search_space_preferences", constraint_name
55+
):
56+
op.drop_constraint(
57+
constraint_name,
58+
"user_search_space_preferences",
59+
type_="foreignkey",
60+
)
61+
else:
62+
print(f"Constraint '{constraint_name}' does not exist. Skipping.")
4363

4464

4565
def downgrade() -> None:
4666
"""
4767
Re-add foreign key constraints (will fail if any negative IDs exist in the table).
4868
"""
49-
# Re-add the foreign key constraints
50-
op.create_foreign_key(
51-
"user_search_space_preferences_long_context_llm_id_fkey",
52-
"user_search_space_preferences",
53-
"llm_configs",
54-
["long_context_llm_id"],
55-
["id"],
56-
ondelete="SET NULL",
57-
)
58-
op.create_foreign_key(
59-
"user_search_space_preferences_fast_llm_id_fkey",
60-
"user_search_space_preferences",
61-
"llm_configs",
62-
["fast_llm_id"],
63-
["id"],
64-
ondelete="SET NULL",
65-
)
66-
op.create_foreign_key(
67-
"user_search_space_preferences_strategic_llm_id_fkey",
68-
"user_search_space_preferences",
69-
"llm_configs",
70-
["strategic_llm_id"],
71-
["id"],
72-
ondelete="SET NULL",
73-
)
69+
connection = op.get_bind()
70+
71+
# Re-add the foreign key constraints if they don't exist
72+
constraints_to_create = [
73+
(
74+
"user_search_space_preferences_long_context_llm_id_fkey",
75+
"long_context_llm_id",
76+
),
77+
("user_search_space_preferences_fast_llm_id_fkey", "fast_llm_id"),
78+
("user_search_space_preferences_strategic_llm_id_fkey", "strategic_llm_id"),
79+
]
80+
81+
for constraint_name, column_name in constraints_to_create:
82+
if not constraint_exists(
83+
connection, "user_search_space_preferences", constraint_name
84+
):
85+
op.create_foreign_key(
86+
constraint_name,
87+
"user_search_space_preferences",
88+
"llm_configs",
89+
[column_name],
90+
["id"],
91+
ondelete="SET NULL",
92+
)
93+
else:
94+
print(f"Constraint '{constraint_name}' already exists. Skipping.")

surfsense_backend/alembic/versions/37_add_system_prompts_to_searchspaces.py

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from collections.abc import Sequence
1010

1111
import sqlalchemy as sa
12+
from sqlalchemy import text
1213

1314
from alembic import op
1415

@@ -19,24 +20,55 @@
1920
depends_on: str | Sequence[str] | None = None
2021

2122

23+
def column_exists(connection, table_name: str, column_name: str) -> bool:
24+
"""Check if a column exists on the given table."""
25+
result = connection.execute(
26+
text(
27+
"""
28+
SELECT 1 FROM information_schema.columns
29+
WHERE table_name = :table_name AND column_name = :column_name
30+
"""
31+
),
32+
{"table_name": table_name, "column_name": column_name},
33+
)
34+
return result.fetchone() is not None
35+
36+
2237
def upgrade() -> None:
2338
"""Add QnA configuration columns to searchspaces table."""
39+
connection = op.get_bind()
40+
2441
# Add citations_enabled boolean (default True)
25-
op.add_column(
26-
"searchspaces",
27-
sa.Column(
28-
"citations_enabled", sa.Boolean(), nullable=False, server_default="true"
29-
),
30-
)
42+
if not column_exists(connection, "searchspaces", "citations_enabled"):
43+
op.add_column(
44+
"searchspaces",
45+
sa.Column(
46+
"citations_enabled", sa.Boolean(), nullable=False, server_default="true"
47+
),
48+
)
49+
else:
50+
print("Column 'citations_enabled' already exists. Skipping.")
3151

3252
# Add custom instructions text field (nullable, defaults to empty)
33-
op.add_column(
34-
"searchspaces",
35-
sa.Column("qna_custom_instructions", sa.Text(), nullable=True),
36-
)
53+
if not column_exists(connection, "searchspaces", "qna_custom_instructions"):
54+
op.add_column(
55+
"searchspaces",
56+
sa.Column("qna_custom_instructions", sa.Text(), nullable=True),
57+
)
58+
else:
59+
print("Column 'qna_custom_instructions' already exists. Skipping.")
3760

3861

3962
def downgrade() -> None:
4063
"""Remove QnA configuration columns from searchspaces table."""
41-
op.drop_column("searchspaces", "qna_custom_instructions")
42-
op.drop_column("searchspaces", "citations_enabled")
64+
connection = op.get_bind()
65+
66+
if column_exists(connection, "searchspaces", "qna_custom_instructions"):
67+
op.drop_column("searchspaces", "qna_custom_instructions")
68+
else:
69+
print("Column 'qna_custom_instructions' does not exist. Skipping.")
70+
71+
if column_exists(connection, "searchspaces", "citations_enabled"):
72+
op.drop_column("searchspaces", "citations_enabled")
73+
else:
74+
print("Column 'citations_enabled' does not exist. Skipping.")
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
"""Add Webcrawler connector enums
2+
3+
Revision ID: 38
4+
Revises: 37
5+
Create Date: 2025-11-17 17:00:00.000000
6+
7+
"""
8+
9+
from collections.abc import Sequence
10+
11+
from alembic import op
12+
13+
revision: str = "38"
14+
down_revision: str | None = "37"
15+
branch_labels: str | Sequence[str] | None = None
16+
depends_on: str | Sequence[str] | None = None
17+
18+
19+
def upgrade() -> None:
20+
"""Safely add 'WEBCRAWLER_CONNECTOR' to enum types if missing."""
21+
22+
# Add to searchsourceconnectortype enum
23+
op.execute(
24+
"""
25+
DO $$
26+
BEGIN
27+
IF NOT EXISTS (
28+
SELECT 1 FROM pg_type t
29+
JOIN pg_enum e ON t.oid = e.enumtypid
30+
WHERE t.typname = 'searchsourceconnectortype' AND e.enumlabel = 'WEBCRAWLER_CONNECTOR'
31+
) THEN
32+
ALTER TYPE searchsourceconnectortype ADD VALUE 'WEBCRAWLER_CONNECTOR';
33+
END IF;
34+
END
35+
$$;
36+
"""
37+
)
38+
39+
# Add to documenttype enum
40+
op.execute(
41+
"""
42+
DO $$
43+
BEGIN
44+
IF NOT EXISTS (
45+
SELECT 1 FROM pg_type t
46+
JOIN pg_enum e ON t.oid = e.enumtypid
47+
WHERE t.typname = 'documenttype' AND e.enumlabel = 'CRAWLED_URL'
48+
) THEN
49+
ALTER TYPE documenttype ADD VALUE 'CRAWLED_URL';
50+
END IF;
51+
END
52+
$$;
53+
"""
54+
)
55+
56+
57+
def downgrade() -> None:
58+
"""Remove 'WEBCRAWLER_CONNECTOR' from enum types."""
59+
pass

0 commit comments

Comments
 (0)