Fix file uploads: include content in messages, update README, remove secrets

- File uploads now read content and include in AI context
- Truncate files over 50KB to avoid token limits
- Updated README with JSON config documentation
- Removed secrets.json from git tracking
This commit is contained in:
2026-02-25 03:54:51 +00:00
parent e6ba19fe8e
commit 05b3ae0071
2 changed files with 132 additions and 103 deletions

203
README.md
View File

@@ -2,12 +2,10 @@
A modern, OpenWebUI-compatible chat interface for OpenClaw with LDAP SSO support.
![OpenClaw WebUI](https://via.placeholder.com/800x400?text=OpenClaw+WebUI)
## Features
- **Modern Chat Interface** - Clean, responsive UI inspired by OpenWebUI
- **Multi-file Upload** - Attach files to messages
- **Multi-file Upload** - Attach files with content included in context
- **Code Canvas** - Side panel for code editing and viewing
- **Chat History** - Persistent conversation storage
- **Streaming Responses** - Real-time token streaming
@@ -18,43 +16,95 @@ A modern, OpenWebUI-compatible chat interface for OpenClaw with LDAP SSO support
## Quick Start
```bash
# Install dependencies
# Clone
git clone https://git.theta42.com/nova/openclaw-webui.git
cd openclaw-webui
# Install
npm install
# Development mode (with hot reload)
# Development (hot reload)
npm run dev
# Production build
npm run build
# Start production server
npm start
# Production server
NODE_ENV=production npm start
```
The server runs on port 3000 by default.
## Configuration
Configure via environment variables:
Uses JSON config files in `conf/` directory:
```bash
# Server
PORT=3000
NODE_ENV=production
```
conf/
├── base.json # Base config (all environments)
├── development.json # Dev overrides (auth disabled)
├── production.json # Production overrides
├── secrets.json # Secrets (gitignored!)
└── secrets.example.json # Template
```
# OpenClaw Gateway
OPENCLAW_GATEWAY=http://127.0.0.1:18789
### Load Order
# LDAP Authentication
LDAP_ENABLED=true
LDAP_URL=ldap://localhost:389
LDAP_BASE_DN=ou=users,dc=example,dc=com
Files merge in order: `base.json``[environment].json``secrets.json`
# Session
SESSION_SECRET=your-secret-key
### Example Configs
# Development (disable auth)
DISABLE_AUTH=true
**conf/base.json:**
```json
{
"server": { "port": 8089 },
"gateway": { "url": "http://127.0.0.1:18789" },
"auth": {
"disabled": false,
"ldap": {
"enabled": true,
"url": "ldap://10.1.0.55:389",
"baseDN": "dc=example,dc=com",
"searchFilter": "(uid={{username}})"
}
}
}
```
**conf/secrets.json:**
```json
{
"gateway": { "token": "your-openclaw-token" },
"session": { "secret": "random-session-secret" },
"auth": {
"ldap": {
"bindDN": "cn=service,ou=people,dc=example,dc=com",
"bindPassword": "ldap-password"
}
}
}
```
### Environment Variables
Can override config at runtime:
- `PORT` - Server port
- `OPENCLAW_GATEWAY` - Gateway URL
- `OPENCLAW_TOKEN` - Gateway auth token
- `SESSION_SECRET` - Session signing secret
- `LDAP_ENABLED` - Enable LDAP auth
## LDAP Authentication
Supports standard LDAP servers (OpenLDAP, Active Directory):
1. Service binds with `bindDN` + `bindPassword`
2. Searches for user with `searchFilter`
3. Binds as user to verify password
**Search Filter:**
Use `{{username}}` as placeholder:
```
(&(memberof=cn=app_access,ou=groups,dc=example,dc=com)(uid={{username}}))
```
## Architecture
@@ -65,7 +115,7 @@ DISABLE_AUTH=true
├─────────────────────────────────────────────────────────┤
│ Frontend (Vanilla JS + Vite) │
│ ├── Chat Interface │
│ ├── File Upload
│ ├── File Upload (content included)
│ ├── Code Canvas │
│ └── History Sidebar │
├─────────────────────────────────────────────────────────┤
@@ -73,108 +123,67 @@ DISABLE_AUTH=true
│ ├── LDAP SSO Authentication │
│ ├── Session Management │
│ ├── Chat History Persistence │
│ ├── File Upload Handling │
│ └── /v1/chat/completions Proxy │
├─────────────────────────────────────────────────────────┤
│ OpenClaw Gateway (port 18789) │
│ ├── WebSocket Protocol │
│ ├── OpenAI-Compatible API │
│ └── Agent Execution │
└─────────────────────────────────────────────────────────┘
```
## API Endpoints
### Authentication
- `GET /api/auth/status` - Check authentication status
- `POST /api/auth/login` - Login with username/password
- `GET /api/auth/status` - Check auth status
- `POST /api/auth/login` - Login
- `POST /api/auth/logout` - Logout
### Conversations
- `GET /api/conversations` - List all conversations
- `POST /api/conversations` - Create new conversation
- `GET /api/conversations` - List conversations
- `POST /api/conversations` - Create conversation
- `PUT /api/conversations/:id` - Update conversation
- `DELETE /api/conversations/:id` - Delete conversation
- `GET /api/conversations/:id/messages` - Get messages
- `POST /api/conversations/:id/messages` - Save message
### Files
- `POST /api/upload` - Upload file
- `GET /api/upload/:id` - Download file
- `GET /api/upload/:id/meta` - Get file metadata
### OpenAI-Compatible
- `POST /v1/chat/completions` - Chat completions (proxied to OpenClaw)
- `GET /v1/models` - List available models
- `POST /v1/chat/completions` - Chat (proxied to OpenClaw)
- `GET /v1/models` - List models
## LDAP Configuration
The LDAP integration supports standard Active Directory and OpenLDAP setups:
## Production Deployment
**Systemd Service:**
```bash
# LDAP Server URL
LDAP_URL=ldap://ldap.example.com:389
# Create service file
mkdir -p ~/.config/systemd/user
cp openclaw-webui.service ~/.config/systemd/user/
# Base DN for user search
LDAP_BASE_DN=ou=users,dc=example,dc=com
# Enable and start
systemctl --user enable openclaw-webui
systemctl --user start openclaw-webui
# Bind DN for searching (if needed)
LDAP_BIND_DN=cn=admin,dc=example,dc=com
LDAP_BIND_PASSWORD=admin_password
# Custom search filter (optional)
LDAP_SEARCH_FILTER=(uid={{username}})
# View logs
journalctl --user -u openclaw-webui -f
```
Users authenticate with their LDAP username and password.
**Requirements:**
- OpenClaw Gateway running on port 18789
- Enable HTTP chat completions in gateway config:
```json
{
"gateway": {
"http": {
"endpoints": { "chatCompletions": { "enabled": true } }
}
}
}
```
## Development
```bash
# Run in development mode
npm run dev
# This starts:
# - Backend server on port 3000 (with hot reload)
# - Vite dev server on port 5173 (with HMR)
# - Proxy from Vite to backend for /api and /v1
```
## Production Deployment
```bash
# Build frontend
npm run build
# Start production server
NODE_ENV=production npm start
```
The production server serves static files from `dist/` and handles all API routes.
## File Structure
```
openclaw-webui/
├── client/
│ ├── index.html
│ ├── main.js # Main application
│ └── public/
│ ├── styles.css
│ └── favicon.svg
├── server/
│ └── index.js # Express server
├── data/ # Chat history (gitignored)
├── dist/ # Production build (gitignored)
├── package.json
├── vite.config.js
└── README.md
# Frontend: http://localhost:5173 (Vite HMR)
# Backend: http://localhost:3000 (auto-restart)
```
## License
MIT License - See LICENSE file for details.
## Credits
Built for [OpenClaw](https://github.com/openclaw/openclaw) - AI assistant framework.
MIT

View File

@@ -550,15 +550,18 @@ async function handleFileSelect(e) {
for (const file of files) {
try {
const uploaded = await api.uploadFile(file);
// Read file content
const content = await readFileContent(file);
state.files.push({
id: uploaded.id,
id: file.name + '-' + Date.now(),
name: file.name,
type: file.type,
size: file.size
size: file.size,
content: content
});
} catch (err) {
console.error('Failed to upload file:', file.name, err);
console.error('Failed to read file:', file.name, err);
}
}
@@ -566,6 +569,15 @@ async function handleFileSelect(e) {
e.target.value = '';
}
function readFileContent(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsText(file);
});
}
function renderFilesPreview() {
const container = document.getElementById('files-preview');
if (!container) return;
@@ -663,8 +675,16 @@ async function handleSendMessage() {
function buildMessageContent(text, files) {
if (!files.length) return text;
const fileDescriptions = files.map(f => `[Attached: ${f.name}]`).join('\n');
return `${text}\n\n${fileDescriptions}`;
const fileContents = files.map(f => {
const maxLen = 50000; // Limit file content length
const content = f.content || '[File content not available]';
const truncated = content.length > maxLen
? content.substring(0, maxLen) + '\n... [truncated]'
: content;
return `--- ${f.name} ---\n${truncated}\n--- end of ${f.name} ---`;
}).join('\n\n');
return `${text}\n\n${fileContents}`;
}
function updateLastMessage(content) {