1515import javafx .collections .ObservableList ;
1616import javafx .geometry .Insets ;
1717import javafx .geometry .Orientation ;
18- import javafx .geometry .Pos ;
1918import javafx .scene .control .*;
2019import javafx .scene .layout .*;
2120import javafx .stage .FileChooser ;
2726 */
2827public class ChatController extends BorderPane {
2928
29+ private static final int SETTINGS_TAB = 0 ;
30+ private static final int LOG_TAB = 1 ;
31+
3032 private final ObservableList <ChatMessage > messages = FXCollections .observableArrayList ();
3133 private final ListView <ChatMessage > messageListView ;
3234 private final TextField inputField ;
3335 private final Button sendButton ;
34- private final ComboBox <CacheType > cacheComboBox ;
35- private final Label statusLabel ;
36- private final Label providerLabel ;
37- private final Label modelLabel ;
38- private final Label loadedPdfLabel ;
39- private Button uploadPdfButton ;
4036 private final PDFViewerPanel pdfViewer ;
37+ private final VerticalTabPane tabPane ;
38+ private final SettingsPanel settingsPanel ;
4139 private final LogPanel logPanel ;
4240 private final EventLogger eventLogger ;
4341 private File currentPdfFile ;
@@ -54,20 +52,33 @@ public ChatController() {
5452 eventLogger = new EventLogger ();
5553 eventLogger .info (Category .SYSTEM , "Application starting..." );
5654
57- // Initialize fields first
58- uploadPdfButton = new Button ("Upload PDF" );
59- uploadPdfButton .setOnAction (e -> uploadPdf ());
60-
6155 // PDF viewer panel (left side)
6256 pdfViewer = new PDFViewerPanel ();
57+ pdfViewer .setPrefWidth (450 );
58+ pdfViewer .setMinWidth (300 );
59+
60+ // Settings panel content
61+ settingsPanel = new SettingsPanel ();
62+ settingsPanel .getUploadPdfButton ().setOnAction (e -> uploadPdf ());
6363
64- // Log panel (bottom)
64+ // Log panel content
6565 logPanel = new LogPanel ();
6666 logPanel .setEventLogger (eventLogger );
67- pdfViewer .setPrefWidth (450 );
68- pdfViewer .setMinWidth (300 );
6967
70- // Message list (center/right side)
68+ // Vertical tab pane (right side)
69+ tabPane = new VerticalTabPane ();
70+ tabPane .addTab ("⚙" , "Settings" , settingsPanel );
71+ tabPane .addTab ("📋" , "Event Log" , logPanel );
72+
73+ // Update log badge when count changes
74+ logPanel .setCountListener (count -> {
75+ tabPane .setBadge (LOG_TAB , count > 0 ? String .valueOf (count ) : null );
76+ });
77+
78+ // Start with settings tab open
79+ tabPane .selectTab (SETTINGS_TAB );
80+
81+ // Message list (center)
7182 messageListView = new ListView <>(messages );
7283 messageListView .setCellFactory (
7384 lv ->
@@ -95,74 +106,6 @@ protected void updateItem(ChatMessage item, boolean empty) {
95106
96107 setCenter (splitPane );
97108
98- // Right control panel
99- VBox rightPanel = new VBox (15 );
100- rightPanel .setPadding (new Insets (15 ));
101- rightPanel .getStyleClass ().add ("control-panel" );
102- rightPanel .setPrefWidth (300 );
103- rightPanel .setMinWidth (300 );
104-
105- // Configuration section
106- Label configTitle = new Label ("Configuration" );
107- configTitle .getStyleClass ().add ("section-title" );
108-
109- providerLabel = new Label ("Provider: " + config .getLLMProvider ().getDisplayName ());
110- providerLabel .getStyleClass ().add ("info-label" );
111-
112- modelLabel = new Label ("Model: " + config .getLLMConfig ().model ());
113- modelLabel .getStyleClass ().add ("info-label" );
114-
115- loadedPdfLabel = new Label ("No PDF loaded" );
116- loadedPdfLabel .getStyleClass ().add ("info-label" );
117- loadedPdfLabel .setWrapText (true );
118-
119- Separator configSeparator = new Separator ();
120-
121- // Cache section
122- Label cacheTitle = new Label ("Semantic Cache" );
123- cacheTitle .getStyleClass ().add ("section-title" );
124-
125- cacheComboBox = new ComboBox <>();
126- cacheComboBox .getItems ().addAll (CacheType .values ());
127- cacheComboBox .setValue (config .isLangCacheEnabled () ? CacheType .LANGCACHE : CacheType .NONE );
128- cacheComboBox .getStyleClass ().add ("cache-combobox" );
129- cacheComboBox .setMaxWidth (Double .MAX_VALUE );
130-
131- Label cacheInfo = new Label ("Select caching mode:\n • No Cache: Always call LLM\n • Local: Redis-based cache\n • LangCache: Cloud cache" );
132- cacheInfo .getStyleClass ().add ("info-text" );
133- cacheInfo .setWrapText (true );
134-
135- Separator cacheSeparator = new Separator ();
136-
137- // Actions section
138- Label actionsTitle = new Label ("Actions" );
139- actionsTitle .getStyleClass ().add ("section-title" );
140-
141- uploadPdfButton .getStyleClass ().add ("action-button" );
142- uploadPdfButton .setMaxWidth (Double .MAX_VALUE );
143-
144- Separator actionsSeparator = new Separator ();
145-
146- // Status section
147- Label statusTitle = new Label ("Status" );
148- statusTitle .getStyleClass ().add ("section-title" );
149-
150- statusLabel = new Label ("Ready" );
151- statusLabel .getStyleClass ().add ("status-label" );
152- statusLabel .setWrapText (true );
153-
154- // Spacer to push status to bottom
155- Region spacer = new Region ();
156- VBox .setVgrow (spacer , Priority .ALWAYS );
157-
158- rightPanel .getChildren ().addAll (
159- configTitle , providerLabel , modelLabel , loadedPdfLabel , configSeparator ,
160- cacheTitle , cacheComboBox , cacheInfo , cacheSeparator ,
161- actionsTitle , uploadPdfButton , actionsSeparator ,
162- spacer ,
163- statusTitle , statusLabel
164- );
165-
166109 // Bottom input area
167110 HBox inputSection = new HBox (10 );
168111 inputSection .setPadding (new Insets (10 ));
@@ -182,10 +125,8 @@ protected void updateItem(ChatMessage item, boolean empty) {
182125 inputSection .getChildren ().addAll (inputField , sendButton );
183126 setBottom (inputSection );
184127
185- // Right side: control panel + log panel (far right edge)
186- HBox rightContainer = new HBox ();
187- rightContainer .getChildren ().addAll (rightPanel , logPanel );
188- setRight (rightContainer );
128+ // Right side: vertical tab pane
129+ setRight (tabPane );
189130
190131 // Add welcome message
191132 addSystemMessage ("Welcome to RedisVL Multimodal RAG Demo!\n \n Configuration loaded from application.properties:\n • Provider: " +
@@ -218,9 +159,9 @@ private void sendMessage() {
218159
219160 // Disable input while processing
220161 setInputEnabled (false );
221- statusLabel . setText ("Thinking..." );
162+ settingsPanel . setStatus ("Thinking..." );
222163
223- CacheType cacheType = cacheComboBox .getValue ();
164+ CacheType cacheType = settingsPanel . getCacheComboBox () .getValue ();
224165 eventLogger .info (Category .LLM , "Query: \" " + truncate (userInput , 50 ) + "\" (cache: " + cacheType + ")" );
225166
226167 // Process in background
@@ -255,7 +196,7 @@ private void sendMessage() {
255196 Platform .runLater (
256197 () -> {
257198 setInputEnabled (true );
258- statusLabel . setText ("Ready" );
199+ settingsPanel . setStatus ("Ready" );
259200 inputField .requestFocus ();
260201 });
261202 }
@@ -304,7 +245,7 @@ private void uploadPdf() {
304245 File file = fileChooser .showOpenDialog (getScene ().getWindow ());
305246 if (file != null ) {
306247 currentPdfFile = file ;
307- statusLabel . setText ("Processing PDF: " + file .getName ());
248+ settingsPanel . setStatus ("Processing PDF: " + file .getName ());
308249 addSystemMessage ("Processing PDF: " + file .getName () + "..." );
309250 eventLogger .info (Category .PDF , "Loading PDF: " + file .getName ());
310251
@@ -338,15 +279,15 @@ private void uploadPdf() {
338279 String .format (
339280 "PDF processed successfully. Indexed %d chunks. You can now ask questions about the document." ,
340281 chunks ));
341- loadedPdfLabel . setText ( "Loaded: " + file .getName () + " (" + chunks + " chunks)" );
342- statusLabel . setText ("Ready" );
282+ settingsPanel . setLoadedPdf ( file .getName () + " (" + chunks + " chunks)" );
283+ settingsPanel . setStatus ("Ready" );
343284 eventLogger .info (Category .PDF , "Indexed " + chunks + " chunks in " + elapsed + "ms" );
344285 });
345286 } catch (Exception e ) {
346287 Platform .runLater (
347288 () -> {
348289 addSystemMessage ("Error processing PDF: " + e .getMessage ());
349- statusLabel . setText ("Ready" );
290+ settingsPanel . setStatus ("Ready" );
350291 eventLogger .error (Category .PDF , "Indexing failed: " + e .getMessage ());
351292 });
352293 }
@@ -391,21 +332,10 @@ private void scrollToBottom() {
391332 */
392333 public void setServiceFactory (com .redis .vl .demo .rag .service .ServiceFactory serviceFactory ) {
393334 this .serviceFactory = serviceFactory ;
394- updateStatusLabel ("Connected to Redis. Select provider and enter API key to start." );
335+ settingsPanel . setStatus ("Connected to Redis. Upload a PDF to start." );
395336 eventLogger .info (Category .SYSTEM , "Connected to Redis" );
396337 }
397338
398- /**
399- * Updates the status label text.
400- *
401- * @param text Status text
402- */
403- private void updateStatusLabel (String text ) {
404- if (statusLabel != null ) {
405- Platform .runLater (() -> statusLabel .setText (text ));
406- }
407- }
408-
409339 /**
410340 * Shutdown executor and close resources.
411341 */
0 commit comments