-
-
Notifications
You must be signed in to change notification settings - Fork 6
Add image attachment support to follow-up chat input #518
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?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,5 @@ | ||
| DATABASE_URL="postgresql://user:password@host:port/db" | ||
| ENABLE_AUTH=false | ||
| OPENAI_API_KEY=sk-dummy | ||
| NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN=pk.dummy | ||
| MAPBOX_ACCESS_TOKEN=pk.dummy | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,67 +1,164 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||
| 'use client' | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| import { useState } from 'react' | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { useState, useRef, ChangeEvent } from 'react' | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { Button } from './ui/button' | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { Input } from './ui/input' | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { useActions, useUIState } from 'ai/rsc' | ||||||||||||||||||||||||||||||||||||||||||||||||
| import type { AI } from '@/app/actions' | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { UserMessage } from './user-message' | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { ArrowRight } from 'lucide-react' | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { ArrowRight, Paperclip, X } from 'lucide-react' | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { useMapData } from './map/map-data-context' | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { nanoid } from '@/lib/utils' | ||||||||||||||||||||||||||||||||||||||||||||||||
| import { nanoid, cn } from '@/lib/utils' | ||||||||||||||||||||||||||||||||||||||||||||||||
| import Textarea from 'react-textarea-autosize' | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| export function FollowupPanel() { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const [input, setInput] = useState('') | ||||||||||||||||||||||||||||||||||||||||||||||||
| const [selectedFile, setSelectedFile] = useState<File | null>(null) | ||||||||||||||||||||||||||||||||||||||||||||||||
| const { submit } = useActions() | ||||||||||||||||||||||||||||||||||||||||||||||||
| const [, setMessages] = useUIState<typeof AI>() | ||||||||||||||||||||||||||||||||||||||||||||||||
| const { mapData } = useMapData() | ||||||||||||||||||||||||||||||||||||||||||||||||
| const fileInputRef = useRef<HTMLInputElement>(null) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| const file = e.target.files?.[0] | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (file) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (file.size > 10 * 1024 * 1024) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| alert('File size must be less than 10MB') | ||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| setSelectedFile(file) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+21
to
+29
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. Using Given this component already uses SuggestionReplace Example:
Reply with "@CharlieHelps yes please" if you’d like me to add a commit implementing an inline error message. |
||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+21
to
+30
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. File input is not reset when validation fails, preventing re-selection of the same file. When a file exceeds 10MB and the early return fires (line 26), Proposed fix const handleFileChange = (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0]
if (file) {
if (file.size > 10 * 1024 * 1024) {
alert('File size must be less than 10MB')
+ if (fileInputRef.current) {
+ fileInputRef.current.value = ''
+ }
return
}
setSelectedFile(file)
}
}📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const handleAttachmentClick = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| fileInputRef.current?.click() | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const clearAttachment = () => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| setSelectedFile(null) | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (fileInputRef.current) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| fileInputRef.current.value = '' | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const handleSubmit = async (event: React.FormEvent<HTMLFormElement>) => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| event.preventDefault() | ||||||||||||||||||||||||||||||||||||||||||||||||
| const formData = new FormData() | ||||||||||||||||||||||||||||||||||||||||||||||||
| formData.append("input", input) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| if (!input.trim() && !selectedFile) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const content: ({ type: 'text'; text: string } | { type: 'image'; image: string })[] = [] | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (input) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| content.push({ type: 'text', text: input }) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (selectedFile && selectedFile.type.startsWith('image/')) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| content.push({ | ||||||||||||||||||||||||||||||||||||||||||||||||
| type: 'image', | ||||||||||||||||||||||||||||||||||||||||||||||||
| image: URL.createObjectURL(selectedFile) | ||||||||||||||||||||||||||||||||||||||||||||||||
| }) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+50
to
+59
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. Text files are accepted but silently dropped from the user message content. The Either remove Also applies to: 91-96 🤖 Prompt for AI Agents
Comment on lines
+54
to
+59
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. Memory leak: Each call to Also, this blob URL is ephemeral and only valid in the current browsing context—it won't survive a page refresh or be meaningful to the server. Is this intentional for a local preview only? Proposed fixTrack the object URL and revoke it on cleanup: + const [previewUrl, setPreviewUrl] = useState<string | null>(null)
const clearAttachment = () => {
+ if (previewUrl) {
+ URL.revokeObjectURL(previewUrl)
+ setPreviewUrl(null)
+ }
setSelectedFile(null)
if (fileInputRef.current) {
fileInputRef.current.value = ''
}
}Then generate the URL when the file is selected (in 🤖 Prompt for AI Agents
Comment on lines
+50
to
+59
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, note that sending an SuggestionTrack and revoke the object URL. Example:
Reply with "@CharlieHelps yes please" if you’d like me to add a commit with this change. |
||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const userMessage = { | ||||||||||||||||||||||||||||||||||||||||||||||||
| id: nanoid(), | ||||||||||||||||||||||||||||||||||||||||||||||||
| isGenerating: false, | ||||||||||||||||||||||||||||||||||||||||||||||||
| component: <UserMessage content={input} /> | ||||||||||||||||||||||||||||||||||||||||||||||||
| component: <UserMessage content={content.length > 0 ? content : input} /> | ||||||||||||||||||||||||||||||||||||||||||||||||
|
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.
If Proposed fixGuard submission against this state, or always include a text entry even if empty (if the server expects it): - component: <UserMessage content={content.length > 0 ? content : input} />
+ component: <UserMessage content={content.length > 0 ? content : input || '[Attachment]'} />Though the better fix is to address the text/plain acceptance issue noted above. 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+46
to
66
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. The optimistic
This is a correctness/UI consistency issue in the optimistic rendering logic. SuggestionAlign the optimistic
Reply with "@CharlieHelps yes please" if you’d like me to add a commit applying this refactor. |
||||||||||||||||||||||||||||||||||||||||||||||||
| // Include drawn features in the form data | ||||||||||||||||||||||||||||||||||||||||||||||||
| const formData = new FormData() | ||||||||||||||||||||||||||||||||||||||||||||||||
| formData.append('input', input) | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (selectedFile) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| formData.append('file', selectedFile) | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+21
to
+71
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.
Related: SuggestionMake attachment handling consistent: either (a) restrict to images only in both Option A (images only):
Option B (support text/plain):
Reply with "@CharlieHelps yes please" if you’d like me to add a commit implementing the chosen option. |
||||||||||||||||||||||||||||||||||||||||||||||||
| formData.append('drawnFeatures', JSON.stringify(mapData.drawnFeatures || [])) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| setInput('') | ||||||||||||||||||||||||||||||||||||||||||||||||
| clearAttachment() | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| const responseMessage = await submit(formData) | ||||||||||||||||||||||||||||||||||||||||||||||||
| setMessages(currentMessages => [ | ||||||||||||||||||||||||||||||||||||||||||||||||
| ...currentMessages, | ||||||||||||||||||||||||||||||||||||||||||||||||
| userMessage, | ||||||||||||||||||||||||||||||||||||||||||||||||
| responseMessage | ||||||||||||||||||||||||||||||||||||||||||||||||
| ]) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| setInput('') | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||||||||||||||||
| <form | ||||||||||||||||||||||||||||||||||||||||||||||||
| onSubmit={handleSubmit} | ||||||||||||||||||||||||||||||||||||||||||||||||
| className="relative flex items-center space-x-1" | ||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||
| <Input | ||||||||||||||||||||||||||||||||||||||||||||||||
| type="text" | ||||||||||||||||||||||||||||||||||||||||||||||||
| name="input" | ||||||||||||||||||||||||||||||||||||||||||||||||
| placeholder="Explore" | ||||||||||||||||||||||||||||||||||||||||||||||||
| value={input} | ||||||||||||||||||||||||||||||||||||||||||||||||
| className="pr-14 h-12" | ||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={e => setInput(e.target.value)} | ||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||||||||||
| type="submit" | ||||||||||||||||||||||||||||||||||||||||||||||||
| size={'icon'} | ||||||||||||||||||||||||||||||||||||||||||||||||
| disabled={input.length === 0} | ||||||||||||||||||||||||||||||||||||||||||||||||
| variant={'ghost'} | ||||||||||||||||||||||||||||||||||||||||||||||||
| className="absolute right-1" | ||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex flex-col w-full"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <form | ||||||||||||||||||||||||||||||||||||||||||||||||
| onSubmit={handleSubmit} | ||||||||||||||||||||||||||||||||||||||||||||||||
| className="relative flex items-start w-full" | ||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||
| <ArrowRight size={20} /> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </form> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <input | ||||||||||||||||||||||||||||||||||||||||||||||||
| type="file" | ||||||||||||||||||||||||||||||||||||||||||||||||
| ref={fileInputRef} | ||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={handleFileChange} | ||||||||||||||||||||||||||||||||||||||||||||||||
| className="hidden" | ||||||||||||||||||||||||||||||||||||||||||||||||
| accept="text/plain,image/png,image/jpeg,image/webp" | ||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||||||||||||||||||||||||||||
| variant={'ghost'} | ||||||||||||||||||||||||||||||||||||||||||||||||
| size={'icon'} | ||||||||||||||||||||||||||||||||||||||||||||||||
| className="absolute left-3 top-1/2 transform -translate-y-1/2" | ||||||||||||||||||||||||||||||||||||||||||||||||
| onClick={handleAttachmentClick} | ||||||||||||||||||||||||||||||||||||||||||||||||
| data-testid="followup-attachment-button" | ||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||
| <Paperclip size={20} /> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <Textarea | ||||||||||||||||||||||||||||||||||||||||||||||||
| name="input" | ||||||||||||||||||||||||||||||||||||||||||||||||
| rows={1} | ||||||||||||||||||||||||||||||||||||||||||||||||
| maxRows={5} | ||||||||||||||||||||||||||||||||||||||||||||||||
| tabIndex={0} | ||||||||||||||||||||||||||||||||||||||||||||||||
| placeholder="Explore" | ||||||||||||||||||||||||||||||||||||||||||||||||
| spellCheck={false} | ||||||||||||||||||||||||||||||||||||||||||||||||
| value={input} | ||||||||||||||||||||||||||||||||||||||||||||||||
| data-testid="followup-input" | ||||||||||||||||||||||||||||||||||||||||||||||||
| className={cn( | ||||||||||||||||||||||||||||||||||||||||||||||||
| 'resize-none w-full min-h-12 rounded-fill border border-input pl-14 pr-14 pt-3 pb-1 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 bg-muted' | ||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||
| onChange={e => setInput(e.target.value)} | ||||||||||||||||||||||||||||||||||||||||||||||||
| onKeyDown={e => { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||
| e.key === 'Enter' && | ||||||||||||||||||||||||||||||||||||||||||||||||
| !e.shiftKey && | ||||||||||||||||||||||||||||||||||||||||||||||||
| !e.nativeEvent.isComposing | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (input.trim().length === 0 && !selectedFile) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| e.preventDefault() | ||||||||||||||||||||||||||||||||||||||||||||||||
| return | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+127
to
+130
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. Inconsistent empty-input checks: Line 127 uses Proposed fix- disabled={input.length === 0 && !selectedFile}
+ disabled={input.trim().length === 0 && !selectedFile}Also applies to: 142-142 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
| e.preventDefault() | ||||||||||||||||||||||||||||||||||||||||||||||||
| const form = e.currentTarget.form | ||||||||||||||||||||||||||||||||||||||||||||||||
| if (form) { | ||||||||||||||||||||||||||||||||||||||||||||||||
| form.requestSubmit() | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
| }} | ||||||||||||||||||||||||||||||||||||||||||||||||
| /> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <Button | ||||||||||||||||||||||||||||||||||||||||||||||||
| type="submit" | ||||||||||||||||||||||||||||||||||||||||||||||||
| size={'icon'} | ||||||||||||||||||||||||||||||||||||||||||||||||
| disabled={input.length === 0 && !selectedFile} | ||||||||||||||||||||||||||||||||||||||||||||||||
| variant={'ghost'} | ||||||||||||||||||||||||||||||||||||||||||||||||
| data-testid="followup-submit" | ||||||||||||||||||||||||||||||||||||||||||||||||
| className="absolute right-3 top-1/2 transform -translate-y-1/2" | ||||||||||||||||||||||||||||||||||||||||||||||||
| > | ||||||||||||||||||||||||||||||||||||||||||||||||
| <ArrowRight size={20} /> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </form> | ||||||||||||||||||||||||||||||||||||||||||||||||
| {selectedFile && ( | ||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="w-full mt-2"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <div className="flex items-center justify-between p-2 bg-muted rounded-lg"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <span className="text-sm text-muted-foreground truncate max-w-xs"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| {selectedFile.name} | ||||||||||||||||||||||||||||||||||||||||||||||||
| </span> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <Button variant="ghost" size="icon" onClick={clearAttachment} data-testid="followup-clear-attachment-button"> | ||||||||||||||||||||||||||||||||||||||||||||||||
| <X size={16} /> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </Button> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||
| )} | ||||||||||||||||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
85
to
163
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 Significant code duplication with Per the PR objectives and the relevant snippets, 🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||
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.
🧩 Analysis chain
🏁 Script executed:
Repository: QueueLab/QCX
Length of output: 283
Remove
.envfrom version control—it is currently tracked in git..envis committed to the repository and not protected by.gitignore(which only excludes.env*.localfiles). This means secrets are permanently stored in git history. Even ifsk-dummyand other values are currently placeholders, this pattern enables accidental commits of real credentials.Rename this file to
.env.example, add.envto.gitignore, and update documentation to instruct developers to copy.env.exampleto.envlocally. Additionally, theENABLE_AUTH=falsesetting should not ship to any environment that reaches production.🧰 Tools
🪛 dotenv-linter (4.0.0)
[warning] 1-1: [QuoteCharacter] The value has quote characters (', ")
(QuoteCharacter)
[warning] 4-4: [UnorderedKey] The NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN key should go before the OPENAI_API_KEY key
(UnorderedKey)
[warning] 5-5: [UnorderedKey] The MAPBOX_ACCESS_TOKEN key should go before the NEXT_PUBLIC_MAPBOX_ACCESS_TOKEN key
(UnorderedKey)
🤖 Prompt for AI Agents