@parallelworks/ui 0.21.2 → 0.23.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/dist/ai/adapter/mock/mockAdapter.js +2 -1
- package/dist/ai/adapter/openai/openaiAdapter.js +6 -3
- package/dist/ai/adapter/openai/wire.d.ts +2 -1
- package/dist/ai/adapter/types.d.ts +2 -1
- package/dist/ai/components/ChatEmptyState.js +2 -0
- package/dist/ai/components/ChatInput.d.ts +4 -2
- package/dist/ai/components/ChatInput.js +36 -10
- package/dist/ai/components/ChatMessage.js +9 -2
- package/dist/ai/components/ChatMessageList.d.ts +3 -1
- package/dist/ai/components/ChatMessageList.js +2 -2
- package/dist/ai/components/ChatThread.js +2 -0
- package/dist/ai/components/ModelSelector.js +94 -62
- package/dist/ai/components/PastedText.d.ts +15 -0
- package/dist/ai/components/PastedText.js +59 -0
- package/dist/ai/components/ProviderIssueBanner.d.ts +1 -0
- package/dist/ai/components/ProviderIssueBanner.js +30 -0
- package/dist/ai/components/usePasteCards.d.ts +30 -0
- package/dist/ai/components/usePasteCards.js +129 -0
- package/dist/ai/core/ChatProvider.d.ts +2 -1
- package/dist/ai/core/ChatProvider.js +7 -3
- package/dist/ai/core/chatReducer.d.ts +3 -1
- package/dist/ai/core/chatReducer.js +2 -0
- package/dist/ai/core/pastes.d.ts +14 -0
- package/dist/ai/core/pastes.js +43 -0
- package/dist/ai/core/providerIssues.d.ts +2 -0
- package/dist/ai/core/providerIssues.js +7 -0
- package/dist/ai/index.d.ts +4 -0
- package/dist/ai/index.js +4 -1
- package/dist/ai/strings.d.ts +14 -0
- package/dist/ai/strings.js +14 -0
- package/dist/ai/types.d.ts +18 -0
- package/dist/components/Provider.d.ts +1 -0
- package/dist/components/Provider.js +1 -0
- package/dist/file-explorer/FileExplorer.d.ts +7 -1
- package/dist/file-explorer/FileExplorer.js +3 -1
- package/dist/file-explorer/FilePreview/Viewer.js +33 -18
- package/dist/file-explorer/FilePreview/previewType.d.ts +3 -1
- package/dist/file-explorer/FilePreview/previewType.js +18 -1
- package/dist/file-explorer/FilePreview/readCappedBytes.d.ts +4 -0
- package/dist/file-explorer/FilePreview/readCappedBytes.js +7 -1
- package/dist/file-explorer/FilePreview/useObjectText.js +3 -3
- package/dist/file-explorer/index.d.ts +1 -1
- package/dist/file-explorer/lib/types.d.ts +11 -0
- package/dist/list/UserHoverCard.js +28 -9
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { countLines, normalizeNewlines, utf8Bytes } from "../core/pastes.js";
|
|
2
|
+
import { useCallback, useEffect, useRef, useState } from "react";
|
|
3
|
+
//#region src/ai/components/usePasteCards.ts
|
|
4
|
+
function appendText(box, text) {
|
|
5
|
+
return box.trim() ? `${box}\n\n${text}` : text;
|
|
6
|
+
}
|
|
7
|
+
/** Keeps the composer's cards in step with the model, so text or card shows how each paste is sent. */
|
|
8
|
+
function usePasteCards(pastes, input, setInput) {
|
|
9
|
+
const [cards, setCards] = useState([]);
|
|
10
|
+
const inBox = useRef([]);
|
|
11
|
+
const saved = useRef(/* @__PURE__ */ new Map());
|
|
12
|
+
const uploading = useRef(/* @__PURE__ */ new Set());
|
|
13
|
+
const seq = useRef(0);
|
|
14
|
+
const latest = useRef({
|
|
15
|
+
cards,
|
|
16
|
+
input,
|
|
17
|
+
pastes,
|
|
18
|
+
setInput
|
|
19
|
+
});
|
|
20
|
+
latest.current = {
|
|
21
|
+
cards,
|
|
22
|
+
input,
|
|
23
|
+
pastes,
|
|
24
|
+
setInput
|
|
25
|
+
};
|
|
26
|
+
const addCard = useCallback((text) => {
|
|
27
|
+
const target = latest.current.pastes;
|
|
28
|
+
if (!target) return;
|
|
29
|
+
seq.current += 1;
|
|
30
|
+
const card = {
|
|
31
|
+
key: `paste-${seq.current}`,
|
|
32
|
+
text,
|
|
33
|
+
lines: countLines(text),
|
|
34
|
+
bytes: utf8Bytes(text)
|
|
35
|
+
};
|
|
36
|
+
const known = saved.current.get(text);
|
|
37
|
+
if (known) {
|
|
38
|
+
setCards((current) => [...current, {
|
|
39
|
+
...card,
|
|
40
|
+
paste: known
|
|
41
|
+
}]);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
setCards((current) => [...current, card]);
|
|
45
|
+
uploading.current.add(card.key);
|
|
46
|
+
const settle = (result) => {
|
|
47
|
+
const waiting = uploading.current.delete(card.key);
|
|
48
|
+
if ("unsupported" in result) {
|
|
49
|
+
setCards((current) => current.filter((c) => c.key !== card.key));
|
|
50
|
+
if (waiting) latest.current.setInput(appendText(latest.current.input, text));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if ("paste" in result) saved.current.set(text, result.paste);
|
|
54
|
+
setCards((current) => current.map((c) => {
|
|
55
|
+
if (c.key !== card.key) return c;
|
|
56
|
+
return "error" in result ? {
|
|
57
|
+
...c,
|
|
58
|
+
error: result.error
|
|
59
|
+
} : {
|
|
60
|
+
...c,
|
|
61
|
+
paste: result.paste
|
|
62
|
+
};
|
|
63
|
+
}));
|
|
64
|
+
};
|
|
65
|
+
target.upload(text).then(settle, (err) => settle({ error: err instanceof Error ? err.message : String(err) }));
|
|
66
|
+
}, []);
|
|
67
|
+
/** Reports whether the paste became a card, so the caller stops the browser pasting it too. */
|
|
68
|
+
const handleTextPaste = (raw) => {
|
|
69
|
+
if (!pastes || !raw) return false;
|
|
70
|
+
const text = normalizeNewlines(raw);
|
|
71
|
+
const bytes = utf8Bytes(text);
|
|
72
|
+
if (pastes.inlineMaxBytes === void 0 || bytes <= pastes.inlineMaxBytes && utf8Bytes(input) + bytes <= 1048576) {
|
|
73
|
+
if (bytes >= 1e3) inBox.current.push(text);
|
|
74
|
+
return false;
|
|
75
|
+
}
|
|
76
|
+
addCard(text);
|
|
77
|
+
return true;
|
|
78
|
+
};
|
|
79
|
+
const inlineMax = pastes?.inlineMaxBytes;
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (inlineMax === void 0) return;
|
|
82
|
+
const { cards: current, input: box, setInput: set } = latest.current;
|
|
83
|
+
const boxed = inBox.current.filter((text) => box.includes(text));
|
|
84
|
+
const outgrown = boxed.filter((text) => utf8Bytes(text) > inlineMax);
|
|
85
|
+
let boxBytes = utf8Bytes(box) - outgrown.reduce((sum, text) => sum + utf8Bytes(text), 0);
|
|
86
|
+
for (const text of boxed.toReversed()) {
|
|
87
|
+
if (boxBytes <= 1048576) break;
|
|
88
|
+
if (!outgrown.includes(text)) {
|
|
89
|
+
outgrown.push(text);
|
|
90
|
+
boxBytes -= utf8Bytes(text);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
const fitting = current.filter((c) => {
|
|
94
|
+
if (c.bytes > inlineMax || boxBytes + c.bytes > 1048576) return false;
|
|
95
|
+
boxBytes += c.bytes;
|
|
96
|
+
return true;
|
|
97
|
+
});
|
|
98
|
+
if (fitting.length === 0 && outgrown.length === 0) return;
|
|
99
|
+
let next = box;
|
|
100
|
+
for (const text of outgrown) next = next.replace(text, () => "");
|
|
101
|
+
for (const card of fitting) {
|
|
102
|
+
next = appendText(next, card.text);
|
|
103
|
+
uploading.current.delete(card.key);
|
|
104
|
+
}
|
|
105
|
+
inBox.current = [...inBox.current.filter((text) => !outgrown.includes(text)), ...fitting.map((c) => c.text)];
|
|
106
|
+
setCards((cs) => cs.filter((c) => !fitting.some((f) => f.key === c.key)));
|
|
107
|
+
set(next);
|
|
108
|
+
for (const text of outgrown) addCard(text);
|
|
109
|
+
}, [inlineMax, addCard]);
|
|
110
|
+
return {
|
|
111
|
+
cards,
|
|
112
|
+
handleTextPaste,
|
|
113
|
+
removeCard: useCallback((key) => {
|
|
114
|
+
uploading.current.delete(key);
|
|
115
|
+
setCards((current) => current.filter((c) => c.key !== key));
|
|
116
|
+
}, []),
|
|
117
|
+
takeCards: useCallback(() => {
|
|
118
|
+
const sent = latest.current.cards.flatMap((c) => c.paste ? [c.paste] : []);
|
|
119
|
+
setCards([]);
|
|
120
|
+
uploading.current.clear();
|
|
121
|
+
inBox.current = [];
|
|
122
|
+
saved.current.clear();
|
|
123
|
+
return sent;
|
|
124
|
+
}, []),
|
|
125
|
+
blocking: cards.some((c) => !c.paste)
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
export { usePasteCards };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
2
|
import type { ChatAdapter } from '../adapter/types.js';
|
|
3
|
-
import type { AttachmentMeta, ChatModel, ChatNavigation, ChatNotify, ChatUser, Conversation, ConversationSummary, ChatMessage as Message, MessagePart, ProviderInfo, UnreachableSession } from '../types.js';
|
|
3
|
+
import type { AttachmentMeta, ChatModel, ChatNavigation, ChatNotify, ChatUser, Conversation, ConversationSummary, ChatMessage as Message, MessagePart, ProviderInfo, ProviderIssue, UnreachableSession } from '../types.js';
|
|
4
4
|
import { type ChatUIConfig } from './config.js';
|
|
5
5
|
interface ChatContextValue {
|
|
6
6
|
adapter: ChatAdapter;
|
|
@@ -13,6 +13,7 @@ interface ChatContextValue {
|
|
|
13
13
|
providers: ProviderInfo[];
|
|
14
14
|
models: ChatModel[];
|
|
15
15
|
unreachableSessions: UnreachableSession[];
|
|
16
|
+
providerIssues: ProviderIssue[];
|
|
16
17
|
selectedProvider: string | null;
|
|
17
18
|
isSelectedModelAvailable: boolean;
|
|
18
19
|
selectedAllocation: string | null;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { applyPartDelta, finalizeParts } from "./parts.js";
|
|
2
2
|
import { chatReducer, initialState } from "./chatReducer.js";
|
|
3
3
|
import { ChatConfigProvider, resolveChatConfig } from "./config.js";
|
|
4
|
+
import { providerIssueFor } from "./providerIssues.js";
|
|
4
5
|
import { useSSEStream } from "./useSSEStream.js";
|
|
5
6
|
import { createContext, useCallback, useContext, useEffect, useMemo, useReducer, useRef } from "react";
|
|
6
7
|
import { jsx } from "react/jsx-runtime";
|
|
@@ -195,17 +196,19 @@ function ChatProvider({ adapter, currentUser, navigation, notify, activeConversa
|
|
|
195
196
|
isLoadingModels: true
|
|
196
197
|
});
|
|
197
198
|
try {
|
|
198
|
-
const { models, unreachableSessions } = await adapter.models.list();
|
|
199
|
+
const { models, unreachableSessions, providerIssues } = await adapter.models.list();
|
|
199
200
|
dispatch({
|
|
200
201
|
type: "SET_MODELS",
|
|
201
202
|
models,
|
|
202
|
-
unreachableSessions
|
|
203
|
+
unreachableSessions,
|
|
204
|
+
providerIssues
|
|
203
205
|
});
|
|
204
206
|
hasLoadedModelsRef.current = true;
|
|
205
207
|
const current = selectedProviderRef.current;
|
|
206
208
|
const stored = readStoredModel();
|
|
207
209
|
const offers = (id) => !!id && models.some((m) => m.id === id);
|
|
208
|
-
const
|
|
210
|
+
const firstUsable = models.find((model) => !providerIssueFor(providerIssues, model)) ?? models[0];
|
|
211
|
+
const resolved = offers(current) ? current : offers(stored) ? stored : firstUsable?.id ?? null;
|
|
209
212
|
if (resolved !== current) dispatch({
|
|
210
213
|
type: "SET_SELECTED_PROVIDER",
|
|
211
214
|
providerId: resolved
|
|
@@ -750,6 +753,7 @@ function ChatProvider({ adapter, currentUser, navigation, notify, activeConversa
|
|
|
750
753
|
providers: state.providers,
|
|
751
754
|
models: state.models,
|
|
752
755
|
unreachableSessions: state.unreachableSessions,
|
|
756
|
+
providerIssues: state.providerIssues,
|
|
753
757
|
selectedProvider: state.selectedProvider,
|
|
754
758
|
isSelectedModelAvailable,
|
|
755
759
|
selectedAllocation: state.selectedAllocation,
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { PartDelta } from '../adapter/types.js';
|
|
2
|
-
import type { ChatMessage, ChatModel, Conversation, ConversationSummary, MessagePart, ProviderInfo, UnreachableSession } from '../types.js';
|
|
2
|
+
import type { ChatMessage, ChatModel, Conversation, ConversationSummary, MessagePart, ProviderInfo, ProviderIssue, UnreachableSession } from '../types.js';
|
|
3
3
|
export interface ChatState {
|
|
4
4
|
conversations: ConversationSummary[];
|
|
5
5
|
currentConversation: Conversation | null;
|
|
6
6
|
providers: ProviderInfo[];
|
|
7
7
|
models: ChatModel[];
|
|
8
8
|
unreachableSessions: UnreachableSession[];
|
|
9
|
+
providerIssues: ProviderIssue[];
|
|
9
10
|
selectedProvider: string | null;
|
|
10
11
|
selectedAllocation: string | null;
|
|
11
12
|
isLoading: boolean;
|
|
@@ -35,6 +36,7 @@ export type ChatAction = {
|
|
|
35
36
|
type: 'SET_MODELS';
|
|
36
37
|
models: ChatModel[];
|
|
37
38
|
unreachableSessions?: UnreachableSession[];
|
|
39
|
+
providerIssues?: ProviderIssue[];
|
|
38
40
|
} | {
|
|
39
41
|
type: 'SET_SELECTED_PROVIDER';
|
|
40
42
|
providerId: string | null;
|
|
@@ -6,6 +6,7 @@ var initialState = {
|
|
|
6
6
|
providers: [],
|
|
7
7
|
models: [],
|
|
8
8
|
unreachableSessions: [],
|
|
9
|
+
providerIssues: [],
|
|
9
10
|
selectedProvider: null,
|
|
10
11
|
selectedAllocation: null,
|
|
11
12
|
isLoading: false,
|
|
@@ -80,6 +81,7 @@ function chatReducer(state, action) {
|
|
|
80
81
|
...state,
|
|
81
82
|
models: action.models,
|
|
82
83
|
unreachableSessions: action.unreachableSessions || [],
|
|
84
|
+
providerIssues: action.providerIssues || [],
|
|
83
85
|
hasLoadedModels: true,
|
|
84
86
|
modelsError: null
|
|
85
87
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { MessagePaste } from '../types.js';
|
|
2
|
+
/** Pastes this small never turn into a card, whatever the model. */
|
|
3
|
+
export declare const PASTE_CARD_MIN_BYTES = 1000;
|
|
4
|
+
/** The most text sent inline, for one paste and for a whole message. */
|
|
5
|
+
export declare const PASTE_INLINE_CAP: number;
|
|
6
|
+
export declare function utf8Bytes(text: string): number;
|
|
7
|
+
/** Terminals and some browsers put CR line endings on the clipboard. */
|
|
8
|
+
export declare function normalizeNewlines(text: string): string;
|
|
9
|
+
/** Counts lines as a paste's label shows them: a final newline starts none. */
|
|
10
|
+
export declare function countLines(text: string): number;
|
|
11
|
+
/** The inline limit for a context window, for a host that has not reported its own. */
|
|
12
|
+
export declare function pasteInlineMaxBytes(contextWindow: number | undefined): number;
|
|
13
|
+
/** Splits a message into its text and the pastes its placeholders stand for. */
|
|
14
|
+
export declare function splitPastes(content: string, pastes: MessagePaste[]): (string | MessagePaste)[];
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
//#region src/ai/core/pastes.ts
|
|
2
|
+
/** Pastes this small never turn into a card, whatever the model. */
|
|
3
|
+
var PASTE_CARD_MIN_BYTES = 1e3;
|
|
4
|
+
/** The most text sent inline, for one paste and for a whole message. */
|
|
5
|
+
var PASTE_INLINE_CAP = 1 << 20;
|
|
6
|
+
var DEFAULT_CONTEXT_WINDOW = 128e3;
|
|
7
|
+
var encoder = new TextEncoder();
|
|
8
|
+
function utf8Bytes(text) {
|
|
9
|
+
return encoder.encode(text).length;
|
|
10
|
+
}
|
|
11
|
+
/** Terminals and some browsers put CR line endings on the clipboard. */
|
|
12
|
+
function normalizeNewlines(text) {
|
|
13
|
+
return text.replace(/\r\n?/g, "\n");
|
|
14
|
+
}
|
|
15
|
+
/** Counts lines as a paste's label shows them: a final newline starts none. */
|
|
16
|
+
function countLines(text) {
|
|
17
|
+
return (text.endsWith("\n") ? text.slice(0, -1) : text).split("\n").length;
|
|
18
|
+
}
|
|
19
|
+
/** The inline limit for a context window, for a host that has not reported its own. */
|
|
20
|
+
function pasteInlineMaxBytes(contextWindow) {
|
|
21
|
+
return Math.min(Math.floor((contextWindow || DEFAULT_CONTEXT_WINDOW) * 4 / 10), PASTE_INLINE_CAP);
|
|
22
|
+
}
|
|
23
|
+
function escapeRegExp(text) {
|
|
24
|
+
return text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
25
|
+
}
|
|
26
|
+
/** Splits a message into its text and the pastes its placeholders stand for. */
|
|
27
|
+
function splitPastes(content, pastes) {
|
|
28
|
+
const byPlaceholder = new Map(pastes.map((p) => [p.placeholder, p]));
|
|
29
|
+
if (byPlaceholder.size === 0) return [content];
|
|
30
|
+
const pattern = new RegExp([...byPlaceholder.keys()].map(escapeRegExp).join("|"), "g");
|
|
31
|
+
const out = [];
|
|
32
|
+
let last = 0;
|
|
33
|
+
for (const match of content.matchAll(pattern)) {
|
|
34
|
+
if (match.index > last) out.push(content.slice(last, match.index));
|
|
35
|
+
const paste = byPlaceholder.get(match[0]);
|
|
36
|
+
if (paste) out.push(paste);
|
|
37
|
+
last = match.index + match[0].length;
|
|
38
|
+
}
|
|
39
|
+
if (last < content.length) out.push(content.slice(last));
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
//#endregion
|
|
43
|
+
export { PASTE_CARD_MIN_BYTES, PASTE_INLINE_CAP, countLines, normalizeNewlines, pasteInlineMaxBytes, splitPastes, utf8Bytes };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/ai/core/providerIssues.ts
|
|
2
|
+
function providerIssueFor(issues, model) {
|
|
3
|
+
if (!model?.provider_name) return;
|
|
4
|
+
return issues.find((issue) => issue.provider_name === model.provider_name && (issue.provider_owner ?? "") === (model.provider_owner ?? ""));
|
|
5
|
+
}
|
|
6
|
+
//#endregion
|
|
7
|
+
export { providerIssueFor };
|
package/dist/ai/index.d.ts
CHANGED
|
@@ -20,8 +20,10 @@ export { ComposerContext, ComposerControls, ComposerSettings, ConnectToolsLink,
|
|
|
20
20
|
export { default as ComposerFrame } from './components/ComposerFrame.js';
|
|
21
21
|
export { default as DragOverlay } from './components/DragOverlay.js';
|
|
22
22
|
export { default as ModelSelector, getProviderKeyFromModelId, } from './components/ModelSelector.js';
|
|
23
|
+
export { default as ProviderIssueBanner } from './components/ProviderIssueBanner.js';
|
|
23
24
|
export { default as ShareDialog, ShareButton, } from './components/ShareDialog.js';
|
|
24
25
|
export { type SlashCommandOption, type SlashMenuConfig, slashMatches, } from './components/SlashMenu.js';
|
|
26
|
+
export type { ComposerPastes, PasteUploadResult, } from './components/usePasteCards.js';
|
|
25
27
|
export { ChatProvider, type ChatProviderProps, useChat, } from './core/ChatProvider.js';
|
|
26
28
|
export { type ChatAction, type ChatState, chatReducer, initialState, } from './core/chatReducer.js';
|
|
27
29
|
export type { PartRendererProps } from './core/config.js';
|
|
@@ -30,6 +32,8 @@ export { FOCUS_SIDEBAR_SEARCH_EVENT } from './core/events.js';
|
|
|
30
32
|
export { getGreeting } from './core/greeting.js';
|
|
31
33
|
export { KeyboardShortcutsProvider } from './core/KeyboardShortcutsProvider.js';
|
|
32
34
|
export { applyPartDelta, finalizeParts } from './core/parts.js';
|
|
35
|
+
export { pasteInlineMaxBytes } from './core/pastes.js';
|
|
36
|
+
export { providerIssueFor } from './core/providerIssues.js';
|
|
33
37
|
export { default as useDragDrop } from './core/useDragDrop.js';
|
|
34
38
|
export { useKeyboardShortcuts } from './core/useKeyboardShortcuts.js';
|
|
35
39
|
export { type ChatStrings, defaultChatStrings } from './strings.js';
|
package/dist/ai/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { applyPartDelta, finalizeParts } from "./core/parts.js";
|
|
|
10
10
|
import { chatReducer, initialState } from "./core/chatReducer.js";
|
|
11
11
|
import { defaultChatStrings } from "./strings.js";
|
|
12
12
|
import { resolveChatConfig, useChatConfig } from "./core/config.js";
|
|
13
|
+
import { providerIssueFor } from "./core/providerIssues.js";
|
|
13
14
|
import { ChatProvider, useChat } from "./core/ChatProvider.js";
|
|
14
15
|
import Dropdown from "./ui/Dropdown.js";
|
|
15
16
|
import { formatFileSize, isMac, isOrgModel } from "./utils.js";
|
|
@@ -26,8 +27,10 @@ import { getGreeting } from "./core/greeting.js";
|
|
|
26
27
|
import useDragDrop from "./core/useDragDrop.js";
|
|
27
28
|
import { ComposerContext, ComposerControls, ComposerSettings, ConnectToolsLink } from "./components/ComposerChrome.js";
|
|
28
29
|
import DragOverlay from "./components/DragOverlay.js";
|
|
30
|
+
import { pasteInlineMaxBytes } from "./core/pastes.js";
|
|
29
31
|
import { slashMatches } from "./components/SlashMenu.js";
|
|
30
32
|
import ChatInput from "./components/ChatInput.js";
|
|
33
|
+
import ProviderIssueBanner from "./components/ProviderIssueBanner.js";
|
|
31
34
|
import ChatEmptyState from "./components/ChatEmptyState.js";
|
|
32
35
|
import { FOCUS_SIDEBAR_SEARCH_EVENT } from "./core/events.js";
|
|
33
36
|
import { useKeyboardShortcuts } from "./core/useKeyboardShortcuts.js";
|
|
@@ -41,4 +44,4 @@ import ChatMessageList from "./components/ChatMessageList.js";
|
|
|
41
44
|
import ChatThread from "./components/ChatThread.js";
|
|
42
45
|
import ComposerFrame from "./components/ComposerFrame.js";
|
|
43
46
|
import { NoticeBar, NoticeCard } from "./ui/Notice.js";
|
|
44
|
-
export { AGENT_COLOR_NAMES, ActivityLine, AgentMessageParts, AllocationSelector, ApprovalBlock, AttachmentManager, AttachmentUpload, BlockedGroupBanner, BranchNavigator, ChatAdapterError, ChatEmptyState, ChatInput, ChatLayout, ChatMessageList, ChatMessage_default as ChatMessageView, ChatProvider, ChatSidebar, ChatThread, ComposerContext, ComposerControls, ComposerFrame, ComposerSettings, ConnectToolsLink, DEFAULT_PERMISSION_MODE, DragOverlay, Dropdown, FOCUS_SIDEBAR_SEARCH_EVENT, INTERRUPTED_NOTICE, KeyboardShortcutsProvider, MAX_EDIT_DIFF_LINES, MAX_WRITE_DIFF_LINES, ModelSelector, NAMED_PERMISSION_MODES, NoticeBar, NoticeCard, PermissionPicker, RenameDialog, ShareButton, ShareDialog, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarToggle, agentColorHex, agentTypeLabel, applyPartDelta, askQuestionFromArgs, bashExitCode, chatReducer, clampPermissionMode, classifyPseudoUserMessage, clip, compactCount, computeEditDiff, computeWriteRows, defaultChatStrings, durationToAbsHumanDuration, editSummary, finalizeParts, firstNonEmptyLine, formatElapsed, formatFileSize, fullToolArgs, getGreeting, getProviderKeyFromModelId, hiddenTranscriptTool, initialState, interruptionNotice, isEditDiffTool, isGroupableTool, isMac, isNamedPermissionMode, isOrgModel, languageForPath, lineDelta, parseEditArgs, parseSubagentNotice, permissionModesUpTo, planFromArgs, plural, resolveChatConfig, resultFailed, rollupSentence, sanitizeLabel, sanitizePanelText, slashMatches, subagentDeliveryLine, summarizeToolArgs, taskCallFromArgs, thinkingHeading, tokenizeDiffLines, toolHeaderFor, toolSummary, useChat, useChatConfig, useDragDrop, useKeyboardShortcuts, useRowDialogs, wordEmphasisRanges };
|
|
47
|
+
export { AGENT_COLOR_NAMES, ActivityLine, AgentMessageParts, AllocationSelector, ApprovalBlock, AttachmentManager, AttachmentUpload, BlockedGroupBanner, BranchNavigator, ChatAdapterError, ChatEmptyState, ChatInput, ChatLayout, ChatMessageList, ChatMessage_default as ChatMessageView, ChatProvider, ChatSidebar, ChatThread, ComposerContext, ComposerControls, ComposerFrame, ComposerSettings, ConnectToolsLink, DEFAULT_PERMISSION_MODE, DragOverlay, Dropdown, FOCUS_SIDEBAR_SEARCH_EVENT, INTERRUPTED_NOTICE, KeyboardShortcutsProvider, MAX_EDIT_DIFF_LINES, MAX_WRITE_DIFF_LINES, ModelSelector, NAMED_PERMISSION_MODES, NoticeBar, NoticeCard, PermissionPicker, ProviderIssueBanner, RenameDialog, ShareButton, ShareDialog, SidebarGroupHeading, SidebarPanel, SidebarRailItem, SidebarRow, SidebarToggle, agentColorHex, agentTypeLabel, applyPartDelta, askQuestionFromArgs, bashExitCode, chatReducer, clampPermissionMode, classifyPseudoUserMessage, clip, compactCount, computeEditDiff, computeWriteRows, defaultChatStrings, durationToAbsHumanDuration, editSummary, finalizeParts, firstNonEmptyLine, formatElapsed, formatFileSize, fullToolArgs, getGreeting, getProviderKeyFromModelId, hiddenTranscriptTool, initialState, interruptionNotice, isEditDiffTool, isGroupableTool, isMac, isNamedPermissionMode, isOrgModel, languageForPath, lineDelta, parseEditArgs, parseSubagentNotice, pasteInlineMaxBytes, permissionModesUpTo, planFromArgs, plural, providerIssueFor, resolveChatConfig, resultFailed, rollupSentence, sanitizeLabel, sanitizePanelText, slashMatches, subagentDeliveryLine, summarizeToolArgs, taskCallFromArgs, thinkingHeading, tokenizeDiffLines, toolHeaderFor, toolSummary, useChat, useChatConfig, useDragDrop, useKeyboardShortcuts, useRowDialogs, wordEmphasisRanges };
|
package/dist/ai/strings.d.ts
CHANGED
|
@@ -25,6 +25,14 @@ export interface ChatStrings {
|
|
|
25
25
|
title: string;
|
|
26
26
|
message: string;
|
|
27
27
|
};
|
|
28
|
+
providerIssue: {
|
|
29
|
+
keyRejected: string;
|
|
30
|
+
unreachable: string;
|
|
31
|
+
keyRejectedHint: string;
|
|
32
|
+
unreachableHint: string;
|
|
33
|
+
bannerKeyRejected: (provider: string) => string;
|
|
34
|
+
bannerUnreachable: (provider: string) => string;
|
|
35
|
+
};
|
|
28
36
|
queue: {
|
|
29
37
|
remove: string;
|
|
30
38
|
};
|
|
@@ -51,6 +59,12 @@ export interface ChatStrings {
|
|
|
51
59
|
sendMessage: string;
|
|
52
60
|
filesAttached: (count: number) => string;
|
|
53
61
|
};
|
|
62
|
+
paste: {
|
|
63
|
+
title: string;
|
|
64
|
+
lines: (count: number) => string;
|
|
65
|
+
saving: string;
|
|
66
|
+
remove: string;
|
|
67
|
+
};
|
|
54
68
|
thinking: {
|
|
55
69
|
label: string;
|
|
56
70
|
ellipsis: string;
|
package/dist/ai/strings.js
CHANGED
|
@@ -33,6 +33,14 @@ var defaultChatStrings = {
|
|
|
33
33
|
message: "It has reached its allocation limit. Messages to this provider are paused until your administrator increases the allocation."
|
|
34
34
|
},
|
|
35
35
|
queue: { remove: "Remove queued message" },
|
|
36
|
+
providerIssue: {
|
|
37
|
+
keyRejected: "API key rejected",
|
|
38
|
+
unreachable: "Endpoint unreachable",
|
|
39
|
+
keyRejectedHint: "The provider rejected this connection’s API key. Update the key in the provider settings to resume.",
|
|
40
|
+
unreachableHint: "The provider’s endpoint did not respond. Check the endpoint URL and network access.",
|
|
41
|
+
bannerKeyRejected: (provider) => `API key rejected for \u201c${provider}\u201d`,
|
|
42
|
+
bannerUnreachable: (provider) => `\u201c${provider}\u201d is unreachable`
|
|
43
|
+
},
|
|
36
44
|
activity: {
|
|
37
45
|
tokens: (input, output) => `↑${input} ↓${output} tokens`,
|
|
38
46
|
stop: "Stop"
|
|
@@ -60,6 +68,12 @@ var defaultChatStrings = {
|
|
|
60
68
|
sendMessage: "Send message (Enter)",
|
|
61
69
|
filesAttached: (count) => `${count} file${count > 1 ? "s" : ""} attached`
|
|
62
70
|
},
|
|
71
|
+
paste: {
|
|
72
|
+
title: "Pasted text",
|
|
73
|
+
lines: (count) => `${count.toLocaleString()} line${count === 1 ? "" : "s"}`,
|
|
74
|
+
saving: "Saving…",
|
|
75
|
+
remove: "Remove paste"
|
|
76
|
+
},
|
|
63
77
|
thinking: {
|
|
64
78
|
label: "Thinking",
|
|
65
79
|
ellipsis: "Thinking...",
|
package/dist/ai/types.d.ts
CHANGED
|
@@ -120,6 +120,16 @@ export interface TodoSnapshotPart {
|
|
|
120
120
|
export type MessagePart = TextPart | ReasoningPart | ToolCallPart | SubagentPart | ApprovalPart | NoticePart | TodoSnapshotPart;
|
|
121
121
|
export type MessagePartKind = MessagePart['kind'];
|
|
122
122
|
export type PseudoUserKind = 'command-input' | 'shell-input' | 'compaction';
|
|
123
|
+
/** A saved paste a user message names by its placeholder. The model got an
|
|
124
|
+
* inline paste as text; a larger one as its saved file. */
|
|
125
|
+
export interface MessagePaste {
|
|
126
|
+
placeholder: string;
|
|
127
|
+
id: number;
|
|
128
|
+
lines: number;
|
|
129
|
+
bytes: number;
|
|
130
|
+
inline?: boolean | undefined;
|
|
131
|
+
text?: string | undefined;
|
|
132
|
+
}
|
|
123
133
|
export interface ChatMessage {
|
|
124
134
|
id: string;
|
|
125
135
|
role: string;
|
|
@@ -141,6 +151,7 @@ export interface ChatMessage {
|
|
|
141
151
|
error?: string | null | undefined;
|
|
142
152
|
parts?: MessagePart[] | null | undefined;
|
|
143
153
|
pseudo?: PseudoUserKind | null | undefined;
|
|
154
|
+
pastes?: MessagePaste[] | null | undefined;
|
|
144
155
|
}
|
|
145
156
|
export interface Conversation {
|
|
146
157
|
id: string;
|
|
@@ -187,6 +198,13 @@ export interface UnreachableSession {
|
|
|
187
198
|
name: string;
|
|
188
199
|
owner: string;
|
|
189
200
|
}
|
|
201
|
+
export interface ProviderIssue {
|
|
202
|
+
provider: string;
|
|
203
|
+
provider_name: string;
|
|
204
|
+
provider_owner?: string;
|
|
205
|
+
status: 'unauthorized' | 'unreachable';
|
|
206
|
+
message?: string;
|
|
207
|
+
}
|
|
190
208
|
export type CspKind = 'openai' | 'anthropic' | 'google' | 'azure' | 'aws' | 'other';
|
|
191
209
|
export interface ProviderInfo {
|
|
192
210
|
id: string;
|
|
@@ -137,6 +137,7 @@ var DEFAULTS = {
|
|
|
137
137
|
errorTitle: "Couldn't load preview",
|
|
138
138
|
previewError: "This file couldn't be previewed in your browser.",
|
|
139
139
|
tooLarge: "This file is too large to preview in the browser.",
|
|
140
|
+
tooLargeSize: (size) => `This file is ${size}, too large to preview in the browser.`,
|
|
140
141
|
downloadInstead: "Download instead",
|
|
141
142
|
archiveTitle: "Can't preview archives",
|
|
142
143
|
archiveMessage: "Archive files can't be previewed in the browser. Download the file to view its contents.",
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type RowAction } from '../list/index.js';
|
|
1
3
|
import type { IFileExplorerClient, IFileExplorerProvider } from './lib/fileExplorer.js';
|
|
2
4
|
import type { ExplorerObject, TreeNode, TStorage } from './lib/types.js';
|
|
3
5
|
interface IFileExplorerProps {
|
|
@@ -42,6 +44,10 @@ interface IFileExplorerProps {
|
|
|
42
44
|
label: string;
|
|
43
45
|
build: (objects: ExplorerObject[]) => string;
|
|
44
46
|
} | undefined;
|
|
47
|
+
/** Host actions for a row's menu, placed after the built-in ones and before Copy. */
|
|
48
|
+
extraRowActions?: ((node: TreeNode, storage: TStorage | undefined) => RowAction[]) | undefined;
|
|
49
|
+
/** Host content for the selected folder's header, before its badges. */
|
|
50
|
+
headerAccessory?: ((node: TreeNode, storage: TStorage | undefined) => ReactNode) | undefined;
|
|
45
51
|
}
|
|
46
|
-
export default function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHierarchy, groups, groupsLoading, selectedPath, onPathChange, getProviderAndClient, treeOnly, initialExpandedPaths, onExpandedPathsChange, objectUris, downloadCommand, }: IFileExplorerProps): import("react").JSX.Element;
|
|
52
|
+
export default function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHierarchy, groups, groupsLoading, selectedPath, onPathChange, getProviderAndClient, treeOnly, initialExpandedPaths, onExpandedPathsChange, objectUris, downloadCommand, extraRowActions, headerAccessory, }: IFileExplorerProps): import("react").JSX.Element;
|
|
47
53
|
export {};
|
|
@@ -120,7 +120,7 @@ function TreeFrame({ treeOnly, sidebarWidth, onWidthChange, children }) {
|
|
|
120
120
|
children
|
|
121
121
|
});
|
|
122
122
|
}
|
|
123
|
-
function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHierarchy = false, groups = [], groupsLoading = false, selectedPath, onPathChange, getProviderAndClient, treeOnly = false, initialExpandedPaths, onExpandedPathsChange, objectUris, downloadCommand }) {
|
|
123
|
+
function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHierarchy = false, groups = [], groupsLoading = false, selectedPath, onPathChange, getProviderAndClient, treeOnly = false, initialExpandedPaths, onExpandedPathsChange, objectUris, downloadCommand, extraRowActions, headerAccessory }) {
|
|
124
124
|
const copySubmenu = useCopySubmenu();
|
|
125
125
|
const t = useStrings().fileExplorer;
|
|
126
126
|
const notify = useNotify();
|
|
@@ -636,6 +636,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
636
636
|
});
|
|
637
637
|
}
|
|
638
638
|
actions.push(...getCreateFolderActions(node, storage));
|
|
639
|
+
actions.push(...extraRowActions?.(node, storage) ?? []);
|
|
639
640
|
if (nodeCanWrite && !node.root) actions.push({
|
|
640
641
|
key: "delete",
|
|
641
642
|
label: t.preview.delete,
|
|
@@ -1021,6 +1022,7 @@ function FileExplorer({ storages, onRefresh, refreshKey, onOpenFile, showUserHie
|
|
|
1021
1022
|
children: [renderBreadcrumb(), /* @__PURE__ */ jsxs("div", {
|
|
1022
1023
|
className: "flex shrink-0 items-center gap-2",
|
|
1023
1024
|
children: [
|
|
1025
|
+
headerAccessory?.(selectedNode, findStorage(selectedNode.storageId)),
|
|
1024
1026
|
folderHasMore && /* @__PURE__ */ jsx(StatusBadge, {
|
|
1025
1027
|
variant: "muted",
|
|
1026
1028
|
className: "whitespace-nowrap tabular-nums",
|