@@ -51,14 +51,20 @@ interface ChatContextValue {
5151
5252const ChatContext = createContext < ChatContextValue | undefined > ( undefined ) ;
5353
54- export function ChatProvider ( { children } : PropsWithChildren ) {
55- const [ messages , setMessages ] = useState < ( Message | DraftMessage ) [ ] > ( [ ] ) ;
56- const [ loading , setLoading ] = useState < boolean > ( false ) ;
57- const [ serverStatus , setServerStatus ] = useState < ServerStatus > ( "unknown" ) ;
58- const eventSourceRef = useRef < EventSource | null > ( null ) ;
54+ const useAgentAPIUrl = ( ) : string => {
5955 const searchParams = useSearchParams ( ) ;
60- // NOTE(cian): We use '../../' here to construct the agent API URL relative
61- // to the current window location. Let's say the app is hosted on a subpath
56+ const paramsUrl = searchParams . get ( "url" ) ;
57+ if ( paramsUrl ) {
58+ return paramsUrl ;
59+ }
60+ const basePath = process . env . NEXT_PUBLIC_BASE_PATH ;
61+ if ( ! basePath ) {
62+ throw new Error (
63+ "agentAPIUrl is not set. Please set the url query parameter to the URL of the AgentAPI or the NEXT_PUBLIC_BASE_PATH environment variable."
64+ ) ;
65+ }
66+ // NOTE(cian): We use '../' here to construct the agent API URL relative
67+ // to the chat's location. Let's say the app is hosted on a subpath
6268 // `/@admin/workspace.agent/apps/ccw/`. When you visit this URL you get
6369 // redirected to `/@admin/workspace.agent/apps/ccw/chat/embed`. This serves
6470 // this React application, but it needs to know where the agent API is hosted.
@@ -67,8 +73,25 @@ export function ChatProvider({ children }: PropsWithChildren) {
6773 // `window.location.origin` but this assumes that the application owns the
6874 // entire origin.
6975 // See: https://github.com/coder/coder/issues/18779#issuecomment-3133290494 for more context.
70- const defaultAgentAPIURL = new URL ( "../../" , window . location . href ) . toString ( ) ;
71- const agentAPIUrl = searchParams . get ( "url" ) || defaultAgentAPIURL ;
76+ let chatURL : string = new URL ( basePath , window . location . origin ) . toString ( ) ;
77+ // NOTE: trailing slashes and relative URLs are tricky.
78+ // https://developer.mozilla.org/en-US/docs/Web/API/URL_API/Resolving_relative_references#current_directory_relative
79+ if ( ! chatURL . endsWith ( "/" ) ) {
80+ chatURL += "/" ;
81+ }
82+ const agentAPIURL = new URL ( ".." , chatURL ) . toString ( ) ;
83+ if ( agentAPIURL . endsWith ( "/" ) ) {
84+ return agentAPIURL . slice ( 0 , - 1 ) ;
85+ }
86+ return agentAPIURL ;
87+ } ;
88+
89+ export function ChatProvider ( { children } : PropsWithChildren ) {
90+ const [ messages , setMessages ] = useState < ( Message | DraftMessage ) [ ] > ( [ ] ) ;
91+ const [ loading , setLoading ] = useState < boolean > ( false ) ;
92+ const [ serverStatus , setServerStatus ] = useState < ServerStatus > ( "unknown" ) ;
93+ const eventSourceRef = useRef < EventSource | null > ( null ) ;
94+ const agentAPIUrl = useAgentAPIUrl ( ) ;
7295
7396 // Set up SSE connection to the events endpoint
7497 useEffect ( ( ) => {
0 commit comments