Skip to content
Merged

Dev #269

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
117 commits
Select commit Hold shift + click to select a range
ca2aaf0
fix: ot terminal
tjbck Mar 3, 2026
a36692b
Merge pull request #22231 from ShirasawaSama/patch-10
ShirasawaSama Mar 4, 2026
5af24b3
fix: Implement archive chat handler in Chat page navbar (#22229)
ShirasawaSama Mar 4, 2026
7d45459
fix: keep save button spinner inline (#22227)
ShirasawaSama Mar 4, 2026
ad27535
i18n(fr-FR): complete French translation pass (#22200)
EliotGodard Mar 4, 2026
6e43861
feat: prioritize in-group members in sorting (#22211)
ShirasawaSama Mar 4, 2026
890949a
feat: add DOCX/XLSX/PPTX file preview
tjbck Mar 4, 2026
e08341d
enh: ot ports
tjbck Mar 4, 2026
f962bae
feat: improve XLSX preview + add code syntax highlighting
tjbck Mar 4, 2026
627b063
refac
tjbck Mar 4, 2026
c40f269
feat: add Shiki syntax highlighting, video, and audio previews in Fil…
tjbck Mar 4, 2026
f4c38e6
feat: add JSON collapsible tree view, SVG rendered preview, and sourc…
tjbck Mar 4, 2026
b081e33
feat: add Jupyter Notebook (.ipynb) preview in FileNav
tjbck Mar 4, 2026
4403c7b
feat: Timeout for event_call events (#22222)
Classic298 Mar 4, 2026
49a2e5b
feat: show refresh button when viewing files, not just directories
tjbck Mar 4, 2026
7ef181b
refac
tjbck Mar 4, 2026
a6fb5a0
refac
tjbck Mar 4, 2026
114f709
refac
tjbck Mar 4, 2026
a181b4a
feat: add SQLite database browser in FileNav
tjbck Mar 5, 2026
f5ea1ce
feat: add copy-to-clipboard button next to download in file toolbar
tjbck Mar 5, 2026
3b97c8d
refac
tjbck Mar 5, 2026
828656b
feat: auto-refresh FileNav on write_file, replace_file_content, and r…
tjbck Mar 5, 2026
8da02c6
refac
tjbck Mar 5, 2026
aaa49bd
refac
tjbck Mar 5, 2026
8cd2157
Perf: precompile katex unicode regex (#22196)
Classic298 Mar 5, 2026
4b3ed3e
feat: notebook per-cell execution via open-terminal REST endpoints
tjbck Mar 5, 2026
cd2c315
refac
tjbck Mar 5, 2026
cc6b51e
Update fi-FI translation.json (#22328)
Kylapaallikko Mar 6, 2026
fe58ef6
perf(frontend): lazy-load shiki to remove ~5-10MB from initial bundle…
Classic298 Mar 6, 2026
b94e1c9
fix: Fix memory leaking in Artifacts (#22303)
ShirasawaSama Mar 6, 2026
0820abb
refac
tjbck Mar 6, 2026
fa1ebfa
fix: use same metric description as OTel (#22192) (#22293)
erhhung Mar 6, 2026
339ed1d
refac
tjbck Mar 6, 2026
1850a98
perf: replace O(n²) unshift with O(n) push+reverse in buildMessages (…
Classic298 Mar 6, 2026
04fae8b
fix: use NullPool for SQLCipher engine to prevent segfault (#22273)
Classic298 Mar 6, 2026
47b007e
refac
tjbck Mar 6, 2026
a25ecfa
perf: skip token parsing when raw content is unchanged (#22183)
Algorithm5838 Mar 6, 2026
c85afce
fix: import
tjbck Mar 6, 2026
def9541
refac
tjbck Mar 6, 2026
1c1c1c3
fix: allow clearing file upload settings (#22336)
Algorithm5838 Mar 6, 2026
9cf6108
feat: add otel system metrics instrumentation (#22265)
ashm-dev Mar 6, 2026
8a6af40
fix: correct conflicting output format instruction in follow-up gener…
MJ16MJ Mar 6, 2026
16701be
fix: show floating action buttons when chat model is unavailable (#22…
Classic298 Mar 6, 2026
ce54b1d
perf: guard TTS sentence parsing behind showCallOverlay check (#22195)
Classic298 Mar 6, 2026
c73efab
feat: load banners on navigation to homepage, not only on refresh (#2…
Classic298 Mar 6, 2026
a70c718
fix: TTS reading thinking content when reasoning has code blocks (#22…
Classic298 Mar 6, 2026
2153c8e
refac
tjbck Mar 6, 2026
39deadc
perf: convert APIKeyRestrictionMiddleware to pure ASGI (#22188)
Algorithm5838 Mar 6, 2026
305e591
feat: use CodeMirror for always-editable code file preview
tjbck Mar 6, 2026
80376a3
revert
tjbck Mar 6, 2026
73b69ae
refac
tjbck Mar 6, 2026
0169287
refac
tjbck Mar 6, 2026
af4500e
refac
tjbck Mar 6, 2026
576ee92
perf: rewrite createMessagesList from recursive to iterative (#22194)
Classic298 Mar 6, 2026
4ab831b
refac
tjbck Mar 6, 2026
200fb09
fix: Use toBlob on first mobile export to avoid black canvas image on…
ShirasawaSama Mar 6, 2026
d93cb36
perf(models): batch-fetch function valves to eliminate N+1 queries (#…
Classic298 Mar 6, 2026
d8bb8c5
refac
tjbck Mar 6, 2026
b362247
refac
tjbck Mar 6, 2026
7806cd5
feat: use CodeMirror editor for HTML source view, hide save in previe…
tjbck Mar 7, 2026
6d9996e
refac
tjbck Mar 7, 2026
b9c0a9c
enh: prevent models from always using internal knowledge base search …
Classic298 Mar 7, 2026
fbf315e
i18n: expand Turkish translations across missing frontend UI strings …
alifurkanstahl Mar 7, 2026
2d0b947
Update translation.json (#22353)
Classic298 Mar 7, 2026
c7d1d1e
refac
tjbck Mar 7, 2026
70a31a9
fix: terminals button ui
tjbck Mar 7, 2026
044fd1b
refac
tjbck Mar 7, 2026
51a2d2b
i18n: improve Chinese translation (#22351)
ShirasawaSama Mar 7, 2026
5eb9b58
feat: Avoid overview profile image squashed (#22261)
ShirasawaSama Mar 7, 2026
7820a31
fix: prevent message queue from overflowing screen (#22176)
ShirasawaSama Mar 7, 2026
2916074
refac
tjbck Mar 7, 2026
03c6caa
refac
tjbck Mar 7, 2026
e6b00a8
refac
tjbck Mar 7, 2026
626fcff
refac
tjbck Mar 7, 2026
42ecdb5
refac
tjbck Mar 7, 2026
2108f42
chore: dep bump (#22305)
Classic298 Mar 7, 2026
d4faa5a
refac
tjbck Mar 7, 2026
dfa2511
fix: persist token usage data for non-streaming chat responses (#22166)
Classic298 Mar 7, 2026
b04de83
refac
tjbck Mar 7, 2026
7cdff6b
refac
tjbck Mar 7, 2026
bc5d519
refac
tjbck Mar 7, 2026
e303c3d
refac: inline codespan rich text input
tjbck Mar 7, 2026
7b2f597
refac
tjbck Mar 7, 2026
b4f3408
fix: migration streaming/batching (#21542)
Classic298 Mar 8, 2026
5d4505c
fix: add support for scope in OAuth refresh token request (#22359)
pedro-inf-custodio Mar 8, 2026
35bc831
refac
tjbck Mar 8, 2026
95b65ff
refac
tjbck Mar 8, 2026
2e1ef80
fix: banner type dropdown requires two selections to register (#22378)
Classic298 Mar 8, 2026
885c94b
refac
tjbck Mar 8, 2026
d7efdcc
refac
tjbck Mar 8, 2026
9a269ec
fix: use path converter for model ID routes in analytics to support s…
Classic298 Mar 8, 2026
459a60a
refac
tjbck Mar 8, 2026
d1975b7
fix: add deterministic ordering to chat_ids pagination query to preve…
Classic298 Mar 8, 2026
ce0ca89
enh: code interpreter pyodide fs
tjbck Mar 8, 2026
8cd3bd7
refac
tjbck Mar 8, 2026
967b113
refac
tjbck Mar 8, 2026
80b5896
refac
tjbck Mar 8, 2026
8913f37
enh: create subfolder
tjbck Mar 8, 2026
9899388
refac
tjbck Mar 8, 2026
144d8b1
refac
tjbck Mar 8, 2026
3ceaa10
chore: format
tjbck Mar 8, 2026
d0c3180
changelog: 0.8.9 (#22186)
Classic298 Mar 8, 2026
223c14f
fix: add deterministic tiebreaker to all paginated chat queries (#22387)
Classic298 Mar 8, 2026
bd456ed
doc: changelog
tjbck Mar 8, 2026
0948235
refac
tjbck Mar 8, 2026
9bb226d
refac
tjbck Mar 8, 2026
b1048fc
refac
tjbck Mar 8, 2026
368912c
refac
tjbck Mar 8, 2026
f047b6b
refac
tjbck Mar 8, 2026
e24299e
refac
tjbck Mar 8, 2026
2bff50f
refac
tjbck Mar 8, 2026
710b527
refac
tjbck Mar 8, 2026
947dcd3
Merge pull request #22385 from open-webui/dev
tjbck Mar 8, 2026
124ad94
refac
tjbck Mar 8, 2026
6c159a9
Merge pull request #22390 from open-webui/dev
tjbck Mar 8, 2026
89e7e07
Merge remote-tracking branch 'openwebui/main' into dev
OrenZhang Mar 8, 2026
0c30518
chore(repo): merge from remote
OrenZhang Mar 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions CHANGELOG.md

Large diffs are not rendered by default.

6 changes: 6 additions & 0 deletions CHANGELOG_EXTRA.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.8.9.1] - 2026.03.08

### Changed

- 合并官方 0.8.9 改动

## [0.8.8.1] - 2026.03.03

### Changed
Expand Down
53 changes: 38 additions & 15 deletions backend/open_webui/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,13 @@ def __getattr__(self, key):
os.environ.get("ENABLE_OAUTH_SIGNUP", "False").lower() == "true",
)

OAUTH_REFRESH_TOKEN_INCLUDE_SCOPE = PersistentConfig(
"OAUTH_REFRESH_TOKEN_INCLUDE_SCOPE",
"oauth.refresh_token_include_scope",
os.environ.get("OAUTH_REFRESH_TOKEN_INCLUDE_SCOPE", "False").lower() == "true",
)


OAUTH_MERGE_ACCOUNTS_BY_EMAIL = PersistentConfig(
"OAUTH_MERGE_ACCOUNTS_BY_EMAIL",
"oauth.merge_accounts_by_email",
Expand Down Expand Up @@ -1921,7 +1928,7 @@ class BannerModel(BaseModel):
- Only suggest follow-ups that make sense given the chat content and do not repeat what was already covered.
- If the conversation is very short or not specific, suggest more general (but relevant) follow-ups the user might ask.
- Use the conversation's primary language; default to English if multilingual.
- Response must be a JSON array of strings, no extra text or formatting.
- Response must be a JSON object with a "follow_ups" key containing an array of strings, no extra text or formatting.
### Output:
JSON format: { "follow_ups": ["Question 1?", "Question 2?", "Question 3?"] }
### Chat History:
Expand Down Expand Up @@ -2242,20 +2249,36 @@ class BannerModel(BaseModel):
]

DEFAULT_CODE_INTERPRETER_PROMPT = """
#### Tools Available

1. **Code Interpreter**: `<code_interpreter type="code" lang="python"></code_interpreter>`
- You have access to a Python shell that runs directly in the user's browser, enabling fast execution of code for analysis, calculations, or problem-solving. Use it in this response.
- The Python code you write can incorporate a wide array of libraries, handle data manipulation or visualization, perform API calls for web-related tasks, or tackle virtually any computational challenge. Use this flexibility to **think outside the box, craft elegant solutions, and harness Python's full potential**.
- To use it, **you must enclose your code within `<code_interpreter type="code" lang="python">` XML tags** and stop right away. If you don't, the code won't execute.
- When writing code in the code_interpreter XML tag, Do NOT use the triple backticks code block for markdown formatting, example: ```py # python code ``` will cause an error because it is markdown formatting, it is not python code.
- When coding, **always aim to print meaningful outputs** (e.g., results, tables, summaries, or visuals) to better interpret and verify the findings. Avoid relying on implicit outputs; prioritize explicit and clear print statements so the results are effectively communicated to the user.
- After obtaining the printed output, **always provide a concise analysis, interpretation, or next steps to help the user understand the findings or refine the outcome further.**
- If the results are unclear, unexpected, or require validation, refine the code and execute it again as needed. Always aim to deliver meaningful insights from the results, iterating if necessary.
- **If a link to an image, audio, or any file is provided in markdown format in the output, ALWAYS regurgitate word for word, explicitly display it as part of the response to ensure the user can access it easily, do NOT change the link.**
- All responses should be communicated in the chat's primary language, ensuring seamless understanding. If the chat is multilingual, default to English for clarity.

Ensure that the tools are effectively utilized to achieve the highest-quality analysis for the user."""
#### Code Interpreter

You have access to a Python code interpreter via: `<code_interpreter type="code" lang="python"></code_interpreter>`

- The Python shell runs directly in the user's browser for fast execution of analysis, calculations, or problem-solving. Use it in this response.
- You can use a wide array of libraries for data manipulation, visualization, API calls, or any computational task. Think outside the box and harness Python's full potential.
- **You must enclose your code within `<code_interpreter type="code" lang="python">` XML tags** and stop right away. If you don't, the code won't execute.
- Do NOT use triple backticks (```py ... ```) inside the XML tags — that is markdown formatting, not executable Python code.
- **Always print meaningful outputs** (results, tables, summaries, visuals). Avoid implicit outputs; use explicit print statements.
- After obtaining output, **provide a concise analysis, interpretation, or next steps** to help the user understand the findings.
- If results are unclear or unexpected, refine the code and re-execute. Iterate until you deliver meaningful insights.
- **If a link to an image, audio, or any file appears in the output, display it exactly as-is** in your response so the user can access it. Do not modify the link.
- Respond in the chat's primary language. Default to English if multilingual.

Ensure the code interpreter is effectively utilized to achieve the highest-quality analysis for the user."""

# Appended to the code interpreter prompt only when engine is pyodide (not jupyter)
CODE_INTERPRETER_PYODIDE_PROMPT = """

##### Pyodide Environment

- This Python environment runs via Pyodide in the browser. **Do not install packages** — `pip install`, `subprocess`, and `micropip.install()` are not available.
- If a required library is unavailable, use an alternative approach with available modules. Do not attempt to install anything.

##### Persistent File System

- User-uploaded files are available at `/mnt/uploads/`. When the user asks you to work with their files, read from this directory.
- You can also write output files to `/mnt/uploads/` so the user can access and download them from the file browser.
- The file system persists across code executions within the same session.
- Use `import os; os.listdir('/mnt/uploads')` to discover available files."""

####################################
# Vector Database
Expand Down
10 changes: 10 additions & 0 deletions backend/open_webui/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,16 @@ def parse_section(section):
except ValueError:
WEBSOCKET_SERVER_PING_INTERVAL = 25

WEBSOCKET_EVENT_CALLER_TIMEOUT = os.environ.get("WEBSOCKET_EVENT_CALLER_TIMEOUT", "")

if WEBSOCKET_EVENT_CALLER_TIMEOUT == "":
WEBSOCKET_EVENT_CALLER_TIMEOUT = None
else:
try:
WEBSOCKET_EVENT_CALLER_TIMEOUT = int(WEBSOCKET_EVENT_CALLER_TIMEOUT)
except ValueError:
WEBSOCKET_EVENT_CALLER_TIMEOUT = 300


REQUESTS_VERIFY = os.environ.get("REQUESTS_VERIFY", "True").lower() == "true"

Expand Down
29 changes: 24 additions & 5 deletions backend/open_webui/internal/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,11 +102,30 @@ def create_sqlcipher_connection():
conn.execute(f"PRAGMA key = '{database_password}'")
return conn

engine = create_engine(
"sqlite://", # Dummy URL since we're using creator
creator=create_sqlcipher_connection,
echo=False,
)
# The dummy "sqlite://" URL would cause SQLAlchemy to auto-select
# SingletonThreadPool, which non-deterministically closes in-use
# connections when thread count exceeds pool_size, leading to segfaults
# in the native sqlcipher3 C library. Use NullPool by default for safety,
# or QueuePool if DATABASE_POOL_SIZE is explicitly configured.
if isinstance(DATABASE_POOL_SIZE, int) and DATABASE_POOL_SIZE > 0:
engine = create_engine(
"sqlite://",
creator=create_sqlcipher_connection,
pool_size=DATABASE_POOL_SIZE,
max_overflow=DATABASE_POOL_MAX_OVERFLOW,
pool_timeout=DATABASE_POOL_TIMEOUT,
pool_recycle=DATABASE_POOL_RECYCLE,
pool_pre_ping=True,
poolclass=QueuePool,
echo=False,
)
else:
engine = create_engine(
"sqlite://",
creator=create_sqlcipher_connection,
poolclass=NullPool,
echo=False,
)

log.info("Connected to encrypted SQLite database using SQLCipher")

Expand Down
83 changes: 45 additions & 38 deletions backend/open_webui/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -1457,46 +1457,52 @@ async def dispatch(self, request: Request, call_next):
app.add_middleware(SecurityHeadersMiddleware)


class APIKeyRestrictionMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
auth_header = request.headers.get("Authorization")
token = None

if auth_header:
parts = auth_header.split(" ", 1)
if len(parts) == 2:
token = parts[1]

# Only apply restrictions if an sk- API key is used
if token and token.startswith("sk-"):
# Check if restrictions are enabled
if request.app.state.config.ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS:
allowed_paths = [
path.strip()
for path in str(
request.app.state.config.API_KEYS_ALLOWED_ENDPOINTS
).split(",")
if path.strip()
]

request_path = request.url.path

# Match exact path or prefix path
is_allowed = any(
request_path == allowed or request_path.startswith(allowed + "/")
for allowed in allowed_paths
)

if not is_allowed:
return JSONResponse(
status_code=status.HTTP_403_FORBIDDEN,
content={
"detail": "API key not allowed to access this endpoint."
},
class APIKeyRestrictionMiddleware:
def __init__(self, app):
self.app = app

async def __call__(self, scope, receive, send):
if scope["type"] == "http":
request = Request(scope)
auth_header = request.headers.get("Authorization")
token = None

if auth_header:
parts = auth_header.split(" ", 1)
if len(parts) == 2:
token = parts[1]

# Only apply restrictions if an sk- API key is used
if token and token.startswith("sk-"):
# Check if restrictions are enabled
if app.state.config.ENABLE_API_KEYS_ENDPOINT_RESTRICTIONS:
allowed_paths = [
path.strip()
for path in str(
app.state.config.API_KEYS_ALLOWED_ENDPOINTS
).split(",")
if path.strip()
]

request_path = request.url.path

# Match exact path or prefix path
is_allowed = any(
request_path == allowed
or request_path.startswith(allowed + "/")
for allowed in allowed_paths
)

response = await call_next(request)
return response
if not is_allowed:
await JSONResponse(
status_code=status.HTTP_403_FORBIDDEN,
content={
"detail": "API key not allowed to access this endpoint."
},
)(scope, receive, send)
return

await self.app(scope, receive, send)


app.add_middleware(APIKeyRestrictionMiddleware)
Expand Down Expand Up @@ -2269,6 +2275,7 @@ async def get_app_config(request: Request):
"user_count": user_count,
"code": {
"engine": app.state.config.CODE_EXECUTION_ENGINE,
"interpreter_engine": app.state.config.CODE_INTERPRETER_ENGINE,
},
"audio": {
"tts": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,39 @@
branch_labels: Union[str, Sequence[str], None] = None
depends_on: Union[str, Sequence[str], None] = None

BATCH_SIZE = 5000


def _flush_batch(conn, table, batch):
"""
Insert a batch of messages, falling back to row-by-row on error.

Tries a single bulk insert first (fast path). If that fails (e.g. due to
a duplicate key), falls back to individual inserts wrapped in savepoints
so the rest of the batch can still succeed.
"""
savepoint = conn.begin_nested()
try:
conn.execute(sa.insert(table), batch)
savepoint.commit()
return len(batch), 0
except Exception:
savepoint.rollback()
# Batch failed - insert one-by-one to isolate the bad row(s)
inserted = 0
failed = 0
for msg in batch:
sp = conn.begin_nested()
try:
conn.execute(sa.insert(table).values(**msg))
sp.commit()
inserted += 1
except Exception as e:
sp.rollback()
failed += 1
log.warning(f"Failed to insert message {msg['id']}: {e}")
return inserted, failed


def upgrade() -> None:
# Step 1: Create table
Expand Down Expand Up @@ -88,18 +121,21 @@ def upgrade() -> None:
sa.column("updated_at", sa.BigInteger()),
)

# Fetch all chats (excluding shared chats which have user_id starting with 'shared-')
chats = conn.execute(
sa.select(chat_table.c.id, chat_table.c.user_id, chat_table.c.chat).where(
~chat_table.c.user_id.like("shared-%")
)
).fetchall()
# Stream rows instead of loading all into memory:
# - yield_per: fetches rows in chunks via cursor.fetchmany() (all backends)
# - stream_results: enables server-side cursors on PostgreSQL (no-op on SQLite)
result = conn.execute(
sa.select(chat_table.c.id, chat_table.c.user_id, chat_table.c.chat)
.where(~chat_table.c.user_id.like("shared-%"))
.execution_options(yield_per=1000, stream_results=True)
)

now = int(time.time())
messages_inserted = 0
messages_failed = 0
messages_batch = []
total_inserted = 0
total_failed = 0

for chat_row in chats:
for chat_row in result:
chat_id = chat_row[0]
user_id = chat_row[1]
chat_data = chat_row[2]
Expand Down Expand Up @@ -139,39 +175,49 @@ def upgrade() -> None:
if timestamp < 1577836800 or timestamp > now + 86400:
timestamp = now

# Use savepoint to allow individual insert failures without aborting transaction
savepoint = conn.begin_nested()
try:
conn.execute(
sa.insert(chat_message_table).values(
id=f"{chat_id}-{message_id}",
chat_id=chat_id,
user_id=user_id,
role=role,
parent_id=message.get("parentId"),
content=message.get("content"),
output=message.get("output"),
model_id=message.get("model"),
files=message.get("files"),
sources=message.get("sources"),
embeds=message.get("embeds"),
done=message.get("done", True),
status_history=message.get("statusHistory"),
error=message.get("error"),
created_at=timestamp,
updated_at=timestamp,
)
messages_batch.append(
{
"id": f"{chat_id}-{message_id}",
"chat_id": chat_id,
"user_id": user_id,
"role": role,
"parent_id": message.get("parentId"),
"content": message.get("content"),
"output": message.get("output"),
"model_id": message.get("model"),
"files": message.get("files"),
"sources": message.get("sources"),
"embeds": message.get("embeds"),
"done": message.get("done", True),
"status_history": message.get("statusHistory"),
"error": message.get("error"),
"usage": message.get("usage"),
"created_at": timestamp,
"updated_at": timestamp,
}
)

# Flush batch when full
if len(messages_batch) >= BATCH_SIZE:
inserted, failed = _flush_batch(
conn, chat_message_table, messages_batch
)
savepoint.commit()
messages_inserted += 1
except Exception as e:
savepoint.rollback()
messages_failed += 1
log.warning(f"Failed to insert message {message_id}: {e}")
continue
total_inserted += inserted
total_failed += failed
if total_inserted % 50000 < BATCH_SIZE:
log.info(
f"Migration progress: {total_inserted} messages inserted..."
)
messages_batch.clear()

# Flush remaining messages
if messages_batch:
inserted, failed = _flush_batch(conn, chat_message_table, messages_batch)
total_inserted += inserted
total_failed += failed

log.info(
f"Backfilled {messages_inserted} messages into chat_message table ({messages_failed} failed)"
f"Backfilled {total_inserted} messages into chat_message table ({total_failed} failed)"
)


Expand Down
Loading
Loading