gloomberb 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +1 -1
- package/src/app.tsx +184 -69
- package/src/cli/commands/ticker.ts +45 -15
- package/src/cli/helpers.ts +4 -2
- package/src/cli/registry.test.ts +42 -35
- package/src/cli.test.ts +4 -2
- package/src/components/chart/chart-control-hint.tsx +39 -0
- package/src/components/chart/chart-controller.test.ts +110 -0
- package/src/components/chart/chart-controller.ts +364 -2
- package/src/components/chart/chart-data.ts +177 -29
- package/src/components/chart/chart-indicator-selector.tsx +53 -0
- package/src/components/chart/chart-pane-settings.ts +20 -0
- package/src/components/chart/chart-renderer.ts +220 -30
- package/src/components/chart/chart-resolution.test.ts +67 -0
- package/src/components/chart/chart-resolution.ts +74 -4
- package/src/components/chart/chart-scroll.test.ts +69 -0
- package/src/components/chart/chart-scroll.ts +104 -0
- package/src/components/chart/chart-types.ts +12 -0
- package/src/components/chart/chart-viewport.test.ts +5 -1
- package/src/components/chart/chart-viewport.ts +1 -1
- package/src/components/chart/chart.test.ts +249 -10
- package/src/components/chart/comparison-chart-renderer.test.ts +71 -1
- package/src/components/chart/comparison-chart-renderer.ts +40 -3
- package/src/components/chart/comparison-stock-chart.tsx +218 -123
- package/src/components/chart/indicators/bands.test.ts +50 -0
- package/src/components/chart/indicators/bands.ts +29 -0
- package/src/components/chart/indicators/moving-averages.test.ts +54 -0
- package/src/components/chart/indicators/moving-averages.ts +42 -0
- package/src/components/chart/indicators/options.test.ts +32 -0
- package/src/components/chart/indicators/options.ts +107 -0
- package/src/components/chart/indicators/oscillators.test.ts +79 -0
- package/src/components/chart/indicators/oscillators.ts +92 -0
- package/src/components/chart/indicators/types.ts +36 -0
- package/src/components/chart/native/chart-rasterizer.ts +51 -0
- package/src/components/chart/native/kitty.test.ts +68 -0
- package/src/components/chart/stock-chart-renderer-switch.test.tsx +740 -9
- package/src/components/chart/stock-chart.test.ts +239 -0
- package/src/components/chart/stock-chart.tsx +2257 -372
- package/src/components/command-bar/command-bar.test.tsx +337 -5
- package/src/components/command-bar/command-bar.tsx +296 -120
- package/src/components/command-bar/helpers.ts +1 -1
- package/src/components/command-bar/root-shortcuts.ts +33 -4
- package/src/components/command-bar/view-model.test.ts +3 -3
- package/src/components/command-bar/view-model.ts +4 -4
- package/src/components/command-bar/workflow-ops.ts +10 -0
- package/src/components/layout/floating-pane.tsx +3 -1
- package/src/components/layout/header.tsx +5 -4
- package/src/components/layout/pane-sizing.ts +6 -0
- package/src/components/layout/pane.tsx +5 -1
- package/src/components/layout/shell.test.tsx +263 -32
- package/src/components/layout/shell.tsx +10 -6
- package/src/components/layout/status-bar.test.tsx +16 -7
- package/src/components/layout/status-bar.tsx +4 -1
- package/src/components/markdown-editor.tsx +1 -0
- package/src/components/pane-settings-dialog.tsx +13 -152
- package/src/components/pane-template-wizard.tsx +1 -0
- package/src/components/price-selector-dialog.tsx +12 -7
- package/src/components/ticker-badge-text.test.tsx +50 -0
- package/src/components/ticker-badge-text.tsx +17 -2
- package/src/components/ticker-badge.tsx +2 -2
- package/src/components/ticker-list-table.tsx +62 -2
- package/src/components/toggle-list.tsx +15 -2
- package/src/components/ui/data-table.tsx +82 -5
- package/src/components/ui/external-link.tsx +48 -11
- package/src/components/ui/fields.tsx +2 -1
- package/src/components/ui/index.ts +8 -1
- package/src/components/ui/multi-select-chips.tsx +69 -0
- package/src/components/ui/multi-select-dialog.tsx +267 -0
- package/src/components/ui/multi-select.ts +78 -0
- package/src/components/ui/ui.test.tsx +343 -1
- package/src/data/plugin-state-store.ts +30 -19
- package/src/data/resource-store.ts +133 -103
- package/src/data/session-store.ts +19 -12
- package/src/data/sqlite-cache.test.ts +37 -2
- package/src/data/sqlite-database.ts +8 -2
- package/src/data/sqlite-retry.test.ts +63 -0
- package/src/data/sqlite-retry.ts +66 -0
- package/src/data/ticker-store.ts +29 -18
- package/src/index.tsx +6 -1
- package/src/market-data/coordinator.test.ts +114 -5
- package/src/market-data/coordinator.ts +70 -1
- package/src/market-data/hooks.test.tsx +73 -2
- package/src/market-data/hooks.tsx +16 -4
- package/src/notifications/app-notifier.test.ts +177 -0
- package/src/notifications/app-notifier.ts +241 -0
- package/src/pane-settings.test.ts +2 -0
- package/src/plugins/builtin/ai/index.test.tsx +1 -1
- package/src/plugins/builtin/ai/screener-pane.test.tsx +2 -0
- package/src/plugins/builtin/ai/screener-pane.tsx +1 -0
- package/src/plugins/builtin/ai/ticker-context.ts +3 -2
- package/src/plugins/builtin/alerts/alert-engine.test.ts +90 -0
- package/src/plugins/builtin/alerts/alert-engine.ts +55 -0
- package/src/plugins/builtin/alerts/index.test.tsx +357 -0
- package/src/plugins/builtin/alerts/index.tsx +519 -0
- package/src/plugins/builtin/alerts/types.ts +14 -0
- package/src/plugins/builtin/analytics/index.test.tsx +300 -0
- package/src/plugins/builtin/analytics/index.tsx +775 -0
- package/src/plugins/builtin/analytics/metrics.test.ts +125 -0
- package/src/plugins/builtin/analytics/metrics.ts +155 -0
- package/src/plugins/builtin/ask-ai.test.tsx +2 -0
- package/src/plugins/builtin/chat-controller.test.ts +92 -18
- package/src/plugins/builtin/chat-controller.ts +40 -11
- package/src/plugins/builtin/chat.test.tsx +715 -4
- package/src/plugins/builtin/chat.tsx +420 -119
- package/src/plugins/builtin/comparison-chart.test.tsx +97 -57
- package/src/plugins/builtin/comparison-chart.tsx +1 -2
- package/src/plugins/builtin/correlation/compute.test.ts +147 -0
- package/src/plugins/builtin/correlation/compute.ts +115 -0
- package/src/plugins/builtin/correlation/index.tsx +385 -0
- package/src/plugins/builtin/correlation/plugin.test.ts +44 -0
- package/src/plugins/builtin/correlation/settings.test.ts +34 -0
- package/src/plugins/builtin/correlation/settings.ts +95 -0
- package/src/plugins/builtin/debug.tsx +36 -50
- package/src/plugins/builtin/earnings/earnings-cache.test.ts +181 -0
- package/src/plugins/builtin/earnings/earnings-cache.ts +136 -0
- package/src/plugins/builtin/earnings/earnings-data.test.ts +59 -0
- package/src/plugins/builtin/earnings/earnings-data.ts +50 -0
- package/src/plugins/builtin/earnings/index.tsx +337 -0
- package/src/plugins/builtin/econ/calendar-source.test.ts +219 -0
- package/src/plugins/builtin/econ/calendar-source.ts +121 -0
- package/src/plugins/builtin/econ/fred-client.test.ts +63 -0
- package/src/plugins/builtin/econ/fred-client.ts +79 -0
- package/src/plugins/builtin/econ/fred-series-map.ts +88 -0
- package/src/plugins/builtin/econ/index.tsx +999 -0
- package/src/plugins/builtin/econ/types.ts +13 -0
- package/src/plugins/builtin/fred-settings.ts +10 -0
- package/src/plugins/builtin/fx-matrix/index.tsx +178 -0
- package/src/plugins/builtin/fx-matrix/pairs.ts +30 -0
- package/src/plugins/builtin/help.test.tsx +1 -5
- package/src/plugins/builtin/help.tsx +1 -16
- package/src/plugins/builtin/insider/index.tsx +417 -0
- package/src/plugins/builtin/insider/insider-data.test.ts +55 -0
- package/src/plugins/builtin/insider/insider-data.ts +49 -0
- package/src/plugins/builtin/layout-manager.tsx +33 -16
- package/src/plugins/builtin/market-movers/index.tsx +573 -0
- package/src/plugins/builtin/market-movers/screener.test.ts +143 -0
- package/src/plugins/builtin/market-movers/screener.ts +105 -0
- package/src/plugins/builtin/options.tsx +4 -3
- package/src/plugins/builtin/portfolio-list/cli/commands.ts +11 -6
- package/src/plugins/builtin/portfolio-list/command-bar.ts +6 -3
- package/src/plugins/builtin/portfolio-list/header.tsx +3 -2
- package/src/plugins/builtin/portfolio-list/metrics.ts +22 -11
- package/src/plugins/builtin/portfolio-list/pane.tsx +3 -9
- package/src/plugins/builtin/portfolio-list/settings.ts +9 -0
- package/src/plugins/builtin/portfolio-list/table.tsx +3 -0
- package/src/plugins/builtin/portfolio-list.test.tsx +3 -1
- package/src/plugins/builtin/portfolio-metrics.test.ts +13 -1
- package/src/plugins/builtin/sec.tsx +114 -7
- package/src/plugins/builtin/sectors/index.tsx +359 -0
- package/src/plugins/builtin/sectors/sector-data.ts +18 -0
- package/src/plugins/builtin/ticker-detail/chart-tab.tsx +75 -13
- package/src/plugins/builtin/ticker-detail/overview-tab.tsx +316 -80
- package/src/plugins/builtin/ticker-detail/quote-monitor.tsx +4 -6
- package/src/plugins/builtin/ticker-detail-chart-tab-switch.test.tsx +17 -12
- package/src/plugins/builtin/ticker-detail.test.tsx +3 -3
- package/src/plugins/builtin/world-indices/index.tsx +410 -0
- package/src/plugins/builtin/world-indices/indices.ts +52 -0
- package/src/plugins/builtin/yield-curve/index.tsx +242 -0
- package/src/plugins/builtin/yield-curve/treasury-data.test.ts +212 -0
- package/src/plugins/builtin/yield-curve/treasury-data.ts +107 -0
- package/src/plugins/catalog.ts +22 -0
- package/src/plugins/gridlock-notification.ts +15 -0
- package/src/plugins/ibkr/__snapshots__/trade-tab.test.tsx.snap +5 -5
- package/src/plugins/ibkr/gateway-service.test.ts +22 -0
- package/src/plugins/ibkr/gateway-service.ts +6 -0
- package/src/plugins/ibkr/index.test.tsx +1 -1
- package/src/plugins/ibkr/index.tsx +8 -2
- package/src/plugins/ibkr/trade-tab.tsx +23 -5
- package/src/plugins/ibkr/trade-utils.test.ts +1 -1
- package/src/plugins/ibkr/trade-utils.ts +7 -6
- package/src/plugins/ibkr/trading-pane.tsx +8 -7
- package/src/plugins/plugin-runtime.test.tsx +2 -0
- package/src/plugins/plugin-runtime.tsx +10 -0
- package/src/plugins/prediction-markets/controller-data.ts +129 -14
- package/src/plugins/prediction-markets/controller.ts +1 -1
- package/src/plugins/prediction-markets/detail/shared.tsx +7 -6
- package/src/plugins/prediction-markets/index.tsx +5 -1
- package/src/plugins/prediction-markets/pane.test.tsx +78 -0
- package/src/plugins/prediction-markets/pane.tsx +7 -3
- package/src/plugins/prediction-markets/plugin.test.tsx +48 -1
- package/src/plugins/prediction-markets/services/fetch.ts +17 -7
- package/src/plugins/prediction-markets/services/polymarket-adapter.ts +29 -11
- package/src/plugins/prediction-markets/services/polymarket-ws.ts +20 -0
- package/src/plugins/prediction-markets/test-helpers.tsx +22 -5
- package/src/plugins/registry.ts +45 -13
- package/src/sources/provider-router.test.ts +3 -2
- package/src/sources/sec-edgar.ts +13 -1
- package/src/sources/yahoo-finance.ts +75 -1
- package/src/state/app-context-selector.test.tsx +17 -1
- package/src/state/app-context.test.tsx +24 -0
- package/src/state/app-context.tsx +29 -6
- package/src/theme/colors.ts +5 -0
- package/src/theme/themes.ts +25 -0
- package/src/types/config.test.ts +107 -0
- package/src/types/config.ts +30 -1
- package/src/types/data-provider.ts +13 -0
- package/src/types/plugin.ts +35 -1
- package/src/updater.test.ts +2 -0
- package/src/utils/api-client.ts +12 -0
- package/src/utils/format.test.ts +11 -1
- package/src/utils/format.ts +86 -8
- package/src/utils/inline-content-tokenizer.test.ts +22 -0
- package/src/utils/inline-content-tokenizer.ts +76 -0
- package/src/utils/link-tokenizer.test.ts +27 -0
- package/src/utils/link-tokenizer.ts +93 -0
- package/src/utils/market-format.test.ts +109 -0
- package/src/utils/market-format.ts +299 -0
- package/src/utils/market-status.ts +3 -2
- package/src/utils/selection-clipboard.test.ts +43 -0
- package/src/utils/selection-clipboard.ts +88 -0
- package/src/utils/terminal-input-reset.test.ts +50 -0
- package/src/utils/terminal-input-reset.ts +24 -0
- package/src/utils/throttled-fetch.test.ts +145 -0
- package/src/utils/throttled-fetch.ts +212 -0
- package/src/utils/ticker-tokenizer.test.ts +31 -0
- package/src/utils/ticker-tokenizer.ts +7 -42
- package/src/version.ts +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { useState, useEffect, useRef, useCallback } from "react";
|
|
1
|
+
import { Fragment, useState, useEffect, useRef, useCallback } from "react";
|
|
2
2
|
import { useKeyboard } from "@opentui/react";
|
|
3
|
-
import { TextAttributes, type
|
|
3
|
+
import { TextAttributes, type ScrollBoxRenderable, type TextareaRenderable } from "@opentui/core";
|
|
4
4
|
import type { GloomPlugin, PaneProps } from "../../types/plugin";
|
|
5
5
|
import { useAppState } from "../../state/app-context";
|
|
6
6
|
import { useInlineTickers } from "../../state/use-inline-tickers";
|
|
@@ -27,14 +27,11 @@ interface ChatStatusWidgetProps {
|
|
|
27
27
|
controller?: Pick<ChatController, "getSnapshot" | "refreshSession" | "subscribe">;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
function estimateWrappedLineCount(text: string, width: number) {
|
|
31
|
-
const safeWidth = Math.max(width, 1);
|
|
32
|
-
return text.split("\n").reduce((total, line) => (
|
|
33
|
-
total + Math.max(1, Math.ceil(Math.max(line.length, 1) / safeWidth))
|
|
34
|
-
), 0);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
30
|
const MESSAGE_GROUP_THRESHOLD_MS = 5 * 60 * 1000;
|
|
31
|
+
const CHAT_COMPOSER_MAX_ROWS = 5;
|
|
32
|
+
const MESSAGE_ACTION_WIDTH = 9;
|
|
33
|
+
const COMPOSER_ACTION_WIDTH = 10;
|
|
34
|
+
const MESSAGE_SELECTION_BOTTOM_INSET = 1;
|
|
38
35
|
|
|
39
36
|
function isGroupedWithPrevious(messages: ChatMessage[], index: number) {
|
|
40
37
|
if (index === 0) return false;
|
|
@@ -44,11 +41,58 @@ function isGroupedWithPrevious(messages: ChatMessage[], index: number) {
|
|
|
44
41
|
return new Date(curr.createdAt).getTime() - new Date(prev.createdAt).getTime() < MESSAGE_GROUP_THRESHOLD_MS;
|
|
45
42
|
}
|
|
46
43
|
|
|
44
|
+
function normalizeInlinePreview(text: string) {
|
|
45
|
+
return text.replace(/\s+/g, " ").trim();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function truncateInlinePreview(text: string, width: number) {
|
|
49
|
+
if (width <= 0) return "";
|
|
50
|
+
if (text.length <= width) return text;
|
|
51
|
+
if (width <= 3) return text.slice(0, width);
|
|
52
|
+
return `${text.slice(0, width - 3)}...`;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function formatInlinePreview(text: string, width: number) {
|
|
56
|
+
return truncateInlinePreview(normalizeInlinePreview(text), width);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function wrapTextLines(text: string, width: number) {
|
|
60
|
+
const safeWidth = Math.max(width, 1);
|
|
61
|
+
const lines: string[] = [];
|
|
62
|
+
|
|
63
|
+
for (const paragraph of text.split("\n")) {
|
|
64
|
+
let remaining = paragraph;
|
|
65
|
+
if (remaining.length === 0) {
|
|
66
|
+
lines.push("");
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
while (remaining.length > safeWidth) {
|
|
71
|
+
const candidate = remaining.slice(0, safeWidth + 1);
|
|
72
|
+
const breakAt = candidate.lastIndexOf(" ");
|
|
73
|
+
const lineEnd = breakAt > 0 ? breakAt : safeWidth;
|
|
74
|
+
lines.push(remaining.slice(0, lineEnd).trimEnd());
|
|
75
|
+
remaining = remaining.slice(lineEnd).trimStart();
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
lines.push(remaining);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return lines.length > 0 ? lines : [""];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function getMessageBodyLines(message: ChatMessage, width: number) {
|
|
85
|
+
const contentLineWidth = Math.max(width - 4 - MESSAGE_ACTION_WIDTH, 1);
|
|
86
|
+
return wrapTextLines(message.content, contentLineWidth);
|
|
87
|
+
}
|
|
88
|
+
|
|
47
89
|
function estimateMessageHeight(message: ChatMessage, width: number, grouped = false) {
|
|
48
|
-
const contentLineWidth = Math.max(width - 4, 1);
|
|
49
|
-
const normalizedContent = message.content.replace(/\$[A-Z][A-Z0-9.-]{0,9}/g, (match) => ` ${match.slice(1)} +0% `);
|
|
50
90
|
const headerHeight = grouped ? 0 : 1;
|
|
51
|
-
return headerHeight + (message.replyTo ? 1 : 0) +
|
|
91
|
+
return headerHeight + (message.replyTo ? 1 : 0) + getMessageBodyLines(message, width).length;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function estimateComposerHeight(text: string, width: number) {
|
|
95
|
+
return Math.max(1, Math.min(CHAT_COMPOSER_MAX_ROWS, wrapTextLines(text, width).length));
|
|
52
96
|
}
|
|
53
97
|
|
|
54
98
|
function getMessageTopOffset(messages: ChatMessage[], index: number, width: number) {
|
|
@@ -64,6 +108,28 @@ function scrollToBottom(scrollBox: ScrollBoxRenderable | null) {
|
|
|
64
108
|
scrollBox.scrollTo(Math.max(0, scrollBox.scrollHeight - scrollBox.viewport.height));
|
|
65
109
|
}
|
|
66
110
|
|
|
111
|
+
export function getSelectedMessageScrollTop({
|
|
112
|
+
scrollTop,
|
|
113
|
+
viewportHeight,
|
|
114
|
+
top,
|
|
115
|
+
rowHeight,
|
|
116
|
+
bottomInset = MESSAGE_SELECTION_BOTTOM_INSET,
|
|
117
|
+
}: {
|
|
118
|
+
scrollTop: number;
|
|
119
|
+
viewportHeight: number;
|
|
120
|
+
top: number;
|
|
121
|
+
rowHeight: number;
|
|
122
|
+
bottomInset?: number;
|
|
123
|
+
}) {
|
|
124
|
+
const safeViewportHeight = Math.max(viewportHeight, 1);
|
|
125
|
+
const visibleMessageRows = Math.max(safeViewportHeight - bottomInset, 1);
|
|
126
|
+
if (top < scrollTop) return top;
|
|
127
|
+
if (top + rowHeight > scrollTop + visibleMessageRows) {
|
|
128
|
+
return Math.max(top + rowHeight - visibleMessageRows, 0);
|
|
129
|
+
}
|
|
130
|
+
return scrollTop;
|
|
131
|
+
}
|
|
132
|
+
|
|
67
133
|
function openAuthCommand(query: string, event?: { preventDefault?: () => void; stopPropagation?: () => void }) {
|
|
68
134
|
event?.preventDefault?.();
|
|
69
135
|
event?.stopPropagation?.();
|
|
@@ -100,6 +166,43 @@ function InlineAuthActions({ showSignup = true }: { showSignup?: boolean }) {
|
|
|
100
166
|
);
|
|
101
167
|
}
|
|
102
168
|
|
|
169
|
+
function ChatActionChip({
|
|
170
|
+
label,
|
|
171
|
+
width,
|
|
172
|
+
emphasized = false,
|
|
173
|
+
onPress,
|
|
174
|
+
}: {
|
|
175
|
+
label: string;
|
|
176
|
+
width: number;
|
|
177
|
+
emphasized?: boolean;
|
|
178
|
+
onPress: () => void;
|
|
179
|
+
}) {
|
|
180
|
+
return (
|
|
181
|
+
<box
|
|
182
|
+
width={width}
|
|
183
|
+
height={1}
|
|
184
|
+
backgroundColor={emphasized ? colors.borderFocused : colors.panel}
|
|
185
|
+
onMouseDown={(event: any) => {
|
|
186
|
+
event?.preventDefault?.();
|
|
187
|
+
event?.stopPropagation?.();
|
|
188
|
+
onPress();
|
|
189
|
+
}}
|
|
190
|
+
>
|
|
191
|
+
<text
|
|
192
|
+
fg={emphasized ? colors.bg : colors.text}
|
|
193
|
+
attributes={emphasized ? TextAttributes.BOLD : 0}
|
|
194
|
+
onMouseDown={(event: any) => {
|
|
195
|
+
event?.preventDefault?.();
|
|
196
|
+
event?.stopPropagation?.();
|
|
197
|
+
onPress();
|
|
198
|
+
}}
|
|
199
|
+
>
|
|
200
|
+
{` ${label} `}
|
|
201
|
+
</text>
|
|
202
|
+
</box>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
205
|
+
|
|
103
206
|
export function ChatContent({
|
|
104
207
|
width,
|
|
105
208
|
height,
|
|
@@ -122,9 +225,16 @@ export function ChatContent({
|
|
|
122
225
|
const [replyTo, setReplyTo] = useState<ChatMessage | null>(() => (
|
|
123
226
|
initialSnapshot.replyToId ? initialSnapshot.messages.find((message) => message.id === initialSnapshot.replyToId) ?? null : null
|
|
124
227
|
));
|
|
125
|
-
const inputRef = useRef<
|
|
228
|
+
const inputRef = useRef<TextareaRenderable>(null);
|
|
126
229
|
const scrollRef = useRef<ScrollBoxRenderable>(null);
|
|
230
|
+
const applyingExternalDraftRef = useRef(false);
|
|
127
231
|
const canSend = !!user?.emailVerified;
|
|
232
|
+
const messageBodyWidth = Math.max(contentWidth - 4 - MESSAGE_ACTION_WIDTH, 1);
|
|
233
|
+
const composerPrefixWidth = 3;
|
|
234
|
+
const composerTextWidth = Math.max(contentWidth - composerPrefixWidth, 1);
|
|
235
|
+
const composerHeight = canSend ? estimateComposerHeight(inputValue, composerTextWidth) : 0;
|
|
236
|
+
const selectionActive = selectedIdx >= 0 && selectedIdx < messages.length;
|
|
237
|
+
const stickyTranscript = followMessages && !selectionActive;
|
|
128
238
|
|
|
129
239
|
useEffect(() => {
|
|
130
240
|
return controller.attachView();
|
|
@@ -136,7 +246,13 @@ export function ChatContent({
|
|
|
136
246
|
setHasSavedSession(snapshot.hasSavedSession);
|
|
137
247
|
setUser(snapshot.user);
|
|
138
248
|
setLoading(snapshot.loading);
|
|
139
|
-
setInputValue(snapshot.draft);
|
|
249
|
+
setInputValue((current) => (current === snapshot.draft ? current : snapshot.draft));
|
|
250
|
+
const textarea = inputRef.current;
|
|
251
|
+
if (textarea && textarea.editBuffer.getText() !== snapshot.draft) {
|
|
252
|
+
applyingExternalDraftRef.current = true;
|
|
253
|
+
textarea.setText(snapshot.draft);
|
|
254
|
+
applyingExternalDraftRef.current = false;
|
|
255
|
+
}
|
|
140
256
|
setReplyTo(snapshot.replyToId
|
|
141
257
|
? snapshot.messages.find((message) => message.id === snapshot.replyToId) ?? null
|
|
142
258
|
: null);
|
|
@@ -165,11 +281,64 @@ export function ChatContent({
|
|
|
165
281
|
dispatch({ type: "SET_INPUT_CAPTURED", captured: false });
|
|
166
282
|
}, [dispatch]);
|
|
167
283
|
|
|
168
|
-
const
|
|
169
|
-
|
|
170
|
-
controller.
|
|
284
|
+
const clearReplyTarget = useCallback(() => {
|
|
285
|
+
setReplyTo(null);
|
|
286
|
+
controller.setReplyToId(null);
|
|
171
287
|
}, [controller]);
|
|
172
288
|
|
|
289
|
+
const beginReplyTo = useCallback((index: number, options?: { deferFocus?: boolean }) => {
|
|
290
|
+
if (!canSend || index < 0 || index >= messages.length) return;
|
|
291
|
+
const nextReplyTo = messages[index] ?? null;
|
|
292
|
+
if (!nextReplyTo) return;
|
|
293
|
+
setSelectedIdx(index);
|
|
294
|
+
setFollowMessages(index === messages.length - 1);
|
|
295
|
+
setReplyTo(nextReplyTo);
|
|
296
|
+
controller.setReplyToId(nextReplyTo.id);
|
|
297
|
+
if (options?.deferFocus) {
|
|
298
|
+
queueMicrotask(() => focusInput());
|
|
299
|
+
} else {
|
|
300
|
+
focusInput();
|
|
301
|
+
}
|
|
302
|
+
}, [canSend, controller, focusInput, messages]);
|
|
303
|
+
|
|
304
|
+
const returnToComposer = useCallback(() => {
|
|
305
|
+
setSelectedIdx(-1);
|
|
306
|
+
setFollowMessages(true);
|
|
307
|
+
if (canSend) {
|
|
308
|
+
queueMicrotask(() => focusInput());
|
|
309
|
+
}
|
|
310
|
+
}, [canSend, focusInput]);
|
|
311
|
+
|
|
312
|
+
const moveMessageSelection = useCallback((direction: "up" | "down") => {
|
|
313
|
+
if (messages.length === 0) return false;
|
|
314
|
+
|
|
315
|
+
let next: number;
|
|
316
|
+
if (selectedIdx < 0) {
|
|
317
|
+
if (direction === "down") return false;
|
|
318
|
+
next = messages.length - 1;
|
|
319
|
+
} else if (direction === "down") {
|
|
320
|
+
next = Math.min(selectedIdx + 1, messages.length - 1);
|
|
321
|
+
} else {
|
|
322
|
+
next = Math.max(selectedIdx - 1, 0);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
setSelectedIdx(next);
|
|
326
|
+
setFollowMessages(next === messages.length - 1);
|
|
327
|
+
return true;
|
|
328
|
+
}, [messages.length, selectedIdx]);
|
|
329
|
+
|
|
330
|
+
const shouldLeaveComposerForSelection = useCallback((direction: "up" | "down") => {
|
|
331
|
+
const textarea = inputRef.current;
|
|
332
|
+
if (!textarea || textarea.hasSelection()) return false;
|
|
333
|
+
|
|
334
|
+
const visualLineCount = Math.max(textarea.virtualLineCount, 1);
|
|
335
|
+
if (direction === "up") {
|
|
336
|
+
return textarea.visualCursor.visualRow <= 0;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return textarea.visualCursor.visualRow >= visualLineCount - 1;
|
|
340
|
+
}, []);
|
|
341
|
+
|
|
173
342
|
const sendMessage = useCallback(() => {
|
|
174
343
|
const content = inputValueRef.current.trim();
|
|
175
344
|
if (!content) return;
|
|
@@ -196,108 +365,178 @@ export function ChatContent({
|
|
|
196
365
|
}
|
|
197
366
|
}, [focused, inputFocused]);
|
|
198
367
|
|
|
368
|
+
useEffect(() => {
|
|
369
|
+
const textarea = inputRef.current;
|
|
370
|
+
if (!textarea) return;
|
|
371
|
+
|
|
372
|
+
textarea.onContentChange = () => {
|
|
373
|
+
if (applyingExternalDraftRef.current) return;
|
|
374
|
+
const draft = textarea.editBuffer.getText();
|
|
375
|
+
setInputValue((current) => (current === draft ? current : draft));
|
|
376
|
+
controller.setDraft(draft);
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
return () => {
|
|
380
|
+
if (textarea) {
|
|
381
|
+
textarea.onContentChange = undefined;
|
|
382
|
+
}
|
|
383
|
+
};
|
|
384
|
+
}, [controller]);
|
|
385
|
+
|
|
199
386
|
useEffect(() => {
|
|
200
387
|
if (canSend || !replyTo) return;
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}, [canSend, controller, replyTo]);
|
|
388
|
+
clearReplyTarget();
|
|
389
|
+
}, [canSend, clearReplyTarget, replyTo]);
|
|
204
390
|
|
|
205
391
|
useKeyboard((event) => {
|
|
206
392
|
if (!focused) return;
|
|
207
|
-
|
|
208
|
-
if (event.name === "c" && event.shift) {
|
|
209
|
-
if (inputFocused) {
|
|
210
|
-
blurInput();
|
|
211
|
-
}
|
|
212
|
-
close?.();
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
393
|
+
const isEnterKey = event.name === "return" || event.name === "enter";
|
|
215
394
|
|
|
216
395
|
if (inputFocused) {
|
|
217
396
|
if (event.name === "escape") {
|
|
397
|
+
event.preventDefault?.();
|
|
398
|
+
event.stopPropagation?.();
|
|
218
399
|
if (replyTo) {
|
|
219
|
-
|
|
220
|
-
controller.setReplyToId(null);
|
|
400
|
+
clearReplyTarget();
|
|
221
401
|
} else {
|
|
222
402
|
blurInput();
|
|
223
403
|
}
|
|
404
|
+
return;
|
|
224
405
|
}
|
|
406
|
+
|
|
407
|
+
if ((event.name === "up" || event.name === "down") && shouldLeaveComposerForSelection(event.name)) {
|
|
408
|
+
const moved = moveMessageSelection(event.name);
|
|
409
|
+
if (moved) {
|
|
410
|
+
event.preventDefault?.();
|
|
411
|
+
event.stopPropagation?.();
|
|
412
|
+
blurInput();
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
225
417
|
return;
|
|
226
418
|
}
|
|
227
419
|
|
|
228
|
-
if (
|
|
229
|
-
|
|
420
|
+
if (canSend && isEnterKey && selectedIdx >= 0 && selectedIdx < messages.length) {
|
|
421
|
+
event.preventDefault?.();
|
|
422
|
+
event.stopPropagation?.();
|
|
423
|
+
beginReplyTo(selectedIdx, { deferFocus: true });
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
if ((isEnterKey || event.name === "i") && canSend) {
|
|
428
|
+
event.preventDefault?.();
|
|
429
|
+
event.stopPropagation?.();
|
|
430
|
+
queueMicrotask(() => focusInput());
|
|
230
431
|
return;
|
|
231
432
|
}
|
|
232
433
|
|
|
233
434
|
if (event.name === "escape") {
|
|
234
|
-
|
|
435
|
+
event.preventDefault?.();
|
|
436
|
+
event.stopPropagation?.();
|
|
437
|
+
if (selectedIdx >= 0) {
|
|
438
|
+
setSelectedIdx(-1);
|
|
439
|
+
setFollowMessages(true);
|
|
440
|
+
}
|
|
235
441
|
return;
|
|
236
442
|
}
|
|
237
443
|
|
|
238
444
|
if (event.name === "j" || event.name === "down") {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
445
|
+
event.preventDefault?.();
|
|
446
|
+
event.stopPropagation?.();
|
|
447
|
+
if (selectedIdx === messages.length - 1) {
|
|
448
|
+
returnToComposer();
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
moveMessageSelection("down");
|
|
243
452
|
return;
|
|
244
453
|
}
|
|
245
454
|
if (event.name === "k" || event.name === "up") {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
setSelectedIdx(next);
|
|
250
|
-
setFollowMessages(false);
|
|
455
|
+
event.preventDefault?.();
|
|
456
|
+
event.stopPropagation?.();
|
|
457
|
+
moveMessageSelection("up");
|
|
251
458
|
return;
|
|
252
459
|
}
|
|
253
460
|
|
|
254
461
|
if (canSend && event.name === "r" && selectedIdx >= 0 && selectedIdx < messages.length) {
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
focusInput();
|
|
462
|
+
event.preventDefault?.();
|
|
463
|
+
event.stopPropagation?.();
|
|
464
|
+
beginReplyTo(selectedIdx, { deferFocus: true });
|
|
259
465
|
return;
|
|
260
466
|
}
|
|
261
467
|
|
|
262
468
|
if (event.name === "g" && !event.shift) {
|
|
469
|
+
event.preventDefault?.();
|
|
470
|
+
event.stopPropagation?.();
|
|
263
471
|
setSelectedIdx(0);
|
|
264
472
|
setFollowMessages(false);
|
|
265
473
|
scrollRef.current?.scrollTo(0);
|
|
266
474
|
return;
|
|
267
475
|
}
|
|
268
476
|
if (event.name === "g" && event.shift) {
|
|
477
|
+
event.preventDefault?.();
|
|
478
|
+
event.stopPropagation?.();
|
|
269
479
|
setSelectedIdx(messages.length - 1);
|
|
270
480
|
setFollowMessages(true);
|
|
271
481
|
queueMicrotask(() => scrollToBottom(scrollRef.current));
|
|
272
482
|
return;
|
|
273
483
|
}
|
|
274
|
-
}, [
|
|
484
|
+
}, [
|
|
485
|
+
beginReplyTo,
|
|
486
|
+
blurInput,
|
|
487
|
+
canSend,
|
|
488
|
+
clearReplyTarget,
|
|
489
|
+
focused,
|
|
490
|
+
inputFocused,
|
|
491
|
+
messages.length,
|
|
492
|
+
moveMessageSelection,
|
|
493
|
+
replyTo,
|
|
494
|
+
returnToComposer,
|
|
495
|
+
selectedIdx,
|
|
496
|
+
shouldLeaveComposerForSelection,
|
|
497
|
+
]);
|
|
498
|
+
|
|
499
|
+
const inputMetaHeight = canSend && replyTo ? 1 : 0;
|
|
500
|
+
const inputAreaHeight = canSend ? composerHeight + inputMetaHeight : 2;
|
|
501
|
+
const headerHeight = 1;
|
|
502
|
+
const separatorHeight = 1;
|
|
503
|
+
const footerSeparatorHeight = 1;
|
|
504
|
+
const messageAreaHeight = Math.max(1, height - headerHeight - separatorHeight - footerSeparatorHeight - inputAreaHeight);
|
|
275
505
|
|
|
276
506
|
useEffect(() => {
|
|
277
507
|
if (selectedIdx < messages.length) return;
|
|
278
508
|
setSelectedIdx(messages.length - 1);
|
|
279
509
|
}, [messages.length, selectedIdx]);
|
|
280
510
|
|
|
511
|
+
useEffect(() => {
|
|
512
|
+
if (!stickyTranscript) return;
|
|
513
|
+
queueMicrotask(() => scrollToBottom(scrollRef.current));
|
|
514
|
+
}, [contentWidth, height, messages, messageAreaHeight, stickyTranscript]);
|
|
515
|
+
|
|
281
516
|
useEffect(() => {
|
|
282
517
|
const sb = scrollRef.current;
|
|
283
518
|
if (!sb) return;
|
|
284
|
-
if (
|
|
285
|
-
if (selectedIdx < 0 || selectedIdx >= messages.length) return;
|
|
519
|
+
if (!selectionActive) return;
|
|
286
520
|
const top = getMessageTopOffset(messages, selectedIdx, contentWidth);
|
|
287
521
|
const rowHeight = estimateMessageHeight(messages[selectedIdx]!, contentWidth, isGroupedWithPrevious(messages, selectedIdx));
|
|
288
|
-
const
|
|
289
|
-
|
|
290
|
-
sb.
|
|
291
|
-
|
|
292
|
-
|
|
522
|
+
const nextScrollTop = getSelectedMessageScrollTop({
|
|
523
|
+
scrollTop: sb.scrollTop,
|
|
524
|
+
viewportHeight: sb.viewport.height,
|
|
525
|
+
top,
|
|
526
|
+
rowHeight,
|
|
527
|
+
});
|
|
528
|
+
if (nextScrollTop !== sb.scrollTop) {
|
|
529
|
+
sb.scrollTo(nextScrollTop);
|
|
293
530
|
}
|
|
294
|
-
}, [contentWidth,
|
|
531
|
+
}, [contentWidth, messages, selectedIdx, selectionActive]);
|
|
295
532
|
|
|
296
|
-
const
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
533
|
+
const replyPreview = replyTo
|
|
534
|
+
? formatInlinePreview(
|
|
535
|
+
replyTo.content,
|
|
536
|
+
Math.max(contentWidth - ` replying to @${replyTo.user.username}: `.length - COMPOSER_ACTION_WIDTH - 1, 0),
|
|
537
|
+
)
|
|
538
|
+
: "";
|
|
539
|
+
const inputPlaceholder = replyTo ? `Reply to @${replyTo.user.username}...` : "Type a message...";
|
|
301
540
|
|
|
302
541
|
if (loading && messages.length === 0) {
|
|
303
542
|
return (
|
|
@@ -324,7 +563,7 @@ export function ChatContent({
|
|
|
324
563
|
height={messageAreaHeight}
|
|
325
564
|
scrollY
|
|
326
565
|
focusable={false}
|
|
327
|
-
stickyScroll={
|
|
566
|
+
stickyScroll={stickyTranscript}
|
|
328
567
|
stickyStart="bottom"
|
|
329
568
|
>
|
|
330
569
|
{messages.length === 0 && (
|
|
@@ -335,56 +574,96 @@ export function ChatContent({
|
|
|
335
574
|
{messages.map((msg, index) => {
|
|
336
575
|
const isSelected = index === selectedIdx;
|
|
337
576
|
const isHovered = index === hoveredIdx && !isSelected;
|
|
577
|
+
const showReplyAction = canSend && (isSelected || hoveredIdx === index);
|
|
338
578
|
const bgColor = isSelected ? colors.selected : isHovered ? hoverBg() : undefined;
|
|
339
579
|
const grouped = isGroupedWithPrevious(messages, index);
|
|
340
580
|
const isSending = msg.clientStatus === "sending";
|
|
341
581
|
const hasFailed = msg.clientStatus === "failed";
|
|
582
|
+
const selectedTextColor = hasFailed ? colors.negative : colors.selectedText;
|
|
583
|
+
const replyMetaColor = isSelected ? selectedTextColor : colors.textMuted;
|
|
584
|
+
const replyAuthorColor = isSelected ? selectedTextColor : colors.textDim;
|
|
342
585
|
const headerStatus = isSending ? "sending..." : hasFailed ? "failed" : formatTimeAgo(msg.createdAt);
|
|
343
|
-
const headerStatusColor =
|
|
586
|
+
const headerStatusColor = isSelected
|
|
587
|
+
? selectedTextColor
|
|
588
|
+
: isSending
|
|
589
|
+
? colors.textDim
|
|
590
|
+
: hasFailed
|
|
591
|
+
? colors.negative
|
|
592
|
+
: colors.textMuted;
|
|
593
|
+
const authorColor = isSelected ? selectedTextColor : hasFailed ? colors.negative : colors.positive;
|
|
344
594
|
const authorAttributes = (isSending ? TextAttributes.DIM : 0) | TextAttributes.BOLD;
|
|
345
|
-
const bodyColor = hasFailed ? colors.negative : isSending ? colors.textDim : colors.text;
|
|
346
|
-
|
|
595
|
+
const bodyColor = isSelected ? selectedTextColor : hasFailed ? colors.negative : isSending ? colors.textDim : colors.text;
|
|
596
|
+
const bodyLines = getMessageBodyLines(msg, contentWidth);
|
|
597
|
+
const setHovered = () => setHoveredIdx(index);
|
|
598
|
+
const clearHovered = () => setHoveredIdx((current) => (current === index ? null : current));
|
|
599
|
+
const messageRowProps = {
|
|
600
|
+
width: contentWidth,
|
|
601
|
+
backgroundColor: bgColor,
|
|
602
|
+
onMouseMove: setHovered,
|
|
603
|
+
onMouseOut: clearHovered,
|
|
604
|
+
};
|
|
347
605
|
return (
|
|
348
|
-
<
|
|
349
|
-
key={msg.id}
|
|
350
|
-
flexDirection="column"
|
|
351
|
-
width={contentWidth}
|
|
352
|
-
backgroundColor={bgColor}
|
|
353
|
-
onMouseMove={() => setHoveredIdx(index)}
|
|
354
|
-
onMouseDown={() => {
|
|
355
|
-
setSelectedIdx(index);
|
|
356
|
-
setFollowMessages(index === messages.length - 1);
|
|
357
|
-
}}
|
|
358
|
-
>
|
|
606
|
+
<Fragment key={msg.id}>
|
|
359
607
|
{msg.replyTo && (
|
|
360
|
-
<box
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
608
|
+
<box
|
|
609
|
+
{...messageRowProps}
|
|
610
|
+
flexDirection="row"
|
|
611
|
+
height={1}
|
|
612
|
+
paddingLeft={2}
|
|
613
|
+
>
|
|
614
|
+
<text fg={replyMetaColor}>reply </text>
|
|
615
|
+
<text fg={replyAuthorColor}>{msg.replyTo.user.username}: </text>
|
|
616
|
+
<text fg={replyMetaColor}>
|
|
617
|
+
{formatInlinePreview(
|
|
618
|
+
msg.replyTo.content,
|
|
619
|
+
Math.max(messageBodyWidth - `reply ${msg.replyTo.user.username}: `.length, 0),
|
|
620
|
+
)}
|
|
367
621
|
</text>
|
|
368
622
|
</box>
|
|
369
623
|
)}
|
|
370
624
|
{!grouped && (
|
|
371
|
-
<box
|
|
372
|
-
|
|
625
|
+
<box
|
|
626
|
+
{...messageRowProps}
|
|
627
|
+
flexDirection="row"
|
|
628
|
+
height={1}
|
|
629
|
+
paddingLeft={1}
|
|
630
|
+
>
|
|
631
|
+
<text fg={authorColor} attributes={authorAttributes}>
|
|
373
632
|
{msg.user.username ?? "anon"}
|
|
374
633
|
</text>
|
|
375
634
|
<text fg={headerStatusColor}> ({headerStatus})</text>
|
|
376
635
|
</box>
|
|
377
636
|
)}
|
|
378
|
-
|
|
379
|
-
<
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
637
|
+
{bodyLines.map((line, lineIndex) => (
|
|
638
|
+
<box
|
|
639
|
+
key={`${msg.id}:body:${lineIndex}`}
|
|
640
|
+
{...messageRowProps}
|
|
641
|
+
paddingLeft={3}
|
|
642
|
+
height={1}
|
|
643
|
+
flexDirection="row"
|
|
644
|
+
>
|
|
645
|
+
<box width={messageBodyWidth} height={1}>
|
|
646
|
+
<TickerBadgeText
|
|
647
|
+
text={line}
|
|
648
|
+
lineWidth={messageBodyWidth}
|
|
649
|
+
catalog={catalog}
|
|
650
|
+
textColor={bodyColor}
|
|
651
|
+
openTicker={openTicker}
|
|
652
|
+
/>
|
|
653
|
+
</box>
|
|
654
|
+
<box width={MESSAGE_ACTION_WIDTH} height={1}>
|
|
655
|
+
{lineIndex === 0 && showReplyAction && (
|
|
656
|
+
<ChatActionChip
|
|
657
|
+
label="Reply"
|
|
658
|
+
width={MESSAGE_ACTION_WIDTH}
|
|
659
|
+
emphasized={isSelected}
|
|
660
|
+
onPress={() => beginReplyTo(index)}
|
|
661
|
+
/>
|
|
662
|
+
)}
|
|
663
|
+
</box>
|
|
664
|
+
</box>
|
|
665
|
+
))}
|
|
666
|
+
</Fragment>
|
|
388
667
|
);
|
|
389
668
|
})}
|
|
390
669
|
</scrollbox>
|
|
@@ -398,31 +677,48 @@ export function ChatContent({
|
|
|
398
677
|
{replyTo && (
|
|
399
678
|
<box height={1} width={contentWidth} flexDirection="row">
|
|
400
679
|
<text fg={colors.textMuted}> replying to </text>
|
|
401
|
-
<text fg={colors.positive}>{replyTo.user.username}</text>
|
|
680
|
+
<text fg={colors.positive} attributes={TextAttributes.BOLD}>{`@${replyTo.user.username}`}</text>
|
|
681
|
+
<text fg={colors.textDim}>{replyPreview ? `: ${replyPreview}` : ""}</text>
|
|
402
682
|
<box flexGrow={1} />
|
|
403
|
-
<
|
|
683
|
+
<ChatActionChip
|
|
684
|
+
label="Cancel"
|
|
685
|
+
width={COMPOSER_ACTION_WIDTH}
|
|
686
|
+
onPress={clearReplyTarget}
|
|
687
|
+
/>
|
|
404
688
|
</box>
|
|
405
689
|
)}
|
|
406
690
|
|
|
407
|
-
<box height={
|
|
408
|
-
<
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
691
|
+
<box height={composerHeight} width={contentWidth} flexDirection="row" onMouseDown={focusInput}>
|
|
692
|
+
<box width={composerPrefixWidth} height={composerHeight}>
|
|
693
|
+
<text fg={colors.textDim}> {">"} </text>
|
|
694
|
+
</box>
|
|
695
|
+
<box width={composerTextWidth} height={composerHeight}>
|
|
696
|
+
<textarea
|
|
697
|
+
ref={inputRef}
|
|
698
|
+
initialValue={inputValue}
|
|
699
|
+
width={composerTextWidth}
|
|
700
|
+
height={composerHeight}
|
|
701
|
+
focused={inputFocused && focused}
|
|
702
|
+
placeholder={inputPlaceholder}
|
|
703
|
+
placeholderColor={colors.textMuted}
|
|
704
|
+
textColor={colors.text}
|
|
705
|
+
backgroundColor={colors.bg}
|
|
706
|
+
keyBindings={[
|
|
707
|
+
{ name: "return", action: "submit" },
|
|
708
|
+
{ name: "linefeed", action: "submit" },
|
|
709
|
+
{ name: "return", shift: true, action: "newline" },
|
|
710
|
+
{ name: "linefeed", shift: true, action: "newline" },
|
|
711
|
+
{ name: "return", meta: true, action: "submit" },
|
|
712
|
+
{ name: "linefeed", meta: true, action: "submit" },
|
|
713
|
+
]}
|
|
714
|
+
onSubmit={() => {
|
|
715
|
+
if (inputValueRef.current.trim()) {
|
|
716
|
+
sendMessage();
|
|
717
|
+
}
|
|
718
|
+
}}
|
|
719
|
+
wrapText
|
|
720
|
+
/>
|
|
721
|
+
</box>
|
|
426
722
|
</box>
|
|
427
723
|
</>
|
|
428
724
|
) : (
|
|
@@ -545,7 +841,7 @@ export const gloomberbCloudPlugin: GloomPlugin = {
|
|
|
545
841
|
|
|
546
842
|
setup(ctx) {
|
|
547
843
|
chatController.attachPersistence(ctx.persistence, ctx.resume);
|
|
548
|
-
chatController.
|
|
844
|
+
chatController.setNotifier(ctx.notify);
|
|
549
845
|
|
|
550
846
|
ctx.registerPane({
|
|
551
847
|
id: "chat",
|
|
@@ -647,7 +943,7 @@ export const gloomberbCloudPlugin: GloomPlugin = {
|
|
|
647
943
|
},
|
|
648
944
|
execute: async () => {
|
|
649
945
|
await apiClient.sendVerification();
|
|
650
|
-
ctx.
|
|
946
|
+
ctx.notify({ body: "Verification email sent.", type: "success" });
|
|
651
947
|
},
|
|
652
948
|
});
|
|
653
949
|
|
|
@@ -663,17 +959,22 @@ export const gloomberbCloudPlugin: GloomPlugin = {
|
|
|
663
959
|
category: "config",
|
|
664
960
|
execute: async () => {
|
|
665
961
|
if (!apiClient.getSessionToken()) {
|
|
666
|
-
ctx.
|
|
962
|
+
ctx.notify({ body: "Not logged in.", type: "error" });
|
|
667
963
|
return;
|
|
668
964
|
}
|
|
669
965
|
await apiClient.signOut();
|
|
670
966
|
await chatController.refreshSession();
|
|
671
967
|
await chatController.refreshMessages();
|
|
672
|
-
ctx.
|
|
968
|
+
ctx.notify({ body: "Logged out.", type: "info" });
|
|
673
969
|
},
|
|
674
970
|
hidden: () => !apiClient.getSessionToken(),
|
|
675
971
|
});
|
|
676
972
|
},
|
|
973
|
+
|
|
974
|
+
dispose() {
|
|
975
|
+
chatController.dispose();
|
|
976
|
+
apiClient.dispose();
|
|
977
|
+
},
|
|
677
978
|
};
|
|
678
979
|
|
|
679
980
|
export const chatPlugin = gloomberbCloudPlugin;
|