feat: Add OIDC authentication with Authentik integration
- Add OIDC login flow with Authentik provider - Implement session-based auth with Redis store - Add avatar display from OIDC claims - Fix input field performance with react-textarea-autosize - Stabilize callbacks to prevent unnecessary re-renders - Fix history loading to skip empty session files - Add 2-row default height for input textarea 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,12 @@
|
||||
import { createContext, useContext, useState, useCallback, useRef, useEffect, useMemo } from 'react';
|
||||
|
||||
const WS_URL = import.meta.env.VITE_WS_URL || 'ws://100.105.142.13:3001';
|
||||
const API_URL = import.meta.env.VITE_API_URL || 'http://100.105.142.13:3001';
|
||||
// Build WebSocket URL from current location
|
||||
function getWsUrl() {
|
||||
if (import.meta.env.VITE_WS_URL) return import.meta.env.VITE_WS_URL;
|
||||
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
|
||||
return `${protocol}//${window.location.host}/ws`;
|
||||
}
|
||||
|
||||
const SESSIONS_STORAGE_KEY = 'claude-webui-sessions';
|
||||
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
||||
|
||||
@@ -64,6 +69,10 @@ export function SessionProvider({ children }) {
|
||||
// Current assistant message refs keyed by session ID
|
||||
const currentAssistantMessages = useRef({});
|
||||
|
||||
// Ref to current sessions state (for stable callbacks)
|
||||
const sessionsRef = useRef(sessions);
|
||||
sessionsRef.current = sessions;
|
||||
|
||||
// Track if initial load is done (for auto-connecting restored sessions)
|
||||
const initialLoadDone = useRef(false);
|
||||
const sessionsToConnect = useRef([]);
|
||||
@@ -263,6 +272,26 @@ export function SessionProvider({ children }) {
|
||||
for (const toolMsg of toolUseBlocks) {
|
||||
addMessage(sessionId, toolMsg);
|
||||
}
|
||||
|
||||
// Extract usage stats from message if present
|
||||
const usage = message.usage;
|
||||
if (usage) {
|
||||
const inputTokens = (usage.input_tokens || 0) + (usage.cache_read_input_tokens || 0);
|
||||
const outputTokens = usage.output_tokens || 0;
|
||||
const cacheReadTokens = usage.cache_read_input_tokens || 0;
|
||||
const cacheCreationTokens = usage.cache_creation_input_tokens || 0;
|
||||
|
||||
updateSession(sessionId, (session) => ({
|
||||
stats: {
|
||||
...(session.stats || {}),
|
||||
inputTokens: (session.stats?.inputTokens || 0) + inputTokens,
|
||||
outputTokens: (session.stats?.outputTokens || 0) + outputTokens,
|
||||
cacheReadTokens: (session.stats?.cacheReadTokens || 0) + cacheReadTokens,
|
||||
cacheCreationTokens: (session.stats?.cacheCreationTokens || 0) + cacheCreationTokens,
|
||||
numTurns: (session.stats?.numTurns || 0) + 1,
|
||||
},
|
||||
}));
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -342,6 +371,7 @@ export function SessionProvider({ children }) {
|
||||
|
||||
case 'result': {
|
||||
// Final result with stats
|
||||
console.log(`[${sessionId}] Result event:`, JSON.stringify(event, null, 2));
|
||||
const defaultStats = { totalCost: 0, inputTokens: 0, outputTokens: 0, cacheReadTokens: 0, cacheCreationTokens: 0, numTurns: 0 };
|
||||
updateSession(sessionId, (session) => ({
|
||||
isProcessing: false,
|
||||
@@ -542,7 +572,7 @@ export function SessionProvider({ children }) {
|
||||
const connectSession = useCallback((sessionId) => {
|
||||
if (wsRefs.current[sessionId]?.readyState === WebSocket.OPEN) return;
|
||||
|
||||
const ws = new WebSocket(WS_URL);
|
||||
const ws = new WebSocket(getWsUrl());
|
||||
wsRefs.current[sessionId] = ws;
|
||||
|
||||
ws.onopen = () => {
|
||||
@@ -643,7 +673,7 @@ export function SessionProvider({ children }) {
|
||||
|
||||
// Start Claude session
|
||||
const startClaudeSession = useCallback(async (sessionId) => {
|
||||
const session = sessions[sessionId];
|
||||
const session = sessionsRef.current[sessionId];
|
||||
if (!session) return;
|
||||
|
||||
const ws = wsRefs.current[sessionId];
|
||||
@@ -659,7 +689,8 @@ export function SessionProvider({ children }) {
|
||||
if (session.resumeOnStart) {
|
||||
try {
|
||||
const res = await fetch(
|
||||
`${API_URL}/api/history/${encodeURIComponent(session.project)}?host=${session.host}`
|
||||
`/api/history/${encodeURIComponent(session.project)}?host=${session.host}`,
|
||||
{ credentials: 'include' }
|
||||
);
|
||||
const data = await res.json();
|
||||
if (data.messages && Array.isArray(data.messages)) {
|
||||
@@ -680,7 +711,7 @@ export function SessionProvider({ children }) {
|
||||
host: session.host,
|
||||
}));
|
||||
}
|
||||
}, [sessions, connectSession, updateSession]);
|
||||
}, [connectSession]);
|
||||
|
||||
// Stop Claude session
|
||||
const stopClaudeSession = useCallback((sessionId) => {
|
||||
@@ -693,7 +724,7 @@ export function SessionProvider({ children }) {
|
||||
|
||||
// Send message to session
|
||||
const sendMessage = useCallback(async (sessionId, message, attachments = []) => {
|
||||
const session = sessions[sessionId];
|
||||
const session = sessionsRef.current[sessionId];
|
||||
if (!session?.active) return;
|
||||
|
||||
const ws = wsRefs.current[sessionId];
|
||||
@@ -708,9 +739,10 @@ export function SessionProvider({ children }) {
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/api/upload/${session.claudeSessionId}`, {
|
||||
const res = await fetch(`/api/upload/${session.claudeSessionId}`, {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
credentials: 'include',
|
||||
});
|
||||
const data = await res.json();
|
||||
uploadedFiles = data.files || [];
|
||||
@@ -743,7 +775,7 @@ export function SessionProvider({ children }) {
|
||||
type: 'user_message',
|
||||
message: finalMessage,
|
||||
}));
|
||||
}, [sessions, updateSession, addMessage]);
|
||||
}, [updateSession, addMessage]);
|
||||
|
||||
// Stop generation
|
||||
const stopGeneration = useCallback((sessionId) => {
|
||||
|
||||
Reference in New Issue
Block a user