返回 DeepSeek-Reasonix
composerKeyboard.ts
根目录 / desktop / frontend / src / lib / composerKeyboard.ts
1 import { loadCustomShortcuts, matchesShortcut, type ShortcutPlatform } from "./keyboardShortcuts";
2 import { replaceInvocationTextRange, type ComposerInvocation } from "./invocationDisplay";
3
4 export type PromptHistoryDirection = "up" | "down";
5
6 export interface ComposerEnterKeyLike {
7 key: string;
8 ctrlKey?: boolean;
9 metaKey?: boolean;
10 altKey?: boolean;
11 shiftKey?: boolean;
12 }
13
14 // "newline-native" keeps the browser's own line-break insertion (only the
15 // plain Shift+Enter chord, today's proven path); "newline-insert" means the
16 // chord has no native insertion (e.g. Ctrl+Enter) so the composer must insert
17 // the "\n" itself. "none" swallows the chord: an Enter combo matching neither
18 // the send nor the newline shortcut must not fall through to the input, or
19 // native insertion would resurrect the unbound default.
20 export type ComposerEnterAction = "send" | "newline-native" | "newline-insert" | "none";
21
22 export function composerEnterAction(event: ComposerEnterKeyLike, platform: ShortcutPlatform): ComposerEnterAction | null {
23 if (event.key !== "Enter") return null;
24 if (matchesShortcut(event, "composer.newline", platform)) {
25 const nativeInserts = Boolean(event.shiftKey) && !event.ctrlKey && !event.metaKey && !event.altKey;
26 return nativeInserts ? "newline-native" : "newline-insert";
27 }
28 if (matchesShortcut(event, "composer.send", platform)) return "send";
29 // Before composer shortcuts were configurable, every non-Shift Enter chord
30 // submitted. Preserve that default compatibility, but stop applying it as
31 // soon as the user explicitly chooses a send chord.
32 if (!loadCustomShortcuts()["composer.send"] && !event.shiftKey) return "send";
33 // Plain Enter bound to neither chord (send moved to e.g. Ctrl+Enter) still
34 // breaks the line — the WeChat/DingTalk-style layout users expect there.
35 if (!event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey) return "newline-insert";
36 return "none";
37 }
38
39 export interface ComposerSelectionLike {
40 start: number;
41 end: number;
42 afterInvocationId?: string;
43 }
44
45 export function insertComposerNewline(
46 text: string,
47 invocations: ComposerInvocation[],
48 selection: ComposerSelectionLike,
49 ): { text: string; invocations: ComposerInvocation[] } {
50 return replaceInvocationTextRange(
51 text,
52 invocations,
53 selection.start,
54 selection.end,
55 "\n",
56 selection.afterInvocationId,
57 );
58 }
59
60 export interface PromptHistoryKeyLike {
61 key?: string;
62 code?: string;
63 keyCode?: number;
64 which?: number;
65 getModifierState?: (keyArg: string) => boolean;
66 }
67
68 export interface PromptHistoryEligibility {
69 direction: PromptHistoryDirection | null;
70 menuOpen: boolean;
71 composing: boolean;
72 altKey: boolean;
73 ctrlKey: boolean;
74 metaKey: boolean;
75 shiftKey: boolean;
76 fnKey: boolean;
77 value: string;
78 selectionStart: number | null;
79 selectionEnd: number | null;
80 historyIndex: number;
81 }
82
83 export function isFnKeyEvent(event: PromptHistoryKeyLike): boolean {
84 return event.key === "Fn" || event.code === "Fn" || event.getModifierState?.("Fn") === true;
85 }
86
87 export function promptHistoryDirectionFromEvent(event: PromptHistoryKeyLike): PromptHistoryDirection | null {
88 const key = event.key ?? "";
89 const code = event.code ?? "";
90 const keyCode = event.keyCode ?? event.which ?? 0;
91 const useLegacyCode = key === "" || key === "Unidentified";
92
93 if (key === "ArrowUp" || key === "Up" || code === "ArrowUp" || (useLegacyCode && keyCode === 38)) {
94 return "up";
95 }
96 if (key === "ArrowDown" || key === "Down" || code === "ArrowDown" || (useLegacyCode && keyCode === 40)) {
97 return "down";
98 }
99 return null;
100 }
101
102 export function canUsePromptHistory(options: PromptHistoryEligibility): boolean {
103 const {
104 direction,
105 menuOpen,
106 composing,
107 altKey,
108 ctrlKey,
109 metaKey,
110 shiftKey,
111 fnKey,
112 value,
113 selectionStart,
114 selectionEnd,
115 historyIndex,
116 } = options;
117 if (!direction || menuOpen || composing || fnKey || altKey || ctrlKey || metaKey || shiftKey) return false;
118 if (selectionStart === null || selectionEnd === null) return false;
119 if (selectionStart !== selectionEnd) return false;
120
121 if (direction === "up") {
122 return historyIndex >= 0 || selectionStart === 0;
123 }
124
125 return historyIndex >= 0 && selectionEnd === value.length;
126 }
127
127 lines TYPESCRIPT