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:
203
README.md
203
README.md
@@ -2,12 +2,10 @@
|
|||||||
|
|
||||||
A modern, OpenWebUI-compatible chat interface for OpenClaw with LDAP SSO support.
|
A modern, OpenWebUI-compatible chat interface for OpenClaw with LDAP SSO support.
|
||||||
|
|
||||||

|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- **Modern Chat Interface** - Clean, responsive UI inspired by OpenWebUI
|
- **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
|
- **Code Canvas** - Side panel for code editing and viewing
|
||||||
- **Chat History** - Persistent conversation storage
|
- **Chat History** - Persistent conversation storage
|
||||||
- **Streaming Responses** - Real-time token streaming
|
- **Streaming Responses** - Real-time token streaming
|
||||||
@@ -18,43 +16,95 @@ A modern, OpenWebUI-compatible chat interface for OpenClaw with LDAP SSO support
|
|||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Install dependencies
|
# Clone
|
||||||
|
git clone https://git.theta42.com/nova/openclaw-webui.git
|
||||||
|
cd openclaw-webui
|
||||||
|
|
||||||
|
# Install
|
||||||
npm install
|
npm install
|
||||||
|
|
||||||
# Development mode (with hot reload)
|
# Development (hot reload)
|
||||||
npm run dev
|
npm run dev
|
||||||
|
|
||||||
# Production build
|
# Production build
|
||||||
npm run build
|
npm run build
|
||||||
|
|
||||||
# Start production server
|
# Production server
|
||||||
npm start
|
NODE_ENV=production npm start
|
||||||
```
|
```
|
||||||
|
|
||||||
The server runs on port 3000 by default.
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Configure via environment variables:
|
Uses JSON config files in `conf/` directory:
|
||||||
|
|
||||||
```bash
|
```
|
||||||
# Server
|
conf/
|
||||||
PORT=3000
|
├── base.json # Base config (all environments)
|
||||||
NODE_ENV=production
|
├── development.json # Dev overrides (auth disabled)
|
||||||
|
├── production.json # Production overrides
|
||||||
|
├── secrets.json # Secrets (gitignored!)
|
||||||
|
└── secrets.example.json # Template
|
||||||
|
```
|
||||||
|
|
||||||
# OpenClaw Gateway
|
### Load Order
|
||||||
OPENCLAW_GATEWAY=http://127.0.0.1:18789
|
|
||||||
|
|
||||||
# LDAP Authentication
|
Files merge in order: `base.json` → `[environment].json` → `secrets.json`
|
||||||
LDAP_ENABLED=true
|
|
||||||
LDAP_URL=ldap://localhost:389
|
|
||||||
LDAP_BASE_DN=ou=users,dc=example,dc=com
|
|
||||||
|
|
||||||
# Session
|
### Example Configs
|
||||||
SESSION_SECRET=your-secret-key
|
|
||||||
|
|
||||||
# Development (disable auth)
|
**conf/base.json:**
|
||||||
DISABLE_AUTH=true
|
```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
|
## Architecture
|
||||||
@@ -65,7 +115,7 @@ DISABLE_AUTH=true
|
|||||||
├─────────────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────────────┤
|
||||||
│ Frontend (Vanilla JS + Vite) │
|
│ Frontend (Vanilla JS + Vite) │
|
||||||
│ ├── Chat Interface │
|
│ ├── Chat Interface │
|
||||||
│ ├── File Upload │
|
│ ├── File Upload (content included) │
|
||||||
│ ├── Code Canvas │
|
│ ├── Code Canvas │
|
||||||
│ └── History Sidebar │
|
│ └── History Sidebar │
|
||||||
├─────────────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────────────┤
|
||||||
@@ -73,108 +123,67 @@ DISABLE_AUTH=true
|
|||||||
│ ├── LDAP SSO Authentication │
|
│ ├── LDAP SSO Authentication │
|
||||||
│ ├── Session Management │
|
│ ├── Session Management │
|
||||||
│ ├── Chat History Persistence │
|
│ ├── Chat History Persistence │
|
||||||
│ ├── File Upload Handling │
|
|
||||||
│ └── /v1/chat/completions Proxy │
|
│ └── /v1/chat/completions Proxy │
|
||||||
├─────────────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────────────┤
|
||||||
│ OpenClaw Gateway (port 18789) │
|
│ OpenClaw Gateway (port 18789) │
|
||||||
│ ├── WebSocket Protocol │
|
|
||||||
│ ├── OpenAI-Compatible API │
|
|
||||||
│ └── Agent Execution │
|
|
||||||
└─────────────────────────────────────────────────────────┘
|
└─────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
## API Endpoints
|
## API Endpoints
|
||||||
|
|
||||||
### Authentication
|
### Authentication
|
||||||
- `GET /api/auth/status` - Check authentication status
|
- `GET /api/auth/status` - Check auth status
|
||||||
- `POST /api/auth/login` - Login with username/password
|
- `POST /api/auth/login` - Login
|
||||||
- `POST /api/auth/logout` - Logout
|
- `POST /api/auth/logout` - Logout
|
||||||
|
|
||||||
### Conversations
|
### Conversations
|
||||||
- `GET /api/conversations` - List all conversations
|
- `GET /api/conversations` - List conversations
|
||||||
- `POST /api/conversations` - Create new conversation
|
- `POST /api/conversations` - Create conversation
|
||||||
- `PUT /api/conversations/:id` - Update conversation
|
- `PUT /api/conversations/:id` - Update conversation
|
||||||
- `DELETE /api/conversations/:id` - Delete conversation
|
- `DELETE /api/conversations/:id` - Delete conversation
|
||||||
- `GET /api/conversations/:id/messages` - Get messages
|
- `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
|
### OpenAI-Compatible
|
||||||
- `POST /v1/chat/completions` - Chat completions (proxied to OpenClaw)
|
- `POST /v1/chat/completions` - Chat (proxied to OpenClaw)
|
||||||
- `GET /v1/models` - List available models
|
- `GET /v1/models` - List models
|
||||||
|
|
||||||
## LDAP Configuration
|
## Production Deployment
|
||||||
|
|
||||||
The LDAP integration supports standard Active Directory and OpenLDAP setups:
|
|
||||||
|
|
||||||
|
**Systemd Service:**
|
||||||
```bash
|
```bash
|
||||||
# LDAP Server URL
|
# Create service file
|
||||||
LDAP_URL=ldap://ldap.example.com:389
|
mkdir -p ~/.config/systemd/user
|
||||||
|
cp openclaw-webui.service ~/.config/systemd/user/
|
||||||
|
|
||||||
# Base DN for user search
|
# Enable and start
|
||||||
LDAP_BASE_DN=ou=users,dc=example,dc=com
|
systemctl --user enable openclaw-webui
|
||||||
|
systemctl --user start openclaw-webui
|
||||||
|
|
||||||
# Bind DN for searching (if needed)
|
# View logs
|
||||||
LDAP_BIND_DN=cn=admin,dc=example,dc=com
|
journalctl --user -u openclaw-webui -f
|
||||||
LDAP_BIND_PASSWORD=admin_password
|
|
||||||
|
|
||||||
# Custom search filter (optional)
|
|
||||||
LDAP_SEARCH_FILTER=(uid={{username}})
|
|
||||||
```
|
```
|
||||||
|
|
||||||
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
|
## Development
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Run in development mode
|
|
||||||
npm run dev
|
npm run dev
|
||||||
|
# Frontend: http://localhost:5173 (Vite HMR)
|
||||||
# This starts:
|
# Backend: http://localhost:3000 (auto-restart)
|
||||||
# - 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
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT License - See LICENSE file for details.
|
MIT
|
||||||
|
|
||||||
## Credits
|
|
||||||
|
|
||||||
Built for [OpenClaw](https://github.com/openclaw/openclaw) - AI assistant framework.
|
|
||||||
@@ -550,15 +550,18 @@ async function handleFileSelect(e) {
|
|||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
try {
|
try {
|
||||||
const uploaded = await api.uploadFile(file);
|
// Read file content
|
||||||
|
const content = await readFileContent(file);
|
||||||
|
|
||||||
state.files.push({
|
state.files.push({
|
||||||
id: uploaded.id,
|
id: file.name + '-' + Date.now(),
|
||||||
name: file.name,
|
name: file.name,
|
||||||
type: file.type,
|
type: file.type,
|
||||||
size: file.size
|
size: file.size,
|
||||||
|
content: content
|
||||||
});
|
});
|
||||||
} catch (err) {
|
} 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 = '';
|
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() {
|
function renderFilesPreview() {
|
||||||
const container = document.getElementById('files-preview');
|
const container = document.getElementById('files-preview');
|
||||||
if (!container) return;
|
if (!container) return;
|
||||||
@@ -663,8 +675,16 @@ async function handleSendMessage() {
|
|||||||
function buildMessageContent(text, files) {
|
function buildMessageContent(text, files) {
|
||||||
if (!files.length) return text;
|
if (!files.length) return text;
|
||||||
|
|
||||||
const fileDescriptions = files.map(f => `[Attached: ${f.name}]`).join('\n');
|
const fileContents = files.map(f => {
|
||||||
return `${text}\n\n${fileDescriptions}`;
|
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) {
|
function updateLastMessage(content) {
|
||||||
|
|||||||
Reference in New Issue
Block a user