Files
multica/packages/views/editor/extensions/submit-shortcut.ts
Naiyuan Qing a744cd4f45 feat(chat): redesign state, header, and unread tracking
State management
- Pending task / live timeline are now Query-cache single source;
  Zustand mirror removed (fixes duplicate assistant render caused by
  the invalidate→refetch race window)
- WS subscriptions moved from ChatWindow to global useRealtimeSync so
  pending state survives minimize and refresh
- New GET /chat/sessions/:id/pending-task to recover live state on mount
- Drafts persisted per-session (was per-workspace)

Unread tracking
- Migration 040: chat_session.unread_since (event-driven; old chats
  stay clean — no mass backfill)
- POST /chat/sessions/:id/read clears unread; broadcasts
  chat:session_read so other devices sync
- New GET /chat/pending-tasks aggregate for the FAB
- ChatFab: brand-color impulse animation while running, brand-dot
  badge of unread session count
- ChatWindow auto-marks read when user is viewing the session

Header redesign
- Two independent dropdowns: agent (avatar + name + My/Others
  grouping) at the input bottom-left; session (title + agent avatar)
  in the header
- ⊕ new-chat button replaces the old + and history buttons
- Session dropdown lists all sessions across agents with avatars
- Empty state: 3 clickable starter prompts that send immediately
- Mention link renderer falls through to default span on null —
  fixes @member/@agent/@all silently disappearing app-wide
- User messages render through Markdown
- Enter submits in chat input only (with IME guard + codeBlock skip);
  bubble menu hidden in chat

Misc
- Partial index on agent_task_queue for fast pending-task lookup
- 2 new storage keys added to clearWorkspaceStorage
- useMarkChatSessionRead has onError rollback
- chat.* namespace logs across store, mutations, components, realtime

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 18:21:11 +08:00

37 lines
1.3 KiB
TypeScript

import { Extension } from "@tiptap/core";
/**
* `onSubmit` must return true when it actually handled the event and false
* when there's no submit handler wired up. That lets us fall through to the
* default Enter behaviour — inserting a newline — when appropriate.
*
* `submitOnEnter` — when true, bare Enter also submits (chat-style). When
* false, only Mod-Enter submits and bare Enter keeps its default (newline).
*/
export function createSubmitExtension(
onSubmit: () => boolean,
{ submitOnEnter }: { submitOnEnter: boolean },
) {
return Extension.create({
name: "submitShortcut",
addKeyboardShortcuts() {
const shortcuts: Record<string, () => boolean> = {
"Mod-Enter": () => onSubmit(),
};
if (submitOnEnter) {
shortcuts.Enter = () => {
const editor = this.editor;
// IME guard — never submit while composing a multi-key input
// (Chinese pinyin, Japanese kana, etc). `view.composing` is set
// by ProseMirror between compositionstart and compositionend.
if (editor.view.composing) return false;
// Let Enter insert a newline inside a code block.
if (editor.isActive("codeBlock")) return false;
return onSubmit();
};
}
return shortcuts;
},
});
}