-
-
Notifications
You must be signed in to change notification settings - Fork 6
autonomous drawing #497
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
autonomous drawing #497
Changes from all commits
bf6043b
9dbf4f0
a83e0d2
e78d174
3970145
e601a48
b6a1992
ef12f5a
b5318d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,13 +12,15 @@ import { useCalendarToggle } from './calendar-toggle-context' | |
| import { CalendarNotepad } from './calendar-notepad' | ||
| import { MapProvider } from './map/map-provider' | ||
| import { useUIState, useAIState } from 'ai/rsc' | ||
| import { AI } from '@/app/actions' | ||
| import { AIMessage } from '@/lib/types' | ||
| import MobileIconsBar from './mobile-icons-bar' | ||
| import { useProfileToggle, ProfileToggleEnum } from "@/components/profile-toggle-context"; | ||
| import { useUsageToggle } from "@/components/usage-toggle-context"; | ||
| import SettingsView from "@/components/settings/settings-view"; | ||
| import { UsageView } from "@/components/usage-view"; | ||
| import { MapDataProvider, useMapData } from './map/map-data-context'; // Add this and useMapData | ||
| import { updateDrawingContext } from '@/lib/actions/chat'; // Import the server action | ||
| import { MapDataProvider, useMapData } from './map/map-data-context'; | ||
| import { updateDrawingContext } from '@/lib/actions/chat'; | ||
| import dynamic from 'next/dynamic' | ||
| import { HeaderSearchButton } from './header-search-button' | ||
|
|
||
|
|
@@ -29,8 +31,8 @@ type ChatProps = { | |
| export function Chat({ id }: ChatProps) { | ||
| const router = useRouter() | ||
| const path = usePathname() | ||
| const [messages] = useUIState() | ||
| const [aiState] = useAIState() | ||
| const [messages] = useUIState<typeof AI>() | ||
| const [aiState] = useAIState<typeof AI>() | ||
| const [isMobile, setIsMobile] = useState(false) | ||
| const { activeView } = useProfileToggle(); | ||
| const { isUsageOpen } = useUsageToggle(); | ||
|
|
@@ -41,6 +43,9 @@ export function Chat({ id }: ChatProps) { | |
| const [suggestions, setSuggestions] = useState<PartialRelated | null>(null) | ||
| const chatPanelRef = useRef<ChatPanelRef>(null); | ||
|
|
||
| // Ref to track the last message ID we refreshed the router for, to prevent infinite loops | ||
| const lastRefreshedMessageIdRef = useRef<string | null>(null); | ||
|
|
||
| const handleAttachment = () => { | ||
| chatPanelRef.current?.handleAttachmentClick(); | ||
| }; | ||
|
|
@@ -54,18 +59,11 @@ export function Chat({ id }: ChatProps) { | |
| }, [messages]) | ||
|
|
||
| useEffect(() => { | ||
| // Check if device is mobile | ||
| const checkMobile = () => { | ||
| setIsMobile(window.innerWidth < 768) | ||
| } | ||
|
|
||
| // Initial check | ||
| checkMobile() | ||
|
|
||
| // Add event listener for window resize | ||
| window.addEventListener('resize', checkMobile) | ||
|
|
||
| // Cleanup | ||
| return () => window.removeEventListener('resize', checkMobile) | ||
| }, []) | ||
|
|
||
|
|
@@ -76,13 +74,16 @@ export function Chat({ id }: ChatProps) { | |
| }, [id, path, messages]) | ||
|
|
||
| useEffect(() => { | ||
| if (aiState.messages[aiState.messages.length - 1]?.type === 'response') { | ||
| // Refresh the page to chat history updates | ||
| router.refresh() | ||
| // Check if there is a 'response' message in the history | ||
| const responseMessage = aiState.messages.findLast((m: AIMessage) => m.type === 'response'); | ||
|
|
||
| if (responseMessage && responseMessage.id !== lastRefreshedMessageIdRef.current) { | ||
| console.log('Chat.tsx: refreshing router for message:', responseMessage.id); | ||
| lastRefreshedMessageIdRef.current = responseMessage.id; | ||
| router.refresh(); | ||
| } | ||
| }, [aiState, router]) | ||
| }, [aiState.messages, router]) | ||
|
Comment on lines
76
to
85
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # Check tsconfig.json for target and lib settings
find . -name "tsconfig.json" -type f | head -5Repository: QueueLab/QCX Length of output: 373 🏁 Script executed: # Look for tsconfig.json in common locations and check target/lib
if [ -f tsconfig.json ]; then
echo "=== tsconfig.json content ==="
cat tsconfig.json
fiRepository: QueueLab/QCX Length of output: 751 🏁 Script executed: # Search for findLast usage in the codebase
rg "findLast" -t ts -t tsx -C 2Repository: QueueLab/QCX Length of output: 83 🏁 Script executed: # Check for polyfills or compatibility layers
rg "polyfill|shim|findLast" --type ts --type tsx --type json | head -30Repository: QueueLab/QCX Length of output: 83 🏁 Script executed: # Search for findLast in TypeScript/React files
rg "findLast" --type tsRepository: QueueLab/QCX Length of output: 158 🏁 Script executed: # Check the specific file and lines mentioned in the review
if [ -f components/chat.tsx ]; then
echo "=== components/chat.tsx lines 70-85 ==="
sed -n '70,85p' components/chat.tsx
fiRepository: QueueLab/QCX Length of output: 685 🏁 Script executed: # Check for any polyfills or compatibility layers in the project
rg "Array.prototype" --type ts | head -10Repository: QueueLab/QCX Length of output: 38
The code at line 76 uses Replace with an alternative that works in ES2018: const responseMessage = [...aiState.messages].reverse().find(m => m.type === 'response');Or upgrade 🤖 Prompt for AI Agents |
||
|
|
||
| // Get mapData to access drawnFeatures | ||
| const { mapData } = useMapData(); | ||
|
|
||
| useEffect(() => { | ||
|
|
@@ -92,10 +93,8 @@ export function Chat({ id }: ChatProps) { | |
| } | ||
| }, [isSubmitting]) | ||
|
|
||
| // useEffect to call the server action when drawnFeatures changes | ||
| useEffect(() => { | ||
| if (id && mapData.drawnFeatures && mapData.cameraState) { | ||
| console.log('Chat.tsx: drawnFeatures changed, calling updateDrawingContext', mapData.drawnFeatures); | ||
| updateDrawingContext(id, { | ||
| drawnFeatures: mapData.drawnFeatures, | ||
| cameraState: mapData.cameraState, | ||
|
|
@@ -112,7 +111,6 @@ export function Chat({ id }: ChatProps) { | |
| onSelect={query => { | ||
| setInput(query) | ||
| setSuggestions(null) | ||
| // Use a small timeout to ensure state update before submission | ||
| setIsSubmitting(true) | ||
| }} | ||
| onClose={() => setSuggestions(null)} | ||
|
|
@@ -122,10 +120,9 @@ export function Chat({ id }: ChatProps) { | |
| ); | ||
| }; | ||
|
|
||
| // Mobile layout | ||
| if (isMobile) { | ||
| return ( | ||
| <MapDataProvider> {/* Add Provider */} | ||
| <MapDataProvider> | ||
| <HeaderSearchButton /> | ||
| <div className="mobile-layout-container"> | ||
| <div className="mobile-map-section"> | ||
|
|
@@ -169,12 +166,10 @@ export function Chat({ id }: ChatProps) { | |
| ); | ||
| } | ||
|
|
||
| // Desktop layout | ||
| return ( | ||
| <MapDataProvider> {/* Add Provider */} | ||
| <MapDataProvider> | ||
| <HeaderSearchButton /> | ||
| <div className="flex justify-start items-start"> | ||
| {/* This is the new div for scrolling */} | ||
| <div className="w-1/2 flex flex-col space-y-3 md:space-y-4 px-8 sm:px-12 pt-16 md:pt-20 pb-4 h-[calc(100vh-0.5in)] overflow-y-auto"> | ||
| {isCalendarOpen ? ( | ||
| <CalendarNotepad chatId={id} /> | ||
|
|
@@ -206,7 +201,7 @@ export function Chat({ id }: ChatProps) { | |
| </div> | ||
| <div | ||
| className="w-1/2 p-4 fixed h-[calc(100vh-0.5in)] top-0 right-0 mt-[0.5in]" | ||
| style={{ zIndex: 10 }} // Added z-index | ||
| style={{ zIndex: 10 }} | ||
| > | ||
| {activeView ? <SettingsView /> : isUsageOpen ? <UsageView /> : <MapProvider />} | ||
| </div> | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -9,7 +9,7 @@ import { useActions, useUIState } from 'ai/rsc' | |
| import { AI } from '@/app/actions' | ||
| import { nanoid } from 'nanoid' | ||
| import { UserMessage } from './user-message' | ||
| import { toast } from 'react-toastify' | ||
| import { toast } from 'sonner' | ||
| import { useSettingsStore } from '@/lib/store/settings' | ||
| import { useMapData } from './map/map-data-context' | ||
|
|
||
|
|
@@ -22,24 +22,46 @@ export function HeaderSearchButton() { | |
| const { map } = useMap() | ||
| const { mapProvider } = useSettingsStore() | ||
| const { mapData } = useMapData() | ||
| // Cast the actions to our defined interface to avoid build errors | ||
| const actions = useActions<typeof AI>() as unknown as HeaderActions | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Double cast This silences any type mismatch between the actual actions returned by Consider properly typing the AI actions or using a type guard instead. 🤖 Prompt for AI Agents |
||
| const [, setMessages] = useUIState<typeof AI>() | ||
| const [isAnalyzing, setIsAnalyzing] = useState(false) | ||
|
|
||
| // Use state for portals to trigger re-renders when they are found | ||
| const [desktopPortal, setDesktopPortal] = useState<HTMLElement | null>(null) | ||
| const [mobilePortal, setMobilePortal] = useState<HTMLElement | null>(null) | ||
|
|
||
| useEffect(() => { | ||
| // Portals can only be used on the client-side after the DOM has mounted | ||
| setDesktopPortal(document.getElementById('header-search-portal')) | ||
| setMobilePortal(document.getElementById('mobile-header-search-portal')) | ||
| // Function to find and set portals | ||
| const findPortals = () => { | ||
| setDesktopPortal(document.getElementById('header-search-portal')) | ||
| setMobilePortal(document.getElementById('mobile-header-search-portal')) | ||
| } | ||
|
|
||
| // Initial check | ||
| findPortals() | ||
|
|
||
| // Use a MutationObserver to detect when portals are added to the DOM | ||
| const observer = new MutationObserver(() => { | ||
| findPortals() | ||
| }) | ||
|
|
||
| observer.observe(document.body, { | ||
| childList: true, | ||
| subtree: true | ||
| }) | ||
|
|
||
| return () => observer.disconnect() | ||
|
Comment on lines
+44
to
+53
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial
This observer fires on every single DOM mutation (any element added/removed anywhere in the page), just to locate two portal Consider a more targeted approach: observe only the direct parent where portals are expected, or simply retry with Example: polling with cleanup useEffect(() => {
- const findPortals = () => {
- setDesktopPortal(document.getElementById('header-search-portal'))
- setMobilePortal(document.getElementById('mobile-header-search-portal'))
- }
-
- findPortals()
-
- const observer = new MutationObserver(() => {
- findPortals()
- })
-
- observer.observe(document.body, {
- childList: true,
- subtree: true
- })
-
- return () => observer.disconnect()
+ let rafId: number;
+ const findPortals = () => {
+ const desktop = document.getElementById('header-search-portal');
+ const mobile = document.getElementById('mobile-header-search-portal');
+ setDesktopPortal(desktop);
+ setMobilePortal(mobile);
+ if (!desktop || !mobile) {
+ rafId = requestAnimationFrame(findPortals);
+ }
+ };
+ findPortals();
+ return () => cancelAnimationFrame(rafId);
}, [])🤖 Prompt for AI Agents |
||
| }, []) | ||
|
|
||
| const handleResolutionSearch = async () => { | ||
| if (mapProvider === 'mapbox' && !map) { | ||
| toast.error('Map is not available yet. Please wait for it to load.') | ||
| return | ||
| } | ||
| if (mapProvider === 'google' && !mapData.cameraState) { | ||
| toast.error('Google Maps state is not available. Try moving the map first.') | ||
| return | ||
| } | ||
| if (!actions) { | ||
| toast.error('Search actions are not available.') | ||
| return | ||
|
|
@@ -102,12 +124,14 @@ export function HeaderSearchButton() { | |
| } | ||
| } | ||
|
|
||
| const isMapAvailable = mapProvider === 'mapbox' ? !!map : !!mapData.cameraState | ||
|
|
||
| const desktopButton = ( | ||
| <Button | ||
| variant="ghost" | ||
| size="icon" | ||
| onClick={handleResolutionSearch} | ||
| disabled={isAnalyzing || !map || !actions} | ||
| disabled={isAnalyzing || !isMapAvailable || !actions} | ||
| title="Analyze current map view" | ||
| > | ||
| {isAnalyzing ? ( | ||
|
|
@@ -119,7 +143,7 @@ export function HeaderSearchButton() { | |
| ) | ||
|
|
||
| const mobileButton = ( | ||
| <Button variant="ghost" size="sm" onClick={handleResolutionSearch} disabled={isAnalyzing || !map || !actions}> | ||
| <Button variant="ghost" size="sm" onClick={handleResolutionSearch} disabled={isAnalyzing || !isMapAvailable || !actions}> | ||
| <Search className="h-4 w-4 mr-2" /> | ||
| Search | ||
| </Button> | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,72 @@ | ||||||||||||||||||||||||||||||||||||||||
| import * as turf from '@turf/turf'; | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| const CircleMode: any = { | ||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Consider adding TypeScript types for better maintainability. The Example type definitionsinterface CircleModeState {
circle: {
id: string;
properties: {
user_isCircle: boolean;
user_center: [number, number] | [];
user_radiusInKm?: number;
};
setCoordinates: (coords: number[][][]) => void;
};
}
interface DrawEvent {
lngLat: { lng: number; lat: number };
}🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
| onSetup: function(opts: any) { | ||||||||||||||||||||||||||||||||||||||||
| const state: any = {}; | ||||||||||||||||||||||||||||||||||||||||
| state.circle = this.newFeature({ | ||||||||||||||||||||||||||||||||||||||||
| type: 'Feature', | ||||||||||||||||||||||||||||||||||||||||
| properties: { | ||||||||||||||||||||||||||||||||||||||||
| user_isCircle: true, | ||||||||||||||||||||||||||||||||||||||||
| user_center: [] | ||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||
| geometry: { | ||||||||||||||||||||||||||||||||||||||||
| type: 'Polygon', | ||||||||||||||||||||||||||||||||||||||||
| coordinates: [[]] | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||
| this.addFeature(state.circle); | ||||||||||||||||||||||||||||||||||||||||
| this.clearSelectedFeatures(); | ||||||||||||||||||||||||||||||||||||||||
| this.updateUIClasses({ mouse: 'add' }); | ||||||||||||||||||||||||||||||||||||||||
| this.activateUIButton('circle'); | ||||||||||||||||||||||||||||||||||||||||
| this.setActionableState({ | ||||||||||||||||||||||||||||||||||||||||
| trash: true | ||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||
| return state; | ||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| onTap: function(state: any, e: any) { | ||||||||||||||||||||||||||||||||||||||||
| this.onClick(state, e); | ||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| onClick: function(state: any, e: any) { | ||||||||||||||||||||||||||||||||||||||||
| if (state.circle.properties.user_center.length === 0) { | ||||||||||||||||||||||||||||||||||||||||
| state.circle.properties.user_center = [e.lngLat.lng, e.lngLat.lat]; | ||||||||||||||||||||||||||||||||||||||||
| // Set initial point-like polygon | ||||||||||||||||||||||||||||||||||||||||
| state.circle.setCoordinates([[ | ||||||||||||||||||||||||||||||||||||||||
| [e.lngLat.lng, e.lngLat.lat], | ||||||||||||||||||||||||||||||||||||||||
| [e.lngLat.lng, e.lngLat.lat], | ||||||||||||||||||||||||||||||||||||||||
| [e.lngLat.lng, e.lngLat.lat], | ||||||||||||||||||||||||||||||||||||||||
| [e.lngLat.lng, e.lngLat.lat] | ||||||||||||||||||||||||||||||||||||||||
| ]]); | ||||||||||||||||||||||||||||||||||||||||
| } else { | ||||||||||||||||||||||||||||||||||||||||
| this.changeMode('simple_select', { featureIds: [state.circle.id] }); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| onMouseMove: function(state: any, e: any) { | ||||||||||||||||||||||||||||||||||||||||
| if (state.circle.properties.user_center.length > 0) { | ||||||||||||||||||||||||||||||||||||||||
| const center = state.circle.properties.user_center; | ||||||||||||||||||||||||||||||||||||||||
| const distance = turf.distance(center, [e.lngLat.lng, e.lngLat.lat], { units: 'kilometers' }); | ||||||||||||||||||||||||||||||||||||||||
| const circle = turf.circle(center, distance, { steps: 64, units: 'kilometers' }); | ||||||||||||||||||||||||||||||||||||||||
| state.circle.setCoordinates(circle.geometry.coordinates); | ||||||||||||||||||||||||||||||||||||||||
| state.circle.properties.user_radiusInKm = distance; | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+46
to
+54
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Zero-radius circle creates degenerate geometry. If the user double-clicks the same point or clicks very close, 🛡️ Proposed fix to enforce minimum radius onMouseMove: function(state: any, e: any) {
if (state.circle.properties.user_center.length > 0) {
const center = state.circle.properties.user_center;
- const distance = turf.distance(center, [e.lngLat.lng, e.lngLat.lat], { units: 'kilometers' });
+ const rawDistance = turf.distance(center, [e.lngLat.lng, e.lngLat.lat], { units: 'kilometers' });
+ const distance = Math.max(rawDistance, 0.01); // Minimum 10m radius
const circle = turf.circle(center, distance, { steps: 64, units: 'kilometers' });
state.circle.setCoordinates(circle.geometry.coordinates);
state.circle.properties.user_radiusInKm = distance;
}
},📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| onKeyUp: function(state: any, e: any) { | ||||||||||||||||||||||||||||||||||||||||
| if (e.keyCode === 27) return this.changeMode('simple_select'); | ||||||||||||||||||||||||||||||||||||||||
| }, | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+56
to
+58
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Missing onStop handler for mode cleanup. The mode doesn't implement Add onStop handler onKeyUp: function(state: any, e: any) {
if (e.keyCode === 27) return this.changeMode('simple_select');
},
+ onStop: function(state: any) {
+ // Clean up incomplete circle if user exits mode without completing
+ if (state.circle && state.circle.properties.user_center.length === 0) {
+ this.deleteFeature(state.circle.id);
+ }
+ },
+
toDisplayFeatures: function(state: any, geojson: any, display: any) {📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| toDisplayFeatures: function(state: any, geojson: any, display: any) { | ||||||||||||||||||||||||||||||||||||||||
| const isActive = geojson.id === state.circle.id; | ||||||||||||||||||||||||||||||||||||||||
| geojson.properties.active = isActive ? 'true' : 'false'; | ||||||||||||||||||||||||||||||||||||||||
| if (!isActive) return display(geojson); | ||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| // Only display if it has a center (and thus coordinates set) | ||||||||||||||||||||||||||||||||||||||||
| if (geojson.properties.user_center && geojson.properties.user_center.length > 0) { | ||||||||||||||||||||||||||||||||||||||||
| display(geojson); | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+1
to
+70
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Also, SuggestionReplace Example (minimal, still pragmatic): import type { MapMouseEvent } from 'mapbox-gl';
import type { DrawCustomMode, DrawFeature } from '@mapbox/mapbox-gl-draw';
type CircleState = {
circle: DrawFeature;
};
type CircleProps = {
user_isCircle: true;
user_center: [number, number] | [];
user_radiusInKm?: number;
};
const CircleMode: DrawCustomMode<CircleState> = {
onSetup() {
const circle = this.newFeature({
type: 'Feature',
properties: { user_isCircle: true, user_center: [] } satisfies CircleProps,
geometry: { type: 'Polygon', coordinates: [[]] }
});
this.addFeature(circle);
this.clearSelectedFeatures();
this.updateUIClasses({ mouse: 'add' });
// Consider removing activateUIButton or ensure it matches the control id
this.setActionableState({ trash: true });
return { circle };
},
onClick(state, e: MapMouseEvent) { /* ... */ },
onMouseMove(state, e: MapMouseEvent) { /* ... */ },
/* ... */
};If you’d like, reply with "@CharlieHelps yes please" and I can add a commit with the typing + safer property handling changes. |
||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||
| export default CircleMode; | ||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove debug
console.logbefore merging.This log fires on every new AI response message and will be noisy in production. Consider removing it or gating it behind a
NODE_ENV === 'development'check.Proposed fix
if (responseMessage && responseMessage.id !== lastRefreshedMessageIdRef.current) { - console.log('Chat.tsx: refreshing router for message:', responseMessage.id); lastRefreshedMessageIdRef.current = responseMessage.id; router.refresh(); }📝 Committable suggestion
🤖 Prompt for AI Agents