Skip to content
Merged

refac #106

Show file tree
Hide file tree
Changes from all commits
Commits
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
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ 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.4] - 2026-07-07

### Fixed

- 💬 **Slash commands at the start.** New chats no longer block slash commands before the first regular message.
- 📱 **Better mobile typing.** Pressing Enter on phones and tablets now behaves like normal typing instead of choosing a suggestion or sending too soon.
- 🤖 **Larger Claude Code replies.** Claude Code can now handle bigger answers before chat display limits are applied.

## [0.8.3] - 2026-07-07

### Fixed
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,4 @@ This is safe when you are the only user and you control the network. It is not s

## License

Open Use License. Source available. All rights reserved. See [LICENSE](LICENSE). [Enterprise licenses available](mailto:sales@openwebui.com).
Open Use License. Source available. All rights reserved. See [LICENSE](LICENSE). [Commercial licenses](https://openwebui.com/computer/license) and [enterprise licenses](mailto:sales@openwebui.com) available.
2 changes: 2 additions & 0 deletions cptr/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ def _env_int(name: str, default: int) -> int:
CHAT_TOOL_MAX_CHARS = int(os.environ.get("CHAT_TOOL_MAX_CHARS", "50000"))
CHAT_TOOL_COMMAND_MAX_CHARS = int(os.environ.get("CHAT_TOOL_COMMAND_MAX_CHARS", "8000"))
CHAT_COMPACT_TOKEN_THRESHOLD = int(os.environ.get("CHAT_COMPACT_TOKEN_THRESHOLD", "80000"))
# Claude SDK stdout JSON buffer; chat/tool output caps apply later after parsing.
CLAUDE_CODE_MAX_BUFFER_SIZE = _env_int("CPTR_CLAUDE_CODE_MAX_BUFFER_SIZE", 128 * 1024 * 1024)

# ── Execute timeout ─────────────────────────────────────────
# Default wait (seconds) for run_command / check_task when the caller
Expand Down
15 changes: 13 additions & 2 deletions cptr/frontend/src/lib/components/chat/ChatInput.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,13 @@
: $t('chat.voiceModeOn')
);

function isMobileInput(): boolean {
return (
typeof window !== 'undefined' &&
window.matchMedia('(hover: none) and (pointer: coarse)').matches
);
}

// ── Lowlight setup ──────────────────────────────
const lowlight = createLowlight(all);
const _origHighlight = lowlight.highlight.bind(lowlight);
Expand Down Expand Up @@ -313,6 +320,7 @@
return true;
}
if (event.key === 'Enter') {
if (isMobileInput()) return false;
const item = currentItems[selectedIndex];
if (item && command) command(item);
return true;
Expand Down Expand Up @@ -477,6 +485,7 @@
return true;
}
if (event.key === 'Enter') {
if (isMobileInput()) return false;
const item = currentItems[selectedIndex];
if (item && command) command(item);
return true;
Expand Down Expand Up @@ -554,6 +563,8 @@
spellcheck: 'true'
},
handleKeyDown: (view, event) => {
if (event.key === 'Enter' && isMobileInput()) return false;

if (showSlashCommands) {
if (event.key === 'ArrowDown') {
event.preventDefault();
Expand Down Expand Up @@ -939,7 +950,7 @@
}
});

const slashCommandQuery = $derived(inputText.trim().toLowerCase());
const slashCommandQuery = $derived(inputText.trimStart().toLowerCase());
const slashCommandIds = $derived.by(() => {
if (!slashCommandQuery.startsWith('/')) return [];
const ids: string[] = [];
Expand Down Expand Up @@ -979,7 +990,7 @@
$effect(() => {
const query = slashCommandQuery;
const requestId = ++slashSkillsRequestId;
if (!query.startsWith('/') || !workspace) {
if (!query.startsWith('/') || /\s/.test(query.slice(1)) || !workspace) {
slashSkillSuggestions = [];
return;
}
Expand Down
8 changes: 1 addition & 7 deletions cptr/frontend/src/lib/components/chat/ChatPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -299,9 +299,7 @@
const streaming = $derived(allMessages.some((m) => m.role === 'assistant' && !m.done));
const isLanding = $derived(allMessages.length === 0 && !chatId);
const hasChatContent = $derived(
activePath.some(
({ msg }) => msg.role === 'user' && msg.content.trim() && !msg.content.trim().startsWith('/')
)
activePath.some(({ msg }) => msg.role === 'user' && msg.content.trim())
);
const workspaceDisplayName = $derived(getPathDisplayName(workspace, 'workspace'));
const displayChatTitle = $derived(chatTitle || firstUserMessageTitle() || workspaceDisplayName);
Expand Down Expand Up @@ -722,10 +720,6 @@
let text = inputText.trim();
if (!text || !selectedModel) return;
if (sending) return;
if (!hasChatContent && text.startsWith('/') && text !== '/plan') {
toast.error('Only /plan is available before this chat has content');
return;
}
if (hasChatContent && text === '/compact') {
await handleManualCompact();
return;
Expand Down
2 changes: 2 additions & 0 deletions cptr/utils/agents/claude_code.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import shlex
from typing import Any, AsyncIterator

from cptr.env import CLAUDE_CODE_MAX_BUFFER_SIZE
from cptr.utils.agents.attachments import PreparedAgentAttachments
from cptr.utils.agents.events import (
AgentDone,
Expand Down Expand Up @@ -128,6 +129,7 @@ async def run_claude_code_agent(
"permission_mode": permission_mode,
"env": env,
"include_partial_messages": True,
"max_buffer_size": CLAUDE_CODE_MAX_BUFFER_SIZE,
}
if workspace:
options_kwargs["cwd"] = workspace
Expand Down
2 changes: 0 additions & 2 deletions cptr/utils/chat_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,6 @@ def _build_skill_create_prompt(user_request: str) -> str:

def _message_has_real_content(message: dict) -> bool:
text = _plain_message_text(message.get("content")).strip()
if message.get("role") == "user" and text.startswith("/"):
return False
return bool(
text or message.get("tool_calls") or message.get("reasoning_items") or message.get("output")
)
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "cptr"
version = "0.8.3"
version = "0.8.4"
description = "Your computer, from anywhere. Code, manage, and control your machine from the web."
license = {file = "LICENSE"}
readme = "README.md"
Expand Down
2 changes: 1 addition & 1 deletion uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading