mirror of
https://github.com/siteboon/claudecodeui.git
synced 2025-12-10 14:09:39 +00:00
Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a100648ccb | ||
|
|
7d0fd141ff | ||
|
|
e5a05d9865 | ||
|
|
2d6c3b5755 | ||
|
|
2a5d27ffc0 | ||
|
|
3c9a4cab82 | ||
|
|
ce9ab0cd16 | ||
|
|
66fad9a6a2 | ||
|
|
0fcf906ff0 | ||
|
|
7e1f2940d3 | ||
|
|
533d589132 | ||
|
|
d1f310161f | ||
|
|
b853e8cda1 | ||
|
|
1610de1f22 | ||
|
|
3f743d8210 | ||
|
|
d74a99ef24 | ||
|
|
133af82935 | ||
|
|
3daf21c3d1 | ||
|
|
f52ca8e702 | ||
|
|
d82a004224 |
@@ -9,4 +9,7 @@
|
|||||||
#API server
|
#API server
|
||||||
PORT=3001
|
PORT=3001
|
||||||
#Frontend port
|
#Frontend port
|
||||||
VITE_PORT=5173
|
VITE_PORT=5173
|
||||||
|
|
||||||
|
# Uncomment the following line if you have a custom claude cli path other than the default "claude"
|
||||||
|
# CLAUDE_CLI_PATH=claude
|
||||||
|
|||||||
@@ -10,7 +10,10 @@
|
|||||||
"github": {
|
"github": {
|
||||||
"release": true,
|
"release": true,
|
||||||
"releaseName": "Claude Code UI v${version}",
|
"releaseName": "Claude Code UI v${version}",
|
||||||
"autoGenerate": true
|
"releaseNotes": {
|
||||||
|
"commit": "* ${commit.subject} (${sha}){ - thanks @${author.login}!}",
|
||||||
|
"excludeMatches": ["viper151"]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"hooks": {
|
"hooks": {
|
||||||
"before:init": ["npm run build"]
|
"before:init": ["npm run build"]
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "@siteboon/claude-code-ui",
|
"name": "@siteboon/claude-code-ui",
|
||||||
"version": "1.8.8",
|
"version": "1.8.12",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "@siteboon/claude-code-ui",
|
"name": "@siteboon/claude-code-ui",
|
||||||
"version": "1.8.8",
|
"version": "1.8.12",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/lang-css": "^6.3.1",
|
"@codemirror/lang-css": "^6.3.1",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@siteboon/claude-code-ui",
|
"name": "@siteboon/claude-code-ui",
|
||||||
"version": "1.8.8",
|
"version": "1.8.12",
|
||||||
"description": "A web-based UI for Claude Code CLI",
|
"description": "A web-based UI for Claude Code CLI",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "server/index.js",
|
"main": "server/index.js",
|
||||||
|
|||||||
@@ -25,15 +25,6 @@ async function spawnClaude(command, options = {}, ws) {
|
|||||||
// Build Claude CLI command - start with print/resume flags first
|
// Build Claude CLI command - start with print/resume flags first
|
||||||
const args = [];
|
const args = [];
|
||||||
|
|
||||||
// Add print flag with command if we have a command
|
|
||||||
if (command && command.trim()) {
|
|
||||||
|
|
||||||
// Separate arguments for better cross-platform compatibility
|
|
||||||
// This prevents issues with spaces and quotes on Windows
|
|
||||||
args.push('--print');
|
|
||||||
args.push(command);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use cwd (actual project directory) instead of projectPath (Claude's metadata directory)
|
// Use cwd (actual project directory) instead of projectPath (Claude's metadata directory)
|
||||||
const workingDir = cwd || process.cwd();
|
const workingDir = cwd || process.cwd();
|
||||||
|
|
||||||
@@ -225,6 +216,17 @@ async function spawnClaude(command, options = {}, ws) {
|
|||||||
console.log('📝 Skip permissions disabled due to plan mode');
|
console.log('📝 Skip permissions disabled due to plan mode');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add print flag with command if we have a command
|
||||||
|
if (command && command.trim()) {
|
||||||
|
|
||||||
|
// Separate arguments for better cross-platform compatibility
|
||||||
|
// This prevents issues with spaces and quotes on Windows
|
||||||
|
args.push('--print');
|
||||||
|
// Use `--` so user input is always treated as text, not options
|
||||||
|
args.push('--');
|
||||||
|
args.push(command);
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Spawning Claude CLI:', 'claude', args.map(arg => {
|
console.log('Spawning Claude CLI:', 'claude', args.map(arg => {
|
||||||
const cleanArg = arg.replace(/\n/g, '\\n').replace(/\r/g, '\\r');
|
const cleanArg = arg.replace(/\n/g, '\\n').replace(/\r/g, '\\r');
|
||||||
@@ -235,7 +237,11 @@ async function spawnClaude(command, options = {}, ws) {
|
|||||||
console.log('🔍 Full command args:', JSON.stringify(args, null, 2));
|
console.log('🔍 Full command args:', JSON.stringify(args, null, 2));
|
||||||
console.log('🔍 Final Claude command will be: claude ' + args.join(' '));
|
console.log('🔍 Final Claude command will be: claude ' + args.join(' '));
|
||||||
|
|
||||||
const claudeProcess = spawnFunction('claude', args, {
|
// Use Claude CLI from environment variable or default to 'claude'
|
||||||
|
const claudePath = process.env.CLAUDE_CLI_PATH || 'claude';
|
||||||
|
console.log('🔍 Using Claude CLI path:', claudePath);
|
||||||
|
|
||||||
|
const claudeProcess = spawnFunction(claudePath, args, {
|
||||||
cwd: workingDir,
|
cwd: workingDir,
|
||||||
stdio: ['pipe', 'pipe', 'pipe'],
|
stdio: ['pipe', 'pipe', 'pipe'],
|
||||||
env: { ...process.env } // Inherit all environment variables
|
env: { ...process.env } // Inherit all environment variables
|
||||||
|
|||||||
47
src/App.jsx
47
src/App.jsx
@@ -33,6 +33,7 @@ import { TasksSettingsProvider } from './contexts/TasksSettingsContext';
|
|||||||
import { WebSocketProvider, useWebSocketContext } from './contexts/WebSocketContext';
|
import { WebSocketProvider, useWebSocketContext } from './contexts/WebSocketContext';
|
||||||
import ProtectedRoute from './components/ProtectedRoute';
|
import ProtectedRoute from './components/ProtectedRoute';
|
||||||
import { useVersionCheck } from './hooks/useVersionCheck';
|
import { useVersionCheck } from './hooks/useVersionCheck';
|
||||||
|
import useLocalStorage from './hooks/useLocalStorage';
|
||||||
import { api, authenticatedFetch } from './utils/api';
|
import { api, authenticatedFetch } from './utils/api';
|
||||||
|
|
||||||
|
|
||||||
@@ -54,22 +55,10 @@ function AppContent() {
|
|||||||
const [isInputFocused, setIsInputFocused] = useState(false);
|
const [isInputFocused, setIsInputFocused] = useState(false);
|
||||||
const [showSettings, setShowSettings] = useState(false);
|
const [showSettings, setShowSettings] = useState(false);
|
||||||
const [showQuickSettings, setShowQuickSettings] = useState(false);
|
const [showQuickSettings, setShowQuickSettings] = useState(false);
|
||||||
const [autoExpandTools, setAutoExpandTools] = useState(() => {
|
const [autoExpandTools, setAutoExpandTools] = useLocalStorage('autoExpandTools', false);
|
||||||
const saved = localStorage.getItem('autoExpandTools');
|
const [showRawParameters, setShowRawParameters] = useLocalStorage('showRawParameters', false);
|
||||||
return saved !== null ? JSON.parse(saved) : false;
|
const [autoScrollToBottom, setAutoScrollToBottom] = useLocalStorage('autoScrollToBottom', true);
|
||||||
});
|
const [sendByCtrlEnter, setSendByCtrlEnter] = useLocalStorage('sendByCtrlEnter', false);
|
||||||
const [showRawParameters, setShowRawParameters] = useState(() => {
|
|
||||||
const saved = localStorage.getItem('showRawParameters');
|
|
||||||
return saved !== null ? JSON.parse(saved) : false;
|
|
||||||
});
|
|
||||||
const [autoScrollToBottom, setAutoScrollToBottom] = useState(() => {
|
|
||||||
const saved = localStorage.getItem('autoScrollToBottom');
|
|
||||||
return saved !== null ? JSON.parse(saved) : true;
|
|
||||||
});
|
|
||||||
const [sendByCtrlEnter, setSendByCtrlEnter] = useState(() => {
|
|
||||||
const saved = localStorage.getItem('sendByCtrlEnter');
|
|
||||||
return saved !== null ? JSON.parse(saved) : false;
|
|
||||||
});
|
|
||||||
// Session Protection System: Track sessions with active conversations to prevent
|
// Session Protection System: Track sessions with active conversations to prevent
|
||||||
// automatic project updates from interrupting ongoing chats. When a user sends
|
// automatic project updates from interrupting ongoing chats. When a user sends
|
||||||
// a message, the session is marked as "active" and project updates are paused
|
// a message, the session is marked as "active" and project updates are paused
|
||||||
@@ -491,9 +480,10 @@ function AppContent() {
|
|||||||
return (
|
return (
|
||||||
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<div
|
<button
|
||||||
className="fixed inset-0 bg-black/50 backdrop-blur-sm"
|
className="fixed inset-0 bg-black/50 backdrop-blur-sm"
|
||||||
onClick={() => setShowVersionModal(false)}
|
onClick={() => setShowVersionModal(false)}
|
||||||
|
aria-label="Close version upgrade modal"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
@@ -604,7 +594,7 @@ function AppContent() {
|
|||||||
<div className={`fixed inset-0 z-50 flex transition-all duration-150 ease-out ${
|
<div className={`fixed inset-0 z-50 flex transition-all duration-150 ease-out ${
|
||||||
sidebarOpen ? 'opacity-100 visible' : 'opacity-0 invisible'
|
sidebarOpen ? 'opacity-100 visible' : 'opacity-0 invisible'
|
||||||
}`}>
|
}`}>
|
||||||
<div
|
<button
|
||||||
className="fixed inset-0 bg-background/80 backdrop-blur-sm transition-opacity duration-150 ease-out"
|
className="fixed inset-0 bg-background/80 backdrop-blur-sm transition-opacity duration-150 ease-out"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -615,6 +605,7 @@ function AppContent() {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
setSidebarOpen(false);
|
setSidebarOpen(false);
|
||||||
}}
|
}}
|
||||||
|
aria-label="Close sidebar"
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
className={`relative w-[85vw] max-w-sm sm:w-80 bg-card border-r border-border transform transition-transform duration-150 ease-out ${
|
className={`relative w-[85vw] max-w-sm sm:w-80 bg-card border-r border-border transform transition-transform duration-150 ease-out ${
|
||||||
@@ -688,25 +679,13 @@ function AppContent() {
|
|||||||
isOpen={showQuickSettings}
|
isOpen={showQuickSettings}
|
||||||
onToggle={setShowQuickSettings}
|
onToggle={setShowQuickSettings}
|
||||||
autoExpandTools={autoExpandTools}
|
autoExpandTools={autoExpandTools}
|
||||||
onAutoExpandChange={(value) => {
|
onAutoExpandChange={setAutoExpandTools}
|
||||||
setAutoExpandTools(value);
|
|
||||||
localStorage.setItem('autoExpandTools', JSON.stringify(value));
|
|
||||||
}}
|
|
||||||
showRawParameters={showRawParameters}
|
showRawParameters={showRawParameters}
|
||||||
onShowRawParametersChange={(value) => {
|
onShowRawParametersChange={setShowRawParameters}
|
||||||
setShowRawParameters(value);
|
|
||||||
localStorage.setItem('showRawParameters', JSON.stringify(value));
|
|
||||||
}}
|
|
||||||
autoScrollToBottom={autoScrollToBottom}
|
autoScrollToBottom={autoScrollToBottom}
|
||||||
onAutoScrollChange={(value) => {
|
onAutoScrollChange={setAutoScrollToBottom}
|
||||||
setAutoScrollToBottom(value);
|
|
||||||
localStorage.setItem('autoScrollToBottom', JSON.stringify(value));
|
|
||||||
}}
|
|
||||||
sendByCtrlEnter={sendByCtrlEnter}
|
sendByCtrlEnter={sendByCtrlEnter}
|
||||||
onSendByCtrlEnterChange={(value) => {
|
onSendByCtrlEnterChange={setSendByCtrlEnter}
|
||||||
setSendByCtrlEnter(value);
|
|
||||||
localStorage.setItem('sendByCtrlEnter', JSON.stringify(value));
|
|
||||||
}}
|
|
||||||
isMobile={isMobile}
|
isMobile={isMobile}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|||||||
41
src/hooks/useLocalStorage.jsx
Normal file
41
src/hooks/useLocalStorage.jsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom hook to persist state in localStorage.
|
||||||
|
*
|
||||||
|
* @param {string} key The key to use for localStorage.
|
||||||
|
* @param {any} initialValue The initial value to use if nothing is in localStorage.
|
||||||
|
* @returns {[any, Function]} A tuple containing the stored value and a setter function.
|
||||||
|
*/
|
||||||
|
function useLocalStorage(key, initialValue) {
|
||||||
|
const [storedValue, setStoredValue] = useState(() => {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return initialValue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const item = window.localStorage.getItem(key);
|
||||||
|
return item ? JSON.parse(item) : initialValue;
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
return initialValue;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const setValue = (value) => {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const valueToStore =
|
||||||
|
value instanceof Function ? value(storedValue) : value;
|
||||||
|
window.localStorage.setItem(key, JSON.stringify(valueToStore));
|
||||||
|
setStoredValue(valueToStore);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return [storedValue, setValue];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useLocalStorage;
|
||||||
Reference in New Issue
Block a user