fix: what the whole-PR review and Copilot found

- A redirect followed for a custom base URL also drops the key headers of
  providers that do not use Authorization (x-api-key, x-goog-api-key,
  api-key) when it goes to another origin.
- A second Enter or click while a message is being prepared (attachments
  read, diagram exported) no longer sends it twice.
- The admin Test on the deployment's own endpoints (EdgeOne, the server's
  keyless Ollama, an address on the server's network) counts toward the
  quota like a chat; the chat and the Test share one rule for it. The
  Test of an Azure entry set up by AZURE_RESOURCE_NAME only goes where chat
  goes.
- EdgeOne's function is called at the site root again, as on main: EdgeOne
  serves edge functions there, outside Next's base path.
- MCP History: the state before a write is kept unless the browser saved
  no change of the user's since the last server write (draw.io's sync copy
  of it adds no entry), and the dedupe compares the exact text again, so a
  change of page size or other settings only is its own version.
- MCP: an edit keeps untouched labels as draw.io shows them (a literal line
  break in an attribute is a space); a new document of empty pages the
  user named is auto-saved; load_diagram reads only regular files, so a
  pipe cannot hold up the other write tools; the preview does not load
  back its own push still on its way (an undo made meanwhile is saved).
- Two overlapping saves of a new chat no longer reload the canvas from the
  older copy.
- At most three screenshot checks per user turn, passed or failed, as
  documented.
- Desktop: the main window navigates only within the app (draw.io stays in
  its frame); a presets file that is not JSON and cannot be moved aside is
  not overwritten.
- A last self-closing cell with a raw "<" in a value is not taken for cut
  off output.
- README: Material Design shapes load their icons from fonts.gstatic.com.
This commit is contained in:
dayuan.jiang
2026-10-05 21:19:32 +09:00
parent 69c7896002
commit b8b851fc5b
29 changed files with 476 additions and 90 deletions
+22 -7
View File
@@ -277,6 +277,7 @@ export default function ChatPanel({
// Set by Stop until the user sends the next message
const stoppedRef = useRef(false)
const preparingSendRef = useRef(false)
// Presses of Stop: a check that began before one still knows of it after
// the next message clears stoppedRef
const stopCountRef = useRef(0)
@@ -524,9 +525,10 @@ export default function ChatPanel({
// Track last synced session ID to detect external changes (e.g., URL back/forward)
const lastSyncedSessionIdRef = useRef<string | null>(null)
// Messages array from our latest save. A session holding this exact array was
// created by our own save, so it must not be treated as an external switch.
const lastSavedMessagesRef = useRef<unknown[] | null>(null)
// Message arrays of our own saves. A session holding one of them was
// created by our own save, so it must not be treated as an external
// switch (with two saves of a new chat at once, the first creates it).
const savedMessagesRef = useRef(new WeakSet<object>())
// Helper: Sync UI state with session data (eliminates duplication)
// Track message IDs that are being loaded from session (to skip animations/scroll)
@@ -594,7 +596,7 @@ export default function ChatPanel({
}
}
const messages = sanitizeMessages(messagesRef.current)
lastSavedMessagesRef.current = messages
savedMessagesRef.current.add(messages)
return {
messages,
xmlSnapshots: Array.from(xmlSnapshotsRef.current.entries()),
@@ -651,7 +653,7 @@ export default function ChatPanel({
// Our own save created this session; the UI already shows its content
const isOwnNewSession =
newSession?.messages === lastSavedMessagesRef.current
!!newSession && savedMessagesRef.current.has(newSession.messages)
// Update last synced ID
lastSyncedSessionIdRef.current = newSessionId
@@ -802,8 +804,7 @@ export default function ChatPanel({
)
}, [sessionManager, buildSessionData])
const onFormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
const submitInput = async () => {
const isProcessing = status === "streaming" || status === "submitted"
// Attachments still extracting have no text yet. Template sends call
// requestSubmit() and skip the disabled send button, so check here too.
@@ -901,6 +902,20 @@ export default function ChatPanel({
}
}
const onFormSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault()
// While a send is prepared (attachments read, diagram exported) the
// status is still "ready": a second Enter or click would send the
// message again
if (preparingSendRef.current) return
preparingSendRef.current = true
try {
await submitInput()
} finally {
preparingSendRef.current = false
}
}
// Export the current diagram, snapshot it for this message, and send.
// onSent runs right after sending, so the input empties as the message
// shows in the chat
+1 -1
View File
@@ -422,7 +422,7 @@ export function ModelConfigDialog({
// For EdgeOne, construct baseUrl from current origin
const baseUrl = isEdgeOne
? `${window.location.origin}${getApiEndpoint("/api/edgeai")}`
? `${window.location.origin}/api/edgeai`
: selectedProvider.baseUrl
// Test every model at once; each row updates when its answer arrives