From 5e26decf199fe64ed0a7cffbf31ee3b0c1a431d7 Mon Sep 17 00:00:00 2001 From: Dario Ghunney Ware Date: Wed, 5 Nov 2025 18:03:58 +0000 Subject: [PATCH] wip - trying to connect to OpenAi --- .../common/model/ApplicationProperties.java | 1 - .../src/main/resources/settings.yml.template | 1 - .../config/ChatbotAiConfiguration.java | 52 ------------------- .../controller/ChatbotController.java | 2 + .../controller/ChatbotExceptionHandler.java | 3 ++ .../configuration/SecurityConfiguration.java | 1 + .../chatbot/ChatbotConversationService.java | 2 + .../chatbot/ChatbotFeatureProperties.java | 20 ++----- .../chatbot/ChatbotIngestionService.java | 2 + .../chatbot/ChatbotRetrievalService.java | 2 + .../service/chatbot/ChatbotService.java | 2 + .../application-proprietary.properties | 2 + devGuide/DeveloperGuide.md | 2 +- frontend/src/core/services/apiClient.ts | 3 ++ frontend/src/core/services/apiClientSetup.ts | 20 ++++++- 15 files changed, 41 insertions(+), 74 deletions(-) delete mode 100644 app/proprietary/src/main/java/stirling/software/proprietary/config/ChatbotAiConfiguration.java diff --git a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java index f5e815111..bb82ac1c2 100644 --- a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java +++ b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java @@ -625,7 +625,6 @@ public class ApplicationProperties { private String primary = "gpt-5-nano"; private String fallback = "gpt-5-mini"; private String embedding = "text-embedding-3-small"; - private String apiKey; } @Data diff --git a/app/core/src/main/resources/settings.yml.template b/app/core/src/main/resources/settings.yml.template index d1da10ce6..235c3c7eb 100644 --- a/app/core/src/main/resources/settings.yml.template +++ b/app/core/src/main/resources/settings.yml.template @@ -102,7 +102,6 @@ premium: primary: gpt-5-nano # Default lightweight model fallback: gpt-5-mini # Escalation model for complex prompts embedding: text-embedding-3-small # Embedding model for vector store usage - apiKey: '' # Provide your OpenAI-compatible API key (or use SPRING_AI_OPENAI_API_KEY env var) rag: chunkSizeTokens: 512 # Token window used when chunking text chunkOverlapTokens: 128 # Overlap between successive chunks diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/config/ChatbotAiConfiguration.java b/app/proprietary/src/main/java/stirling/software/proprietary/config/ChatbotAiConfiguration.java deleted file mode 100644 index 02f4bc6fd..000000000 --- a/app/proprietary/src/main/java/stirling/software/proprietary/config/ChatbotAiConfiguration.java +++ /dev/null @@ -1,52 +0,0 @@ -package stirling.software.proprietary.config; - -import org.springframework.ai.chat.model.ChatModel; -import org.springframework.ai.embedding.EmbeddingModel; -import org.springframework.ai.openai.OpenAiChatModel; -import org.springframework.ai.openai.OpenAiChatOptions; -import org.springframework.ai.openai.OpenAiEmbeddingModel; -import org.springframework.ai.openai.OpenAiEmbeddingOptions; -import org.springframework.ai.openai.api.OpenAiApi; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.util.StringUtils; - -import stirling.software.proprietary.service.chatbot.ChatbotFeatureProperties; -import stirling.software.proprietary.service.chatbot.ChatbotFeatureProperties.ChatbotSettings; - -@Configuration -@ConditionalOnProperty(value = "premium.proFeatures.chatbot.enabled", havingValue = "true") -public class ChatbotAiConfiguration { - - @Bean - public OpenAiApi chatbotOpenAiApi(ChatbotFeatureProperties properties) { - ChatbotSettings settings = properties.current(); - String apiKey = settings.models().apiKey(); - if (!StringUtils.hasText(apiKey)) { - throw new IllegalStateException( - "premium.proFeatures.chatbot.models.apiKey must be set (or provide SPRING_AI_OPENAI_API_KEY)"); - } - return new OpenAiApi(apiKey); - } - - @Bean - public ChatModel chatbotChatModel( - OpenAiApi chatbotOpenAiApi, ChatbotFeatureProperties properties) { - OpenAiChatOptions options = - OpenAiChatOptions.builder() - .withModel(properties.current().models().primary()) - .build(); - return new OpenAiChatModel(chatbotOpenAiApi, options); - } - - @Bean - public EmbeddingModel chatbotEmbeddingModel( - OpenAiApi chatbotOpenAiApi, ChatbotFeatureProperties properties) { - OpenAiEmbeddingOptions options = - OpenAiEmbeddingOptions.builder() - .withModel(properties.current().models().embedding()) - .build(); - return new OpenAiEmbeddingModel(chatbotOpenAiApi, options); - } -} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/controller/ChatbotController.java b/app/proprietary/src/main/java/stirling/software/proprietary/controller/ChatbotController.java index 281523e8e..b099a5dfb 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/controller/ChatbotController.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/controller/ChatbotController.java @@ -3,6 +3,7 @@ package stirling.software.proprietary.controller; import java.util.ArrayList; import java.util.List; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -34,6 +35,7 @@ import stirling.software.proprietary.service.chatbot.exception.ChatbotException; @RequiredArgsConstructor @Slf4j @ConditionalOnProperty(value = "premium.proFeatures.chatbot.enabled", havingValue = "true") +@ConditionalOnBean(ChatbotService.class) public class ChatbotController { private final ChatbotService chatbotService; diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/controller/ChatbotExceptionHandler.java b/app/proprietary/src/main/java/stirling/software/proprietary/controller/ChatbotExceptionHandler.java index f3f172b50..1d9b091bb 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/controller/ChatbotExceptionHandler.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/controller/ChatbotExceptionHandler.java @@ -3,6 +3,7 @@ package stirling.software.proprietary.controller; import java.time.Instant; import java.util.Map; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -11,12 +12,14 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import lombok.extern.slf4j.Slf4j; +import stirling.software.proprietary.service.chatbot.ChatbotService; import stirling.software.proprietary.service.chatbot.exception.ChatbotException; import stirling.software.proprietary.service.chatbot.exception.NoTextDetectedException; @RestControllerAdvice(assignableTypes = ChatbotController.class) @Slf4j @ConditionalOnProperty(value = "premium.proFeatures.chatbot.enabled", havingValue = "true") +@ConditionalOnBean(ChatbotService.class) public class ChatbotExceptionHandler { @ExceptionHandler(NoTextDetectedException.class) diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java index 76c8dec30..28ab87ee1 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/SecurityConfiguration.java @@ -11,6 +11,7 @@ import org.springframework.context.annotation.DependsOn; import org.springframework.context.annotation.Lazy; import org.springframework.security.authentication.ProviderManager; import org.springframework.security.authentication.dao.DaoAuthenticationProvider; +import org.springframework.security.config.Customizer; import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotConversationService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotConversationService.java index f77126f12..136664fa9 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotConversationService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotConversationService.java @@ -16,6 +16,7 @@ import org.springframework.ai.chat.model.ChatResponse; import org.springframework.ai.chat.prompt.Prompt; import org.springframework.ai.openai.OpenAiChatModel; import org.springframework.ai.openai.OpenAiChatOptions; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -38,6 +39,7 @@ import stirling.software.proprietary.service.chatbot.exception.ChatbotException; @Slf4j @RequiredArgsConstructor @ConditionalOnProperty(value = "premium.proFeatures.chatbot.enabled", havingValue = "true") +@ConditionalOnBean(ChatModel.class) public class ChatbotConversationService { private final ChatModel chatModel; diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotFeatureProperties.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotFeatureProperties.java index 46c6b71bf..653254822 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotFeatureProperties.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotFeatureProperties.java @@ -2,9 +2,7 @@ package stirling.software.proprietary.service.chatbot; import java.util.Optional; -import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; import stirling.software.common.model.ApplicationProperties; import stirling.software.common.model.ApplicationProperties.Premium; @@ -15,23 +13,13 @@ import stirling.software.common.model.ApplicationProperties.Premium.ProFeatures. public class ChatbotFeatureProperties { private final ApplicationProperties applicationProperties; - private final Environment environment; - public ChatbotFeatureProperties( - ApplicationProperties applicationProperties, Environment environment) { + public ChatbotFeatureProperties(ApplicationProperties applicationProperties) { this.applicationProperties = applicationProperties; - this.environment = environment; } public ChatbotSettings current() { Chatbot chatbot = resolveChatbot(); - String configuredKey = Optional.ofNullable(chatbot.getModels().getApiKey()).orElse(""); - String fallbackKey = environment.getProperty("spring.ai.openai.api-key", ""); - String apiKey = - StringUtils.hasText(configuredKey) - ? configuredKey - : (StringUtils.hasText(fallbackKey) ? fallbackKey : ""); - return new ChatbotSettings( chatbot.isEnabled(), chatbot.isAlphaWarning(), @@ -40,8 +28,7 @@ public class ChatbotFeatureProperties { new ChatbotSettings.ModelSettings( chatbot.getModels().getPrimary(), chatbot.getModels().getFallback(), - chatbot.getModels().getEmbedding(), - apiKey), + chatbot.getModels().getEmbedding()), new ChatbotSettings.RagSettings( chatbot.getRag().getChunkSizeTokens(), chatbot.getRag().getChunkOverlapTokens(), @@ -77,8 +64,7 @@ public class ChatbotFeatureProperties { OcrSettings ocr, AuditSettings audit) { - public record ModelSettings( - String primary, String fallback, String embedding, String apiKey) {} + public record ModelSettings(String primary, String fallback, String embedding) {} public record RagSettings(int chunkSizeTokens, int chunkOverlapTokens, int topK) {} diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotIngestionService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotIngestionService.java index bb79eaad1..63bd24587 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotIngestionService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotIngestionService.java @@ -8,6 +8,7 @@ import java.util.UUID; import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.embedding.EmbeddingResponse; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @@ -24,6 +25,7 @@ import stirling.software.proprietary.service.chatbot.exception.NoTextDetectedExc @Service @ConditionalOnProperty(value = "premium.proFeatures.chatbot.enabled", havingValue = "true") +@ConditionalOnBean(EmbeddingModel.class) @Slf4j @RequiredArgsConstructor public class ChatbotIngestionService { diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotRetrievalService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotRetrievalService.java index d82a0b4fb..c2030633b 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotRetrievalService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotRetrievalService.java @@ -7,6 +7,7 @@ import java.util.Optional; import org.springframework.ai.embedding.EmbeddingModel; import org.springframework.ai.embedding.EmbeddingResponse; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @@ -23,6 +24,7 @@ import stirling.software.proprietary.service.chatbot.exception.ChatbotException; @RequiredArgsConstructor @Slf4j @ConditionalOnProperty(value = "premium.proFeatures.chatbot.enabled", havingValue = "true") +@ConditionalOnBean(EmbeddingModel.class) public class ChatbotRetrievalService { private final ChatbotCacheService cacheService; diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotService.java b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotService.java index fa910fe56..1a2205037 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotService.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/service/chatbot/ChatbotService.java @@ -3,6 +3,7 @@ package stirling.software.proprietary.service.chatbot; import java.util.HashMap; import java.util.Map; +import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; @@ -18,6 +19,7 @@ import stirling.software.proprietary.service.chatbot.exception.ChatbotException; @Service @ConditionalOnProperty(value = "premium.proFeatures.chatbot.enabled", havingValue = "true") +@ConditionalOnBean({ChatbotIngestionService.class, ChatbotConversationService.class}) @Slf4j @RequiredArgsConstructor public class ChatbotService { diff --git a/app/proprietary/src/main/resources/application-proprietary.properties b/app/proprietary/src/main/resources/application-proprietary.properties index e69de29bb..dc6b3d73e 100644 --- a/app/proprietary/src/main/resources/application-proprietary.properties +++ b/app/proprietary/src/main/resources/application-proprietary.properties @@ -0,0 +1,2 @@ +spring.ai.openai.enabled=true +spring.ai.openai.api-key=your-proprietary-api-key-here diff --git a/devGuide/DeveloperGuide.md b/devGuide/DeveloperGuide.md index 6ecbfe343..13ae500dd 100644 --- a/devGuide/DeveloperGuide.md +++ b/devGuide/DeveloperGuide.md @@ -591,4 +591,4 @@ Remember, never hard-code text in your templates or Java code. Always use transl ### Chatbot Feature Configuration - The chatbot backend is disabled unless `premium.proFeatures.chatbot.enabled` is true in `configs/settings.yml`. -- Provide an OpenAI-compatible key through `premium.proFeatures.chatbot.models.apiKey` (preferred) or the `SPRING_AI_OPENAI_API_KEY` environment variable. Without a key the app still boots, but the chatbot beans are not created. +- Provide an OpenAI-compatible key via `SPRING_AI_OPENAI_API_KEY` (or `spring.ai.openai.api-key`) and set `spring.ai.openai.enabled=true` when you want chatbot beans to load. Leaving this property disabled allows the rest of Stirling-PDF to run without AI credentials. diff --git a/frontend/src/core/services/apiClient.ts b/frontend/src/core/services/apiClient.ts index 57869fc36..735f91925 100644 --- a/frontend/src/core/services/apiClient.ts +++ b/frontend/src/core/services/apiClient.ts @@ -7,6 +7,9 @@ import { getApiBaseUrl } from '@app/services/apiClientConfig'; const apiClient = axios.create({ baseURL: getApiBaseUrl(), responseType: 'json', + withCredentials: true, + xsrfCookieName: 'XSRF-TOKEN', + xsrfHeaderName: 'X-XSRF-TOKEN', }); // Setup interceptors (core does nothing, proprietary adds JWT auth) diff --git a/frontend/src/core/services/apiClientSetup.ts b/frontend/src/core/services/apiClientSetup.ts index f7de717a3..557e6db5f 100644 --- a/frontend/src/core/services/apiClientSetup.ts +++ b/frontend/src/core/services/apiClientSetup.ts @@ -1,5 +1,21 @@ import { AxiosInstance } from 'axios'; -export function setupApiInterceptors(_client: AxiosInstance): void { - // Core version: no interceptors to add +function readXsrfToken(): string | undefined { + const match = document.cookie + .split(';') + .map((cookie) => cookie.trim()) + .find((cookie) => cookie.startsWith('XSRF-TOKEN=')); + + return match ? decodeURIComponent(match.substring('XSRF-TOKEN='.length)) : undefined; +} + +export function setupApiInterceptors(client: AxiosInstance): void { + client.interceptors.request.use((config) => { + const token = readXsrfToken(); + if (token) { + config.headers = config.headers ?? {}; + config.headers['X-XSRF-TOKEN'] = token; + } + return config; + }); }