@@ -39,7 +39,9 @@ import {
3939 Maximize2 ,
4040 Minimize2 ,
4141 Volume2 ,
42- Square
42+ Square ,
43+ Folder ,
44+ ChevronDown
4345} from "lucide-react" ;
4446import RecordRTC from "recordrtc" ;
4547import { useQueryClient } from "@tanstack/react-query" ;
@@ -69,9 +71,11 @@ import {
6971 DropdownMenu ,
7072 DropdownMenuContent ,
7173 DropdownMenuItem ,
74+ DropdownMenuSeparator ,
7275 DropdownMenuTrigger
7376} from "@/components/ui/dropdown-menu" ;
7477import { isTauri } from "@/utils/platform" ;
78+ import { useProjects } from "@/state/useProjects" ;
7579import type {
7680 InputTextContent ,
7781 OutputTextContent ,
@@ -875,6 +879,7 @@ export function UnifiedChat() {
875879 const isTauriEnv = isTauri ( ) ;
876880 const queryClient = useQueryClient ( ) ;
877881 const { playbackError, clearPlaybackError } = useTTS ( ) ;
882+ const { projects } = useProjects ( ) ;
878883
879884 // Track chatId from URL - use state so we can update it
880885 const [ chatId , setChatId ] = useState < string | undefined > ( ( ) => {
@@ -926,6 +931,16 @@ export function UnifiedChat() {
926931 return localStorage . getItem ( "webSearchEnabled" ) === "true" ;
927932 } ) ;
928933
934+ // Project selector for new chats
935+ const [ selectedProjectId , setSelectedProjectId ] = useState < string | null > ( ( ) => {
936+ const params = new URLSearchParams ( window . location . search ) ;
937+ return params . get ( "project_id" ) || null ;
938+ } ) ;
939+ const selectedProject = useMemo (
940+ ( ) => projects . find ( ( p ) => p . id === selectedProjectId ) || null ,
941+ [ projects , selectedProjectId ]
942+ ) ;
943+
929944 // Fullscreen mode for power users - persisted in localStorage
930945 const [ isFullscreen , setIsFullscreen ] = useState ( ( ) => {
931946 return localStorage . getItem ( "chatFullscreen" ) === "true" ;
@@ -1144,6 +1159,16 @@ export function UnifiedChat() {
11441159 clearAllAttachments ( ) ;
11451160 // Reset scroll tracking
11461161 prevMessageCountRef . current = 0 ;
1162+ // Check if navigating from a project page
1163+ const projectParam = new URLSearchParams ( window . location . search ) . get ( "project_id" ) ;
1164+ setSelectedProjectId ( projectParam || null ) ;
1165+ if ( projectParam ) {
1166+ // Clean up the URL param
1167+ const cleaned = new URLSearchParams ( window . location . search ) ;
1168+ cleaned . delete ( "project_id" ) ;
1169+ const newUrl = cleaned . toString ( ) ? `/?${ cleaned . toString ( ) } ` : "/" ;
1170+ window . history . replaceState ( null , "" , newUrl ) ;
1171+ }
11471172 } ;
11481173
11491174 // Handle conversation selection from sidebar
@@ -2347,15 +2372,11 @@ export function UnifiedChat() {
23472372 // Trigger sidebar refresh to show the new conversation
23482373 window . dispatchEvent ( new Event ( "conversationcreated" ) ) ;
23492374
2350- // Auto-assign to project if project_id param is present
2351- const projectParam = new URLSearchParams ( window . location . search ) . get ( "project_id" ) ;
2352- if ( projectParam ) {
2375+ // Auto-assign to project if one is selected
2376+ if ( selectedProjectId ) {
23532377 window . dispatchEvent ( new CustomEvent ( "assignchattoproject" , {
2354- detail : { chatId : conversationId , projectId : projectParam }
2378+ detail : { chatId : conversationId , projectId : selectedProjectId }
23552379 } ) ) ;
2356- const cleaned = new URLSearchParams ( window . location . search ) ;
2357- cleaned . delete ( "project_id" ) ;
2358- window . history . replaceState ( null , "" , `${ window . location . pathname } ?${ cleaned . toString ( ) } ` ) ;
23592380 }
23602381 }
23612382
@@ -3011,6 +3032,55 @@ export function UnifiedChat() {
30113032 </ DropdownMenuItem >
30123033 </ DropdownMenuContent >
30133034 </ DropdownMenu >
3035+
3036+ { /* Project selector — only for new chats */ }
3037+ { ! chatId && (
3038+ < DropdownMenu >
3039+ < DropdownMenuTrigger asChild >
3040+ < Button
3041+ type = "button"
3042+ variant = "ghost"
3043+ size = "sm"
3044+ className = { `p-0 ${ selectedProject ? "h-8 px-2 gap-1.5" : "h-8 w-8" } ` }
3045+ >
3046+ < Folder
3047+ className = { `h-4 w-4 flex-shrink-0 ${
3048+ selectedProject ? "text-purple-500" : "text-muted-foreground"
3049+ } `}
3050+ />
3051+ { selectedProject && (
3052+ < >
3053+ < span className = "text-xs truncate max-w-[120px]" >
3054+ { selectedProject . name }
3055+ </ span >
3056+ < ChevronDown className = "h-3 w-3 flex-shrink-0 text-muted-foreground" />
3057+ </ >
3058+ ) }
3059+ </ Button >
3060+ </ DropdownMenuTrigger >
3061+ < DropdownMenuContent align = "start" >
3062+ < DropdownMenuItem onClick = { ( ) => setSelectedProjectId ( null ) } >
3063+ < Check
3064+ className = { `mr-2 h-4 w-4 ${ ! selectedProjectId ? "opacity-100" : "opacity-0" } ` }
3065+ />
3066+ < span > No project</ span >
3067+ </ DropdownMenuItem >
3068+ { projects . length > 0 && < DropdownMenuSeparator /> }
3069+ { projects . map ( ( project ) => (
3070+ < DropdownMenuItem
3071+ key = { project . id }
3072+ onClick = { ( ) => setSelectedProjectId ( project . id ) }
3073+ >
3074+ < Check
3075+ className = { `mr-2 h-4 w-4 ${ selectedProjectId === project . id ? "opacity-100" : "opacity-0" } ` }
3076+ />
3077+ < Folder className = "mr-2 h-4 w-4 flex-shrink-0" />
3078+ < span className = "truncate" > { project . name } </ span >
3079+ </ DropdownMenuItem >
3080+ ) ) }
3081+ </ DropdownMenuContent >
3082+ </ DropdownMenu >
3083+ ) }
30143084 </ div >
30153085
30163086 < div className = "flex items-center gap-2" >
@@ -3254,6 +3324,55 @@ export function UnifiedChat() {
32543324 </ DropdownMenuItem >
32553325 </ DropdownMenuContent >
32563326 </ DropdownMenu >
3327+
3328+ { /* Project selector — only for new chats */ }
3329+ { ! chatId && (
3330+ < DropdownMenu >
3331+ < DropdownMenuTrigger asChild >
3332+ < Button
3333+ type = "button"
3334+ variant = "ghost"
3335+ size = "sm"
3336+ className = { `p-0 ${ selectedProject ? "h-8 px-2 gap-1.5" : "h-8 w-8" } ` }
3337+ >
3338+ < Folder
3339+ className = { `h-4 w-4 flex-shrink-0 ${
3340+ selectedProject ? "text-purple-500" : "text-muted-foreground"
3341+ } `}
3342+ />
3343+ { selectedProject && (
3344+ < >
3345+ < span className = "text-xs truncate max-w-[120px]" >
3346+ { selectedProject . name }
3347+ </ span >
3348+ < ChevronDown className = "h-3 w-3 flex-shrink-0 text-muted-foreground" />
3349+ </ >
3350+ ) }
3351+ </ Button >
3352+ </ DropdownMenuTrigger >
3353+ < DropdownMenuContent align = "start" >
3354+ < DropdownMenuItem onClick = { ( ) => setSelectedProjectId ( null ) } >
3355+ < Check
3356+ className = { `mr-2 h-4 w-4 ${ ! selectedProjectId ? "opacity-100" : "opacity-0" } ` }
3357+ />
3358+ < span > No project</ span >
3359+ </ DropdownMenuItem >
3360+ { projects . length > 0 && < DropdownMenuSeparator /> }
3361+ { projects . map ( ( project ) => (
3362+ < DropdownMenuItem
3363+ key = { project . id }
3364+ onClick = { ( ) => setSelectedProjectId ( project . id ) }
3365+ >
3366+ < Check
3367+ className = { `mr-2 h-4 w-4 ${ selectedProjectId === project . id ? "opacity-100" : "opacity-0" } ` }
3368+ />
3369+ < Folder className = "mr-2 h-4 w-4 flex-shrink-0" />
3370+ < span className = "truncate" > { project . name } </ span >
3371+ </ DropdownMenuItem >
3372+ ) ) }
3373+ </ DropdownMenuContent >
3374+ </ DropdownMenu >
3375+ ) }
32573376 </ div >
32583377
32593378 < div className = "flex items-center gap-2" >
0 commit comments