From bc22a4e104b3f678623e43db2df47decaf19bb86 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Wed, 1 Jul 2026 00:20:34 -0500 Subject: [PATCH 01/15] refac --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index db9f35b..60904e7 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ ![GitHub top language](https://img.shields.io/github/languages/top/open-webui/computer) ![GitHub last commit](https://img.shields.io/github/last-commit/open-webui/computer?color=red) [![Discord](https://img.shields.io/badge/Discord-Open_WebUI-blue?logo=discord&logoColor=white)](https://discord.gg/5rJgQTnV4s) -[![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/tjbck) +[![](https://img.shields.io/static/v1?label=Sponsor&message=%E2%9D%A4&logo=GitHub&color=%23fe8e86)](https://github.com/sponsors/open-webui) ![Open WebUI Computer Demo](./demo.png) From 0c01beeaa20cc2feff93a6e1a5869f181a0a3679 Mon Sep 17 00:00:00 2001 From: Tim Baek Date: Wed, 1 Jul 2026 00:53:23 -0500 Subject: [PATCH 02/15] refac Updated GitHub Sponsors username in FUNDING.yml. --- .github/FUNDING.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..0c0599d --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: open-webui From c487081e7c46c210bd75323e6ce8044ade3a8e70 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Thu, 2 Jul 2026 13:02:11 -0500 Subject: [PATCH 03/15] refac --- cptr/routers/terminal.py | 12 ++++++------ cptr/utils/terminal.py | 3 +++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/cptr/routers/terminal.py b/cptr/routers/terminal.py index adfd4b8..3a70179 100644 --- a/cptr/routers/terminal.py +++ b/cptr/routers/terminal.py @@ -4,13 +4,12 @@ import asyncio import logging -import os from typing import List, Optional from fastapi import APIRouter, HTTPException, WebSocket, WebSocketDisconnect from pydantic import BaseModel -from cptr.utils.config import check_access, get_auth_mode, get_user_uid_gid, AuthMode +from cptr.utils.config import check_access from cptr.utils.terminal import manager, IS_WINDOWS from cptr.utils.tools import ( command_session_bytes_since, @@ -247,17 +246,18 @@ async def read_pty(): try: if IS_WINDOWS: # Windows ProactorEventLoop doesn't support add_reader; - # fall back to minimal-sleep polling. + # pywinpty read() blocks, so run it outside the event loop. while True: if not session.is_alive(): logger.info(f"Session {session_id} died, stopping read") break try: - data = session.read(16384) + data = await asyncio.to_thread(session.read, 16384) if data: await websocket.send_bytes(data) - else: - await asyncio.sleep(0.005) + except EOFError: + logger.info(f"Session {session_id} reached EOF") + break except (OSError, IOError) as e: logger.error(f"PTY read error for {session_id}: {e}") break diff --git a/cptr/utils/terminal.py b/cptr/utils/terminal.py index 623d7c8..2c14bfd 100644 --- a/cptr/utils/terminal.py +++ b/cptr/utils/terminal.py @@ -35,6 +35,7 @@ class TerminalSession: def write(self, data: bytes) -> None: if IS_WINDOWS: + data = data.decode("utf-8", errors="replace") self._process.write(data) # type: ignore else: os.write(self._fd, data) @@ -43,6 +44,8 @@ def read(self, size: int = 4096) -> bytes: try: if IS_WINDOWS: data = self._process.read(size) # type: ignore + if isinstance(data, str): + data = data.encode("utf-8", errors="replace") else: data = os.read(self._fd, size) except (BlockingIOError, OSError): From cfec1eb931baaafd44cf0eec167358de8cb46966 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Thu, 2 Jul 2026 13:06:58 -0500 Subject: [PATCH 04/15] refac --- cptr/routers/terminal.py | 7 +++++-- cptr/utils/terminal.py | 13 ++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/cptr/routers/terminal.py b/cptr/routers/terminal.py index 3a70179..dc3c8b1 100644 --- a/cptr/routers/terminal.py +++ b/cptr/routers/terminal.py @@ -10,7 +10,7 @@ from pydantic import BaseModel from cptr.utils.config import check_access -from cptr.utils.terminal import manager, IS_WINDOWS +from cptr.utils.terminal import TerminalUnavailable, manager, IS_WINDOWS from cptr.utils.tools import ( command_session_bytes_since, drain_command_session_input, @@ -57,7 +57,10 @@ class CommandSessionInfo(BaseModel): async def create_session(req: CreateSessionRequest): """Create a new terminal session.""" logger.info(f"Creating terminal session: cwd={req.cwd}, rows={req.rows}, cols={req.cols}") - session = manager.create(rows=req.rows, cols=req.cols, cwd=req.cwd) + try: + session = manager.create(rows=req.rows, cols=req.cols, cwd=req.cwd) + except TerminalUnavailable as exc: + raise HTTPException(status_code=503, detail=str(exc)) from exc logger.info( f"Created session {session.session_id} at {session.cwd}, fd={session._fd}, alive={session.is_alive()}" ) diff --git a/cptr/utils/terminal.py b/cptr/utils/terminal.py index 2c14bfd..a36a97f 100644 --- a/cptr/utils/terminal.py +++ b/cptr/utils/terminal.py @@ -18,6 +18,10 @@ IS_WINDOWS = platform.system() == "Windows" +class TerminalUnavailable(RuntimeError): + """Raised when the platform terminal backend cannot be loaded.""" + + @dataclass class TerminalSession: """Platform-agnostic terminal session with scrollback buffer.""" @@ -194,7 +198,14 @@ def _create_windows( session_id: str, shell: str, work_dir: str, env: dict, rows: int, cols: int ) -> TerminalSession: """Create a terminal session on Windows using pywinpty.""" - from winpty import PtyProcess # type: ignore + try: + from winpty import PtyProcess # type: ignore + except (ImportError, OSError) as exc: + raise TerminalUnavailable( + "Windows terminal support requires pywinpty and the Microsoft Visual C++ " + "Redistributable. Install the latest supported Visual C++ Redistributable " + "from Microsoft, then restart cptr." + ) from exc proc = PtyProcess.spawn( [shell], From 4df5195afb7d6e10209604d22236ed93bc0181cd Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Thu, 2 Jul 2026 13:11:39 -0500 Subject: [PATCH 05/15] refac --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 60904e7..11d27b0 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ The Docker image includes all optional feature groups. Or with [uv](https://docs.astral.sh/uv/): `uvx cptr@latest run` +On Windows, if opening a terminal reports a missing `VCRUNTIME140.dll` or Universal CRT DLL, install Microsoft's [Visual C++ Redistributable](https://learn.microsoft.com/en-us/cpp/windows/latest-supported-vc-redist) and restart `cptr`. + Opens in your browser at `http://localhost:8000`. ### Access from your phone From f47e20353a62e06ac05c1ae244a8e86bf5662563 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Thu, 2 Jul 2026 13:39:14 -0500 Subject: [PATCH 06/15] refac --- cptr/frontend/src/app.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/cptr/frontend/src/app.css b/cptr/frontend/src/app.css index 5927914..660f4d3 100644 --- a/cptr/frontend/src/app.css +++ b/cptr/frontend/src/app.css @@ -23,6 +23,10 @@ --color-gray-950: oklch(0.16 0 0); } +:root { + --app-text-scale: 1; +} + @layer base { *, ::after, @@ -50,6 +54,7 @@ html { color-scheme: dark light; touch-action: manipulation; + font-size: calc(1rem * var(--app-text-scale, 1)); } button { From cee39914bc4b238319773e4c9a1c08d6c97a32e6 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Thu, 2 Jul 2026 13:39:36 -0500 Subject: [PATCH 07/15] refac --- cptr/frontend/src/lib/i18n/locales/de.json | 6 +++++- cptr/frontend/src/lib/i18n/locales/en.json | 6 +++++- cptr/frontend/src/lib/i18n/locales/es.json | 6 +++++- cptr/frontend/src/lib/i18n/locales/fr.json | 6 +++++- cptr/frontend/src/lib/i18n/locales/ja.json | 6 +++++- cptr/frontend/src/lib/i18n/locales/ko.json | 6 +++++- cptr/frontend/src/lib/i18n/locales/pt-BR.json | 6 +++++- cptr/frontend/src/lib/i18n/locales/ru.json | 6 +++++- cptr/frontend/src/lib/i18n/locales/zh-CN.json | 6 +++++- cptr/frontend/src/lib/i18n/locales/zh-TW.json | 6 +++++- cptr/frontend/src/lib/stores.ts | 11 ++++++++++- cptr/frontend/src/lib/utils/text-scale.ts | 4 ++++ 12 files changed, 64 insertions(+), 11 deletions(-) create mode 100644 cptr/frontend/src/lib/utils/text-scale.ts diff --git a/cptr/frontend/src/lib/i18n/locales/de.json b/cptr/frontend/src/lib/i18n/locales/de.json index b77e386..bccfe6a 100644 --- a/cptr/frontend/src/lib/i18n/locales/de.json +++ b/cptr/frontend/src/lib/i18n/locales/de.json @@ -859,5 +859,9 @@ "general.persistentYes": "Aktiv", "general.persistentNo": "Nicht gewährt", "general.clearCache": "Cache leeren", - "general.cacheCleared": "Cache geleert" + "general.cacheCleared": "Cache geleert", + "general.uiScale": "UI Scale", + "general.default": "Default", + "general.decreaseUiScale": "Decrease UI Scale", + "general.increaseUiScale": "Increase UI Scale" } diff --git a/cptr/frontend/src/lib/i18n/locales/en.json b/cptr/frontend/src/lib/i18n/locales/en.json index 28b1382..d67b9d7 100644 --- a/cptr/frontend/src/lib/i18n/locales/en.json +++ b/cptr/frontend/src/lib/i18n/locales/en.json @@ -859,5 +859,9 @@ "general.persistentYes": "Active", "general.persistentNo": "Not granted", "general.clearCache": "Clear cache", - "general.cacheCleared": "Cache cleared" + "general.cacheCleared": "Cache cleared", + "general.uiScale": "UI Scale", + "general.default": "Default", + "general.decreaseUiScale": "Decrease UI Scale", + "general.increaseUiScale": "Increase UI Scale" } diff --git a/cptr/frontend/src/lib/i18n/locales/es.json b/cptr/frontend/src/lib/i18n/locales/es.json index 18a48e0..c41fd87 100644 --- a/cptr/frontend/src/lib/i18n/locales/es.json +++ b/cptr/frontend/src/lib/i18n/locales/es.json @@ -859,5 +859,9 @@ "general.persistentYes": "Activo", "general.persistentNo": "No concedido", "general.clearCache": "Borrar caché", - "general.cacheCleared": "Caché borrada" + "general.cacheCleared": "Caché borrada", + "general.uiScale": "UI Scale", + "general.default": "Default", + "general.decreaseUiScale": "Decrease UI Scale", + "general.increaseUiScale": "Increase UI Scale" } diff --git a/cptr/frontend/src/lib/i18n/locales/fr.json b/cptr/frontend/src/lib/i18n/locales/fr.json index bc8cb85..4c78e20 100644 --- a/cptr/frontend/src/lib/i18n/locales/fr.json +++ b/cptr/frontend/src/lib/i18n/locales/fr.json @@ -859,5 +859,9 @@ "general.persistentYes": "Actif", "general.persistentNo": "Non accordé", "general.clearCache": "Vider le cache", - "general.cacheCleared": "Cache vidé" + "general.cacheCleared": "Cache vidé", + "general.uiScale": "UI Scale", + "general.default": "Default", + "general.decreaseUiScale": "Decrease UI Scale", + "general.increaseUiScale": "Increase UI Scale" } diff --git a/cptr/frontend/src/lib/i18n/locales/ja.json b/cptr/frontend/src/lib/i18n/locales/ja.json index 6287db7..47d181b 100644 --- a/cptr/frontend/src/lib/i18n/locales/ja.json +++ b/cptr/frontend/src/lib/i18n/locales/ja.json @@ -859,5 +859,9 @@ "general.persistentYes": "有効", "general.persistentNo": "許可されていません", "general.clearCache": "キャッシュを消去", - "general.cacheCleared": "キャッシュを消去しました" + "general.cacheCleared": "キャッシュを消去しました", + "general.uiScale": "UI Scale", + "general.default": "Default", + "general.decreaseUiScale": "Decrease UI Scale", + "general.increaseUiScale": "Increase UI Scale" } diff --git a/cptr/frontend/src/lib/i18n/locales/ko.json b/cptr/frontend/src/lib/i18n/locales/ko.json index dbc49dd..e2fd22c 100644 --- a/cptr/frontend/src/lib/i18n/locales/ko.json +++ b/cptr/frontend/src/lib/i18n/locales/ko.json @@ -859,5 +859,9 @@ "general.persistentYes": "활성", "general.persistentNo": "허용되지 않음", "general.clearCache": "캐시 지우기", - "general.cacheCleared": "캐시가 지워졌습니다" + "general.cacheCleared": "캐시가 지워졌습니다", + "general.uiScale": "UI Scale", + "general.default": "Default", + "general.decreaseUiScale": "Decrease UI Scale", + "general.increaseUiScale": "Increase UI Scale" } diff --git a/cptr/frontend/src/lib/i18n/locales/pt-BR.json b/cptr/frontend/src/lib/i18n/locales/pt-BR.json index b387652..d8b117f 100644 --- a/cptr/frontend/src/lib/i18n/locales/pt-BR.json +++ b/cptr/frontend/src/lib/i18n/locales/pt-BR.json @@ -859,5 +859,9 @@ "general.persistentYes": "Ativo", "general.persistentNo": "Não concedido", "general.clearCache": "Limpar cache", - "general.cacheCleared": "Cache limpo" + "general.cacheCleared": "Cache limpo", + "general.uiScale": "UI Scale", + "general.default": "Default", + "general.decreaseUiScale": "Decrease UI Scale", + "general.increaseUiScale": "Increase UI Scale" } diff --git a/cptr/frontend/src/lib/i18n/locales/ru.json b/cptr/frontend/src/lib/i18n/locales/ru.json index 734900e..942eee5 100644 --- a/cptr/frontend/src/lib/i18n/locales/ru.json +++ b/cptr/frontend/src/lib/i18n/locales/ru.json @@ -859,5 +859,9 @@ "general.persistentYes": "Активно", "general.persistentNo": "Не разрешено", "general.clearCache": "Очистить кэш", - "general.cacheCleared": "Кэш очищен" + "general.cacheCleared": "Кэш очищен", + "general.uiScale": "UI Scale", + "general.default": "Default", + "general.decreaseUiScale": "Decrease UI Scale", + "general.increaseUiScale": "Increase UI Scale" } diff --git a/cptr/frontend/src/lib/i18n/locales/zh-CN.json b/cptr/frontend/src/lib/i18n/locales/zh-CN.json index b01cf83..888646b 100644 --- a/cptr/frontend/src/lib/i18n/locales/zh-CN.json +++ b/cptr/frontend/src/lib/i18n/locales/zh-CN.json @@ -859,5 +859,9 @@ "general.persistentYes": "已启用", "general.persistentNo": "未授予", "general.clearCache": "清除缓存", - "general.cacheCleared": "缓存已清除" + "general.cacheCleared": "缓存已清除", + "general.uiScale": "UI Scale", + "general.default": "Default", + "general.decreaseUiScale": "Decrease UI Scale", + "general.increaseUiScale": "Increase UI Scale" } diff --git a/cptr/frontend/src/lib/i18n/locales/zh-TW.json b/cptr/frontend/src/lib/i18n/locales/zh-TW.json index 0d80b28..2e149c2 100644 --- a/cptr/frontend/src/lib/i18n/locales/zh-TW.json +++ b/cptr/frontend/src/lib/i18n/locales/zh-TW.json @@ -859,5 +859,9 @@ "general.persistentYes": "啟用", "general.persistentNo": "未授予", "general.clearCache": "清除快取", - "general.cacheCleared": "快取已清除" + "general.cacheCleared": "快取已清除", + "general.uiScale": "UI Scale", + "general.default": "Default", + "general.decreaseUiScale": "Decrease UI Scale", + "general.increaseUiScale": "Increase UI Scale" } diff --git a/cptr/frontend/src/lib/stores.ts b/cptr/frontend/src/lib/stores.ts index 3b04104..797bfdd 100644 --- a/cptr/frontend/src/lib/stores.ts +++ b/cptr/frontend/src/lib/stores.ts @@ -30,6 +30,7 @@ import { streamingChatTabs } from '$lib/stores/chat'; import { keybindings, loadKeybindings } from '$lib/stores/keybindings'; import { defaultPwaPreferences, type PwaPreferences } from '$lib/intents/types'; import { getPathDisplayName, isSupportedWorkspacePath } from '$lib/utils/paths'; +import { setTextScale } from '$lib/utils/text-scale'; // ── Types ─────────────────────────────────────────────────────── @@ -82,6 +83,7 @@ export interface UserPreferences { requestParams?: Record; // arbitrary params merged into API request body showUpdateToast?: boolean; // show version update notifications (default true) pwa?: PwaPreferences; + textScale?: number | null; } export type Theme = 'dark' | 'light' | 'system'; @@ -172,6 +174,7 @@ export type StreamingBehavior = 'queue' | 'interrupt'; export const streamingBehavior = writable('queue'); export const selectedModelId = writable(''); export const pwaPreferences = writable(defaultPwaPreferences); +export const textScale = writable(null); /** Saved workspace path order for sidebar drag-reorder. */ export const workspaceOrder = writable([]); @@ -308,7 +311,8 @@ function persistPreferences(): void { selectedModelId: get(selectedModelId) || undefined, requestParams: Object.keys(get(requestParams)).length ? get(requestParams) : undefined, showUpdateToast: get(showUpdateToastPref), - pwa: get(pwaPreferences) + pwa: get(pwaPreferences), + textScale: get(textScale) ?? undefined }; savePreferences(prefs as unknown as Record).catch(() => {}); }, 300); @@ -357,6 +361,9 @@ function subscribeForPersistence() { pwaPreferences.subscribe(() => { if (get(stateLoaded)) persistPreferences(); }); + textScale.subscribe(() => { + if (get(stateLoaded)) persistPreferences(); + }); i18next.on('languageChanged', () => { if (get(stateLoaded)) persistPreferences(); }); @@ -386,6 +393,7 @@ export async function loadPreferences(): Promise { if (prefs.requestParams) requestParams.set(prefs.requestParams as Record); if (prefs.showUpdateToast !== undefined) showUpdateToastPref.set(prefs.showUpdateToast as boolean); + textScale.set(typeof prefs.textScale === 'number' ? (prefs.textScale as number) : null); const pwaPrefs = prefs.pwa; if (pwaPrefs) pwaPreferences.set({ @@ -503,6 +511,7 @@ function applyTheme(t: Theme) { } theme.subscribe(applyTheme); +textScale.subscribe((scale) => setTextScale(scale ?? 1)); if (typeof window !== 'undefined') { window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => { diff --git a/cptr/frontend/src/lib/utils/text-scale.ts b/cptr/frontend/src/lib/utils/text-scale.ts new file mode 100644 index 0000000..e783dc1 --- /dev/null +++ b/cptr/frontend/src/lib/utils/text-scale.ts @@ -0,0 +1,4 @@ +export const setTextScale = (scale: number) => { + if (typeof document === 'undefined') return; + document.documentElement.style.setProperty('--app-text-scale', `${scale}`); +}; From 6a359036c5834baf0b02b3da0bf62b85a96b5001 Mon Sep 17 00:00:00 2001 From: Timothy Jaeryang Baek Date: Thu, 2 Jul 2026 13:39:52 -0500 Subject: [PATCH 08/15] refac --- .../src/lib/components/FileEditor.svelte | 2 +- .../lib/components/Settings/General.svelte | 175 +++++++++++++++++- .../src/lib/components/SettingsModal.svelte | 2 +- .../src/lib/components/chat/ChatInput.svelte | 2 + .../components/markdown/EditorToolbar.svelte | 38 ++-- .../components/markdown/RichTextEditor.svelte | 76 ++++---- 6 files changed, 227 insertions(+), 68 deletions(-) diff --git a/cptr/frontend/src/lib/components/FileEditor.svelte b/cptr/frontend/src/lib/components/FileEditor.svelte index 819eda7..2530ad8 100644 --- a/cptr/frontend/src/lib/components/FileEditor.svelte +++ b/cptr/frontend/src/lib/components/FileEditor.svelte @@ -941,7 +941,7 @@ EditorView.theme({ '&': { height: '100%', - fontSize: '13px', + fontSize: '0.8125rem', backgroundColor: dark ? '#000' : '#ffffff' }, '.cm-scroller': { fontFamily: '"JetBrains Mono", "Fira Code", ui-monospace, monospace' }, diff --git a/cptr/frontend/src/lib/components/Settings/General.svelte b/cptr/frontend/src/lib/components/Settings/General.svelte index 6482790..8387bab 100644 --- a/cptr/frontend/src/lib/components/Settings/General.svelte +++ b/cptr/frontend/src/lib/components/Settings/General.svelte @@ -1,7 +1,7 @@
@@ -91,13 +137,72 @@ {/each} +

{$t('general.uiScale')}

+
+
+ + {$t('general.uiScale')} + + +
+ + {#if scaleEnabled} +
+ + setTextScalePreference(scaleDraft)} + /> + +
+ {/if} +
+ -

{$t('general.notifications')}

+

+ {$t('general.notifications')} +

@@ -106,7 +211,9 @@ @@ -131,7 +238,9 @@ {#if $session?.role === 'admin'}

{$t('general.updates')}

@@ -154,9 +263,7 @@ {/each}

- {$streamingBehavior === 'queue' - ? $t('general.queueDesc') - : $t('general.interruptDesc')} + {$streamingBehavior === 'queue' ? $t('general.queueDesc') : $t('general.interruptDesc')}

@@ -171,3 +278,53 @@ + + diff --git a/cptr/frontend/src/lib/components/SettingsModal.svelte b/cptr/frontend/src/lib/components/SettingsModal.svelte index 0783c8c..bccdc70 100644 --- a/cptr/frontend/src/lib/components/SettingsModal.svelte +++ b/cptr/frontend/src/lib/components/SettingsModal.svelte @@ -124,7 +124,7 @@ class="w-full max-w-3xl mx-4 md:mx-0 flex flex-col md:flex-row max-h-[85vh] md:h-[560px]" >