@@ -15,6 +15,7 @@ import {
15
15
import { MessageContent } from "./components/MessageContent" ;
16
16
import { ThinkingBlock } from "./components/ThinkingBlock" ;
17
17
import { ConversationList } from "./components/ConversationList" ;
18
+ import { MentionInput } from "./components/MentionInput" ;
18
19
import { GeminiLogo } from "./components/GeminiLogo" ;
19
20
import { PiebaldLogo } from "./components/PiebaldLogo" ;
20
21
import { type ToolCall , type ToolCallResult } from "./utils/toolCallParser" ;
@@ -26,6 +27,8 @@ import {
26
27
AlertTriangle ,
27
28
UserRound ,
28
29
FolderKanban ,
30
+ Send ,
31
+ ImagePlus ,
29
32
} from "lucide-react" ;
30
33
import "./index.css" ;
31
34
import { ToolCallDisplay } from "./components/ToolCallDisplay" ;
@@ -168,6 +171,10 @@ export const api = {
168
171
return webApi . list_projects ( args ) as Promise < T > ;
169
172
case "get_project_discussions" :
170
173
return webApi . get_project_discussions ( args ) as Promise < T > ;
174
+ case "list_enriched_projects" :
175
+ return webApi . list_projects_enriched ( ) as Promise < T > ;
176
+ case "start_session" :
177
+ return webApi . start_session ( args . sessionId , args . workingDirectory , args . model ) as Promise < T > ;
171
178
default :
172
179
throw new Error ( `Unknown command: ${ command } ` ) ;
173
180
}
@@ -289,6 +296,8 @@ interface ConversationContextType {
289
296
_mentions : unknown [ ]
290
297
) => void ;
291
298
handleSendMessage : ( e : React . FormEvent ) => Promise < void > ;
299
+ selectedModel : string ;
300
+ startNewConversation : ( title : string , workingDirectory ?: string ) => Promise < string > ;
292
301
}
293
302
294
303
const ConversationContext = createContext < ConversationContextType | undefined > ( undefined ) ;
@@ -762,6 +771,36 @@ function RootLayout() {
762
771
}
763
772
} ;
764
773
774
+ const startNewConversation = async ( title : string , workingDirectory ?: string ) : Promise < string > => {
775
+ const convId = Date . now ( ) . toString ( ) ;
776
+
777
+ // Create conversation in UI
778
+ const newConversation : Conversation = {
779
+ id : convId ,
780
+ title,
781
+ messages : [ ] ,
782
+ lastUpdated : new Date ( ) ,
783
+ isStreaming : false ,
784
+ } ;
785
+
786
+ setConversations ( ( prev ) => [ newConversation , ...prev ] ) ;
787
+ setActiveConversation ( convId ) ;
788
+
789
+ // Initialize session with working directory if provided
790
+ if ( workingDirectory ) {
791
+ await api . invoke ( "start_session" , {
792
+ sessionId : convId ,
793
+ workingDirectory,
794
+ model : selectedModel
795
+ } ) ;
796
+ }
797
+
798
+ // Set up event listeners
799
+ await setupEventListenerForConversation ( convId ) ;
800
+
801
+ return convId ;
802
+ } ;
803
+
765
804
766
805
767
806
const handleModelChange = ( model : string ) => {
@@ -915,9 +954,112 @@ function RootLayout() {
915
954
cliIOLogs,
916
955
handleInputChange,
917
956
handleSendMessage,
957
+ selectedModel,
958
+ startNewConversation,
918
959
} } >
919
960
< Outlet />
920
961
</ ConversationContext . Provider >
962
+
963
+ { /* Message Input Bar - Only show when there's an active conversation */ }
964
+ { activeConversation && (
965
+ < div className = "sticky bottom-0 bg-white dark:bg-neutral-900 flex items-center border-t border-gray-200 dark:border-neutral-700" >
966
+ < div className = "px-6 py-2 w-full" >
967
+ < div className = "mx-auto" >
968
+ < form
969
+ className = "flex gap-3 items-end"
970
+ onSubmit = { handleSendMessage }
971
+ >
972
+ < div className = "flex-1 relative" >
973
+ < MentionInput
974
+ value = { input }
975
+ onChange = { handleInputChange }
976
+ placeholder = {
977
+ isCliInstalled === false
978
+ ? "Gemini CLI not found"
979
+ : "Type @ to mention files..."
980
+ }
981
+ disabled = { isCliInstalled === false }
982
+ className = "h-9 w-full"
983
+ onKeyDown = { ( e ) => {
984
+ if ( e . key === "Enter" && ! e . shiftKey ) {
985
+ e . preventDefault ( ) ;
986
+ handleSendMessage ( e ) ;
987
+ }
988
+ } }
989
+ />
990
+ </ div >
991
+ < Button
992
+ type = "submit"
993
+ disabled = {
994
+ isCliInstalled === false ||
995
+ ! input . trim ( )
996
+ }
997
+ size = "icon"
998
+ >
999
+ < Send />
1000
+ </ Button >
1001
+ < Dialog >
1002
+ < DialogTrigger asChild >
1003
+ < Button
1004
+ type = "button"
1005
+ size = "icon"
1006
+ variant = "outline"
1007
+ title = "View CLI Input/Output"
1008
+ >
1009
+ < Info className = "h-4 w-4" />
1010
+ </ Button >
1011
+ </ DialogTrigger >
1012
+ < DialogContent className = "max-w-4xl max-h-[80vh] overflow-y-auto" >
1013
+ < DialogHeader >
1014
+ < DialogTitle > CLI Input/Output Logs</ DialogTitle >
1015
+ </ DialogHeader >
1016
+ < div className = "space-y-2 max-h-96 overflow-y-auto" >
1017
+ { cliIOLogs . map ( ( log , index ) => (
1018
+ < div key = { index } className = "border rounded p-2" >
1019
+ < div className = "flex items-center gap-2 mb-1" >
1020
+ < span
1021
+ className = { `text-xs font-mono px-2 py-1 rounded ${
1022
+ log . type === "input"
1023
+ ? "bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200"
1024
+ : "bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200"
1025
+ } `}
1026
+ >
1027
+ { log . type === "input" ? "IN" : "OUT" }
1028
+ </ span >
1029
+ < span className = "text-xs text-muted-foreground" >
1030
+ { log . timestamp . toLocaleTimeString ( ) }
1031
+ </ span >
1032
+ < span className = "text-xs text-muted-foreground font-mono" >
1033
+ { log . conversationId }
1034
+ </ span >
1035
+ </ div >
1036
+ < pre className = "text-xs whitespace-pre-wrap break-all font-mono bg-white dark:bg-gray-900 p-2 rounded border" >
1037
+ { log . data }
1038
+ </ pre >
1039
+ </ div >
1040
+ ) ) }
1041
+ { cliIOLogs . length === 0 && (
1042
+ < div className = "text-center text-muted-foreground py-8" >
1043
+ No CLI I/O logs available yet. Start a conversation
1044
+ to see the raw communication.
1045
+ </ div >
1046
+ ) }
1047
+ </ div >
1048
+ </ DialogContent >
1049
+ </ Dialog >
1050
+ < Button
1051
+ type = "button"
1052
+ disabled = { true }
1053
+ size = "icon"
1054
+ variant = "outline"
1055
+ >
1056
+ < ImagePlus />
1057
+ </ Button >
1058
+ </ form >
1059
+ </ div >
1060
+ </ div >
1061
+ </ div >
1062
+ ) }
921
1063
</ div >
922
1064
</ div >
923
1065
@@ -1284,112 +1426,6 @@ function HomeDashboard() {
1284
1426
</ div >
1285
1427
</ div >
1286
1428
) }
1287
-
1288
- { /* <div className="sticky bottom-0 bg-white dark:bg-neutral-900 flex items-center border-t border-gray-200 dark:border-neutral-700">
1289
- <div className="px-6 py-2 w-full">
1290
- <div className="mx-auto">
1291
- <form
1292
- className="flex gap-3 items-end"
1293
- onSubmit={handleSendMessage}
1294
- >
1295
- <div className="flex-1 relative">
1296
- <MentionInput
1297
- value={input}
1298
- onChange={handleInputChange}
1299
- placeholder={
1300
- isCliInstalled === false
1301
- ? "Gemini CLI not found"
1302
- : "Type @ to mention files..."
1303
- }
1304
- disabled={isCliInstalled === false}
1305
- className="h-9 w-full"
1306
- onKeyDown={(e) => {
1307
- if (e.key === "Enter" && !e.shiftKey) {
1308
- e.preventDefault();
1309
- handleSendMessage(e);
1310
- }
1311
- }}
1312
- />
1313
- </div>
1314
- <Button
1315
- type="submit"
1316
- disabled={
1317
- isCliInstalled === false ||
1318
- !input.trim()
1319
- }
1320
- size="icon"
1321
- >
1322
- <Send />
1323
- </Button>
1324
- <Dialog>
1325
- <DialogTrigger asChild>
1326
- <Button
1327
- type="button"
1328
- size="icon"
1329
- variant="outline"
1330
- title="View CLI Input/Output"
1331
- >
1332
- <Info />
1333
- </Button>
1334
- </DialogTrigger>
1335
- <DialogContent className="max-w-4xl max-h-[80vh] overflow-y-auto">
1336
- <DialogHeader>
1337
- <DialogTitle>CLI Input/Output Log</DialogTitle>
1338
- </DialogHeader>
1339
- <div className="space-y-2 max-h-[60vh] overflow-y-auto">
1340
- {cliIOLogs
1341
- .map((log, index) => (
1342
- <div
1343
- key={index}
1344
- className={`p-3 rounded-lg border ${
1345
- log.type === "input"
1346
- ? "bg-blue-50 dark:bg-blue-950/30 border-blue-200 dark:border-blue-800"
1347
- : "bg-green-50 dark:bg-green-950/30 border-green-200 dark:border-green-800"
1348
- }`}
1349
- >
1350
- <div className="flex items-center gap-2 mb-2">
1351
- <span
1352
- className={`text-xs font-mono px-2 py-1 rounded ${
1353
- log.type === "input"
1354
- ? "bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200"
1355
- : "bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200"
1356
- }`}
1357
- >
1358
- {log.type === "input" ? "IN" : "OUT"}
1359
- </span>
1360
- <span className="text-xs text-muted-foreground">
1361
- {log.timestamp.toLocaleTimeString()}
1362
- </span>
1363
- <span className="text-xs text-muted-foreground font-mono">
1364
- {log.conversationId}
1365
- </span>
1366
- </div>
1367
- <pre className="text-xs whitespace-pre-wrap break-all font-mono bg-white dark:bg-gray-900 p-2 rounded border">
1368
- {log.data}
1369
- </pre>
1370
- </div>
1371
- ))}
1372
- {cliIOLogs.length === 0 && (
1373
- <div className="text-center text-muted-foreground py-8">
1374
- No CLI I/O logs available yet. Start a conversation
1375
- to see the raw communication.
1376
- </div>
1377
- )}
1378
- </div>
1379
- </DialogContent>
1380
- </Dialog>
1381
- <Button
1382
- type="button"
1383
- disabled={true}
1384
- size="icon"
1385
- variant="outline"
1386
- >
1387
- <ImagePlus />
1388
- </Button>
1389
- </form>
1390
- </div>
1391
- </div>
1392
- </div> */ }
1393
1429
</ >
1394
1430
) ;
1395
1431
}
0 commit comments