@nextclaw/ui 0.14.1-beta.0 → 0.14.1
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/CHANGELOG.md +50 -0
- package/dist/assets/{api-C6EDHBDR.js → api-DPcvCLfa.js} +3 -3
- package/dist/assets/{channels-list-page-DKj6m1Hc.js → channels-list-page-D6jtziu4.js} +2 -2
- package/dist/assets/chat-page-BBnNJoKG.js +105 -0
- package/dist/assets/{config-split-page-CjlmJy8X.js → config-split-page-BCJhqdK9.js} +1 -1
- package/dist/assets/{confirm-dialog-B39X0pYp.js → confirm-dialog-CWh5FfK2.js} +1 -1
- package/dist/assets/{desktop-update-config-DfUQ8nly.js → desktop-update-config-DsRhBizi.js} +1 -1
- package/dist/assets/{dist-CmMro3nV.js → dist-DPrgcEC0.js} +1 -1
- package/dist/assets/{dist-BIYV2-Ia.js → dist-DsE4OsSS.js} +1 -1
- package/dist/assets/doc-browser-C8xOF_9F.js +1 -0
- package/dist/assets/{doc-browser-CDtUtcim.js → doc-browser-DgQedeAn.js} +1 -1
- package/dist/assets/{doc-browser-context-DZbC9zVB.js → doc-browser-context-B46tQGmO.js} +1 -1
- package/dist/assets/{ellipsis-DDpgTzgx.js → ellipsis-B81FStLm.js} +1 -1
- package/dist/assets/{external-link-D2cBOCqn.js → external-link-C_dmG-WJ.js} +1 -1
- package/dist/assets/index-7o1WdUbU.js +103 -0
- package/dist/assets/index-DQWmEWzk.css +1 -0
- package/dist/assets/mcp-marketplace-page-CWZN7ohK.js +1 -0
- package/dist/assets/mcp-marketplace-page-Dzull_4-.js +40 -0
- package/dist/assets/model-config-page-Cv0O3BIj.js +1 -0
- package/dist/assets/plus-Bnt56oqG.js +1 -0
- package/dist/assets/provider-scoped-model-input-Duuh7i3h.js +1 -0
- package/dist/assets/providers-config-page-CPDz0Lxc.js +1 -0
- package/dist/assets/{react-DYQXXGbH.js → react-sZz5HSCm.js} +1 -1
- package/dist/assets/remote-B0LMs8u7.js +1 -0
- package/dist/assets/rotate-cw-BfyG_KEF.js +1 -0
- package/dist/assets/runtime-config-page-q0fvv8ME.js +1 -0
- package/dist/assets/{save-BNVTgBVn.js → save-MzjoiHri.js} +1 -1
- package/dist/assets/{search-CKCzwLId.js → search-B29dE4GN.js} +1 -1
- package/dist/assets/{search-config-page-DG5Q2hF1.js → search-config-page-Dl5sZzob.js} +1 -1
- package/dist/assets/secrets-config-page-C_l-IFCK.js +3 -0
- package/dist/assets/{select-CIRwTNP4.js → select-BKmh05hd.js} +2 -2
- package/dist/assets/{tag-chip-DKhVFtTE.js → tag-chip-BS_7Ueom.js} +1 -1
- package/dist/assets/use-config-DZkBWIdL.js +1 -0
- package/dist/assets/x-CW2KO-Hz.js +1 -0
- package/dist/index.html +14 -14
- package/package.json +9 -9
- package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +12 -63
- package/src/features/chat/components/conversation/chat-conversation-content.tsx +11 -50
- package/src/features/chat/components/conversation/chat-conversation-panel.tsx +15 -2
- package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +70 -7
- package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +104 -16
- package/src/features/chat/components/layout/chat-sidebar.tsx +21 -2
- package/src/features/chat/features/input/components/chat-input-bar.container.tsx +84 -6
- package/src/features/chat/features/input/hooks/use-chat-input-bar-query-state.ts +2 -0
- package/src/features/chat/features/input/hooks/use-chat-model-favorites.ts +88 -0
- package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +64 -24
- package/src/features/chat/features/input/utils/__tests__/ncp-chat-input-availability.utils.test.ts +1 -0
- package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +21 -12
- package/src/features/chat/features/input/utils/chat-input-toolbar.utils.ts +80 -25
- package/src/features/chat/features/message/utils/__tests__/chat-message-file-operation.utils.test.ts +310 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message-test.utils.ts +43 -0
- package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +1 -346
- package/src/features/chat/features/message/utils/chat-message-tool-card.utils.ts +10 -9
- package/src/features/chat/features/message/utils/file-operation/diff.utils.ts +39 -36
- package/src/features/chat/features/message/utils/file-operation/line-builder.utils.ts +46 -38
- package/src/features/chat/features/message/utils/file-operation/parsed-block.utils.ts +18 -15
- package/src/features/chat/features/session/components/chat-sidebar-project-groups.tsx +3 -2
- package/src/features/chat/features/session/components/session-header/__tests__/chat-session-project-badge.test.tsx +25 -25
- package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +15 -2
- package/src/features/chat/features/session-type/components/__tests__/chat-session-type-menu.test.tsx +24 -0
- package/src/features/chat/features/session-type/components/__tests__/chat-session-type-option-item.test.tsx +27 -1
- package/src/features/chat/features/session-type/components/{chat-sidebar-create-menu.tsx → chat-session-type-menu.tsx} +17 -4
- package/src/features/chat/features/session-type/components/chat-session-type-option-item.tsx +15 -3
- package/src/features/chat/features/session-type/hooks/__tests__/use-chat-new-session-type-preference.test.tsx +152 -0
- package/src/features/chat/features/session-type/hooks/use-chat-new-session-type-preference.ts +159 -0
- package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +193 -0
- package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +123 -0
- package/src/features/chat/features/welcome/components/chat-conversation-welcome.tsx +104 -0
- package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +72 -0
- package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +54 -0
- package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +132 -0
- package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +82 -0
- package/src/features/chat/features/welcome/components/chat-welcome.tsx +121 -0
- package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +53 -0
- package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-selection.utils.test.ts +63 -0
- package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-visibility.utils.test.ts +34 -0
- package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +71 -0
- package/src/features/chat/features/welcome/utils/chat-welcome-selection.utils.ts +79 -0
- package/src/features/chat/features/welcome/utils/chat-welcome-visibility.utils.ts +17 -0
- package/src/features/chat/managers/__tests__/chat-input.manager.test.ts +65 -0
- package/src/features/chat/managers/chat-input.manager.ts +24 -2
- package/src/features/chat/stores/chat-input.store.ts +2 -0
- package/src/features/chat/types/chat-input-bar.types.ts +5 -0
- package/src/features/marketplace/components/__tests__/marketplace-page.test.tsx +24 -0
- package/src/features/marketplace/components/curated-shelves/marketplace-shelf-card.tsx +8 -3
- package/src/features/marketplace/components/marketplace-item-list-view.tsx +2 -2
- package/src/features/marketplace/components/marketplace-list-card.tsx +124 -98
- package/src/features/marketplace/components/marketplace-page-parts.tsx +11 -6
- package/src/features/marketplace/utils/marketplace-installed-cache.utils.ts +27 -23
- package/src/features/settings/components/config/provider-form.tsx +284 -387
- package/src/features/settings/components/config/provider-status-badge.tsx +4 -4
- package/src/features/settings/components/config/secrets-config-form.tsx +11 -135
- package/src/features/settings/hooks/use-provider-auth-flow.ts +140 -0
- package/src/features/settings/pages/__tests__/model-config-page.test.tsx +16 -0
- package/src/features/settings/pages/secrets-config-page.tsx +2 -1
- package/src/features/settings/utils/__tests__/provider-form-support.utils.test.ts +60 -1
- package/src/features/settings/utils/__tests__/secrets-config-form.utils.test.ts +66 -0
- package/src/features/settings/utils/provider-form-context.utils.ts +131 -0
- package/src/features/settings/utils/provider-form-model.utils.ts +101 -0
- package/src/features/settings/utils/provider-form-support.utils.ts +102 -1
- package/src/features/settings/utils/secrets-config-form.utils.ts +139 -0
- package/src/features/system-status/components/config/runtime-agent-list-card.tsx +17 -11
- package/src/features/system-status/components/config/runtime-binding-list-card.tsx +14 -9
- package/src/features/system-status/components/config/runtime-config-editor.tsx +8 -5
- package/src/features/system-status/components/config/runtime-entry-list-card.tsx +14 -9
- package/src/features/system-status/components/config/runtime-settings-card.tsx +16 -7
- package/src/features/system-status/components/runtime-security-card.tsx +79 -25
- package/src/shared/components/common/__tests__/agent-avatar.test.tsx +3 -1
- package/src/shared/components/common/__tests__/searchable-model-input.test.tsx +34 -0
- package/src/shared/components/common/agent-avatar.tsx +5 -2
- package/src/shared/components/common/provider-scoped-model-input.tsx +1 -0
- package/src/shared/components/common/searchable-model-input.tsx +102 -87
- package/src/shared/components/ui/README.md +7 -0
- package/src/shared/components/ui/popover.tsx +19 -3
- package/src/shared/components/ui/select.tsx +14 -4
- package/src/shared/hooks/use-infinite-scroll-loader.ts +14 -10
- package/src/shared/lib/api/index.ts +3 -0
- package/src/shared/lib/api/preferences/preference-keys.config.ts +9 -0
- package/src/shared/lib/api/preferences/preference.types.ts +22 -0
- package/src/shared/lib/api/preferences/preference.utils.ts +40 -0
- package/src/shared/lib/i18n/locales/en-US/chat.json +16 -1
- package/src/shared/lib/i18n/locales/en-US/core.json +1 -0
- package/src/shared/lib/i18n/locales/en-US/marketplace.json +2 -0
- package/src/shared/lib/i18n/locales/zh-CN/chat.json +16 -1
- package/src/shared/lib/i18n/locales/zh-CN/core.json +1 -0
- package/src/shared/lib/i18n/locales/zh-CN/marketplace.json +2 -0
- package/dist/assets/chat-page-BXngLJY6.js +0 -105
- package/dist/assets/doc-browser-B6Wc_TIY.js +0 -1
- package/dist/assets/index-DTb_XVIH.js +0 -103
- package/dist/assets/index-ayiPYhf1.css +0 -1
- package/dist/assets/mcp-marketplace-page-DZ9H0v05.js +0 -1
- package/dist/assets/mcp-marketplace-page-cnVWf7QQ.js +0 -40
- package/dist/assets/model-config-page-Bcb35AVv.js +0 -1
- package/dist/assets/plus-CYE1onU2.js +0 -1
- package/dist/assets/provider-scoped-model-input-kdoNp4HG.js +0 -1
- package/dist/assets/providers-config-page-CQSibSwO.js +0 -1
- package/dist/assets/remote-BlJDbXOI.js +0 -1
- package/dist/assets/rotate-cw-BWbvFDAX.js +0 -1
- package/dist/assets/runtime-config-page-Tn2yLQ0c.js +0 -1
- package/dist/assets/secrets-config-page-BXn5Uuwp.js +0 -3
- package/dist/assets/use-config-DgKM9VIU.js +0 -1
- package/dist/assets/x-Cm3JjYut.js +0 -1
- package/src/features/chat/components/__tests__/chat-welcome.test.tsx +0 -45
- package/src/features/chat/components/chat-welcome.tsx +0 -102
|
@@ -150,17 +150,18 @@ export function resolveToolCardStatus(params: {
|
|
|
150
150
|
ChatToolPartViewModel,
|
|
151
151
|
"kind" | "hasResult" | "statusTone" | "statusLabel"
|
|
152
152
|
> {
|
|
153
|
+
const { cancelled, error, result, status, texts } = params;
|
|
153
154
|
const rawStatus =
|
|
154
|
-
typeof
|
|
155
|
+
typeof status === "string" ? status.trim().toLowerCase() : "";
|
|
155
156
|
const hasError =
|
|
156
|
-
typeof
|
|
157
|
-
const isCancelled =
|
|
157
|
+
typeof error === "string" && error.trim().length > 0;
|
|
158
|
+
const isCancelled = cancelled === true || rawStatus === "cancelled";
|
|
158
159
|
if (isCancelled) {
|
|
159
160
|
return {
|
|
160
161
|
kind: "result",
|
|
161
162
|
hasResult: true,
|
|
162
163
|
statusTone: "cancelled",
|
|
163
|
-
statusLabel:
|
|
164
|
+
statusLabel: texts.toolStatusCancelledLabel,
|
|
164
165
|
};
|
|
165
166
|
}
|
|
166
167
|
if (hasError || rawStatus === "error") {
|
|
@@ -168,15 +169,15 @@ export function resolveToolCardStatus(params: {
|
|
|
168
169
|
kind: "result",
|
|
169
170
|
hasResult: true,
|
|
170
171
|
statusTone: "error",
|
|
171
|
-
statusLabel:
|
|
172
|
+
statusLabel: texts.toolStatusFailedLabel,
|
|
172
173
|
};
|
|
173
174
|
}
|
|
174
|
-
if (rawStatus === "result" ||
|
|
175
|
+
if (rawStatus === "result" || result != null) {
|
|
175
176
|
return {
|
|
176
177
|
kind: "result",
|
|
177
178
|
hasResult: true,
|
|
178
179
|
statusTone: "success",
|
|
179
|
-
statusLabel:
|
|
180
|
+
statusLabel: texts.toolStatusCompletedLabel,
|
|
180
181
|
};
|
|
181
182
|
}
|
|
182
183
|
if (rawStatus === "partial-call") {
|
|
@@ -184,14 +185,14 @@ export function resolveToolCardStatus(params: {
|
|
|
184
185
|
kind: "call",
|
|
185
186
|
hasResult: false,
|
|
186
187
|
statusTone: "running",
|
|
187
|
-
statusLabel:
|
|
188
|
+
statusLabel: texts.toolStatusRunningLabel,
|
|
188
189
|
};
|
|
189
190
|
}
|
|
190
191
|
return {
|
|
191
192
|
kind: "call",
|
|
192
193
|
hasResult: false,
|
|
193
194
|
statusTone: "running",
|
|
194
|
-
statusLabel:
|
|
195
|
+
statusLabel: texts.toolStatusRunningLabel,
|
|
195
196
|
};
|
|
196
197
|
}
|
|
197
198
|
|
|
@@ -24,16 +24,17 @@ export function buildRawPreviewBlock(params: {
|
|
|
24
24
|
oldStartLine?: number | null;
|
|
25
25
|
newStartLine?: number | null;
|
|
26
26
|
}): ParsedBlock | null {
|
|
27
|
-
const
|
|
27
|
+
const { newStartLine: rawNewStartLine, oldStartLine: rawOldStartLine, operation, path, text } = params;
|
|
28
|
+
const previewText = text.trim();
|
|
28
29
|
if (!previewText) {
|
|
29
30
|
return null;
|
|
30
31
|
}
|
|
31
32
|
const previewKind =
|
|
32
|
-
|
|
33
|
+
operation?.trim().toLowerCase() === "write" ? "add" : "context";
|
|
33
34
|
const oldStartLine =
|
|
34
|
-
typeof
|
|
35
|
+
typeof rawOldStartLine === "number" ? rawOldStartLine : 1;
|
|
35
36
|
const newStartLine =
|
|
36
|
-
typeof
|
|
37
|
+
typeof rawNewStartLine === "number" ? rawNewStartLine : 1;
|
|
37
38
|
const lines = buildPreviewLines({
|
|
38
39
|
text: previewText,
|
|
39
40
|
kind: previewKind,
|
|
@@ -41,10 +42,10 @@ export function buildRawPreviewBlock(params: {
|
|
|
41
42
|
newStartLine,
|
|
42
43
|
});
|
|
43
44
|
return {
|
|
44
|
-
path
|
|
45
|
+
path,
|
|
45
46
|
display: "preview",
|
|
46
47
|
caption: buildCaption({
|
|
47
|
-
operation
|
|
48
|
+
operation,
|
|
48
49
|
lines,
|
|
49
50
|
}),
|
|
50
51
|
lines,
|
|
@@ -62,10 +63,11 @@ export function buildFullReplaceBlock(params: {
|
|
|
62
63
|
oldStartLine?: number | null;
|
|
63
64
|
newStartLine?: number | null;
|
|
64
65
|
}): ParsedBlock | null {
|
|
66
|
+
const { afterText, beforeText, operation, path } = params;
|
|
65
67
|
const { oldStartLine, newStartLine } = readDefaultDiffStartLines(params);
|
|
66
68
|
const lines = buildLineDiff({
|
|
67
|
-
beforeText:
|
|
68
|
-
afterText:
|
|
69
|
+
beforeText: beforeText ?? "",
|
|
70
|
+
afterText: afterText ?? "",
|
|
69
71
|
oldStartLine,
|
|
70
72
|
newStartLine,
|
|
71
73
|
});
|
|
@@ -74,16 +76,16 @@ export function buildFullReplaceBlock(params: {
|
|
|
74
76
|
return null;
|
|
75
77
|
}
|
|
76
78
|
return {
|
|
77
|
-
path
|
|
79
|
+
path,
|
|
78
80
|
display: "diff",
|
|
79
81
|
caption: buildCaption({
|
|
80
|
-
operation
|
|
82
|
+
operation,
|
|
81
83
|
lines,
|
|
82
84
|
}),
|
|
83
85
|
lines: limited.lines,
|
|
84
86
|
...(limited.truncated ? { fullLines: lines } : {}),
|
|
85
|
-
...(
|
|
86
|
-
...(
|
|
87
|
+
...(beforeText != null ? { beforeText } : {}),
|
|
88
|
+
...(afterText != null ? { afterText } : {}),
|
|
87
89
|
...(typeof oldStartLine === "number" ? { oldStartLine } : {}),
|
|
88
90
|
...(typeof newStartLine === "number" ? { newStartLine } : {}),
|
|
89
91
|
truncated: limited.truncated,
|
|
@@ -95,23 +97,24 @@ function updateApplyPatchCursor(params: {
|
|
|
95
97
|
flushCurrent: () => void;
|
|
96
98
|
setCurrent: (path: string, operation: string) => void;
|
|
97
99
|
}): boolean {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
100
|
+
const { flushCurrent, line, setCurrent } = params;
|
|
101
|
+
if (line.startsWith("*** Update File: ")) {
|
|
102
|
+
flushCurrent();
|
|
103
|
+
setCurrent(
|
|
104
|
+
line.slice("*** Update File: ".length).trim(),
|
|
102
105
|
"update",
|
|
103
106
|
);
|
|
104
107
|
return true;
|
|
105
108
|
}
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
+
if (line.startsWith("*** Add File: ")) {
|
|
110
|
+
flushCurrent();
|
|
111
|
+
setCurrent(line.slice("*** Add File: ".length).trim(), "add");
|
|
109
112
|
return true;
|
|
110
113
|
}
|
|
111
|
-
if (
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
114
|
+
if (line.startsWith("*** Delete File: ")) {
|
|
115
|
+
flushCurrent();
|
|
116
|
+
setCurrent(
|
|
117
|
+
line.slice("*** Delete File: ".length).trim(),
|
|
115
118
|
"delete",
|
|
116
119
|
);
|
|
117
120
|
return true;
|
|
@@ -128,18 +131,18 @@ function appendPatchLine(params: {
|
|
|
128
131
|
oldLineNumber?: number;
|
|
129
132
|
newLineNumber?: number;
|
|
130
133
|
} {
|
|
131
|
-
const { currentLines, line } = params;
|
|
134
|
+
const { currentLines, line, newLineNumber, oldLineNumber } = params;
|
|
132
135
|
if (line.startsWith("+")) {
|
|
133
136
|
currentLines.push(
|
|
134
137
|
createLine({
|
|
135
138
|
kind: "add",
|
|
136
139
|
text: line.slice(1),
|
|
137
|
-
newLineNumber
|
|
140
|
+
newLineNumber,
|
|
138
141
|
}),
|
|
139
142
|
);
|
|
140
143
|
return {
|
|
141
|
-
oldLineNumber
|
|
142
|
-
newLineNumber: incrementLineNumber(
|
|
144
|
+
oldLineNumber,
|
|
145
|
+
newLineNumber: incrementLineNumber(newLineNumber),
|
|
143
146
|
};
|
|
144
147
|
}
|
|
145
148
|
if (line.startsWith("-")) {
|
|
@@ -147,12 +150,12 @@ function appendPatchLine(params: {
|
|
|
147
150
|
createLine({
|
|
148
151
|
kind: "remove",
|
|
149
152
|
text: line.slice(1),
|
|
150
|
-
oldLineNumber
|
|
153
|
+
oldLineNumber,
|
|
151
154
|
}),
|
|
152
155
|
);
|
|
153
156
|
return {
|
|
154
|
-
oldLineNumber: incrementLineNumber(
|
|
155
|
-
newLineNumber
|
|
157
|
+
oldLineNumber: incrementLineNumber(oldLineNumber),
|
|
158
|
+
newLineNumber,
|
|
156
159
|
};
|
|
157
160
|
}
|
|
158
161
|
if (line.startsWith(" ")) {
|
|
@@ -160,18 +163,18 @@ function appendPatchLine(params: {
|
|
|
160
163
|
createLine({
|
|
161
164
|
kind: "context",
|
|
162
165
|
text: line.slice(1),
|
|
163
|
-
oldLineNumber
|
|
164
|
-
newLineNumber
|
|
166
|
+
oldLineNumber,
|
|
167
|
+
newLineNumber,
|
|
165
168
|
}),
|
|
166
169
|
);
|
|
167
170
|
return {
|
|
168
|
-
oldLineNumber: incrementLineNumber(
|
|
169
|
-
newLineNumber: incrementLineNumber(
|
|
171
|
+
oldLineNumber: incrementLineNumber(oldLineNumber),
|
|
172
|
+
newLineNumber: incrementLineNumber(newLineNumber),
|
|
170
173
|
};
|
|
171
174
|
}
|
|
172
175
|
return {
|
|
173
|
-
oldLineNumber
|
|
174
|
-
newLineNumber
|
|
176
|
+
oldLineNumber,
|
|
177
|
+
newLineNumber,
|
|
175
178
|
};
|
|
176
179
|
}
|
|
177
180
|
|
|
@@ -19,14 +19,15 @@ export function createLine(params: {
|
|
|
19
19
|
oldLineNumber?: number;
|
|
20
20
|
newLineNumber?: number;
|
|
21
21
|
}): ChatFileOperationLineViewModel {
|
|
22
|
+
const { kind, newLineNumber, oldLineNumber, text } = params;
|
|
22
23
|
return {
|
|
23
|
-
kind
|
|
24
|
-
text
|
|
25
|
-
...(typeof
|
|
26
|
-
? { oldLineNumber
|
|
24
|
+
kind,
|
|
25
|
+
text,
|
|
26
|
+
...(typeof oldLineNumber === "number"
|
|
27
|
+
? { oldLineNumber }
|
|
27
28
|
: {}),
|
|
28
|
-
...(typeof
|
|
29
|
-
? { newLineNumber
|
|
29
|
+
...(typeof newLineNumber === "number"
|
|
30
|
+
? { newLineNumber }
|
|
30
31
|
: {}),
|
|
31
32
|
};
|
|
32
33
|
}
|
|
@@ -51,18 +52,19 @@ export function buildPreviewLines(params: {
|
|
|
51
52
|
oldStartLine: number;
|
|
52
53
|
newStartLine: number;
|
|
53
54
|
}): ChatFileOperationLineViewModel[] {
|
|
54
|
-
|
|
55
|
-
|
|
55
|
+
const { kind, newStartLine, oldStartLine, text } = params;
|
|
56
|
+
return splitLines(text).map((line, index) =>
|
|
57
|
+
kind === "add"
|
|
56
58
|
? createLine({
|
|
57
59
|
kind: "add",
|
|
58
60
|
text: line,
|
|
59
|
-
newLineNumber:
|
|
61
|
+
newLineNumber: newStartLine + index,
|
|
60
62
|
})
|
|
61
63
|
: createLine({
|
|
62
64
|
kind: "context",
|
|
63
65
|
text: line,
|
|
64
|
-
oldLineNumber:
|
|
65
|
-
newLineNumber:
|
|
66
|
+
oldLineNumber: oldStartLine + index,
|
|
67
|
+
newLineNumber: newStartLine + index,
|
|
66
68
|
}),
|
|
67
69
|
);
|
|
68
70
|
}
|
|
@@ -73,10 +75,11 @@ function buildFallbackDiffLines(params: {
|
|
|
73
75
|
oldStartLine?: number;
|
|
74
76
|
newStartLine?: number;
|
|
75
77
|
}): ChatFileOperationLineViewModel[] {
|
|
76
|
-
|
|
77
|
-
let
|
|
78
|
+
const { afterLines, beforeLines, newStartLine, oldStartLine } = params;
|
|
79
|
+
let oldLineNumber = oldStartLine;
|
|
80
|
+
let newLineNumber = newStartLine;
|
|
78
81
|
return [
|
|
79
|
-
...
|
|
82
|
+
...beforeLines.map((line) => {
|
|
80
83
|
const nextLine = createLine({
|
|
81
84
|
kind: "remove",
|
|
82
85
|
text: line,
|
|
@@ -85,7 +88,7 @@ function buildFallbackDiffLines(params: {
|
|
|
85
88
|
oldLineNumber = incrementLineNumber(oldLineNumber);
|
|
86
89
|
return nextLine;
|
|
87
90
|
}),
|
|
88
|
-
...
|
|
91
|
+
...afterLines.map((line) => {
|
|
89
92
|
const nextLine = createLine({
|
|
90
93
|
kind: "add",
|
|
91
94
|
text: line,
|
|
@@ -101,22 +104,23 @@ function buildLcsMatrix(params: {
|
|
|
101
104
|
beforeLines: string[];
|
|
102
105
|
afterLines: string[];
|
|
103
106
|
}): number[][] {
|
|
107
|
+
const { afterLines, beforeLines } = params;
|
|
104
108
|
const matrix: number[][] = Array.from(
|
|
105
|
-
{ length:
|
|
106
|
-
() => Array.from({ length:
|
|
109
|
+
{ length: beforeLines.length + 1 },
|
|
110
|
+
() => Array.from({ length: afterLines.length + 1 }, () => 0),
|
|
107
111
|
);
|
|
108
112
|
for (
|
|
109
|
-
let beforeIndex =
|
|
113
|
+
let beforeIndex = beforeLines.length - 1;
|
|
110
114
|
beforeIndex >= 0;
|
|
111
115
|
beforeIndex -= 1
|
|
112
116
|
) {
|
|
113
117
|
for (
|
|
114
|
-
let afterIndex =
|
|
118
|
+
let afterIndex = afterLines.length - 1;
|
|
115
119
|
afterIndex >= 0;
|
|
116
120
|
afterIndex -= 1
|
|
117
121
|
) {
|
|
118
122
|
matrix[beforeIndex]![afterIndex] =
|
|
119
|
-
|
|
123
|
+
beforeLines[beforeIndex] === afterLines[afterIndex]
|
|
120
124
|
? (matrix[beforeIndex + 1]![afterIndex + 1] ?? 0) + 1
|
|
121
125
|
: Math.max(
|
|
122
126
|
matrix[beforeIndex + 1]![afterIndex] ?? 0,
|
|
@@ -136,33 +140,36 @@ function appendRemainingDiffLines(params: {
|
|
|
136
140
|
oldLineNumber?: number;
|
|
137
141
|
newLineNumber?: number;
|
|
138
142
|
}): void {
|
|
143
|
+
const { afterIndex, afterLines, beforeIndex, beforeLines, lines, newLineNumber: initialNewLineNumber, oldLineNumber: initialOldLineNumber } = params;
|
|
144
|
+
let oldLineNumber = initialOldLineNumber;
|
|
145
|
+
let newLineNumber = initialNewLineNumber;
|
|
139
146
|
for (
|
|
140
|
-
let index =
|
|
141
|
-
index <
|
|
147
|
+
let index = beforeIndex;
|
|
148
|
+
index < beforeLines.length;
|
|
142
149
|
index += 1
|
|
143
150
|
) {
|
|
144
|
-
|
|
151
|
+
lines.push(
|
|
145
152
|
createLine({
|
|
146
153
|
kind: "remove",
|
|
147
|
-
text:
|
|
148
|
-
oldLineNumber
|
|
154
|
+
text: beforeLines[index] ?? "",
|
|
155
|
+
oldLineNumber,
|
|
149
156
|
}),
|
|
150
157
|
);
|
|
151
|
-
|
|
158
|
+
oldLineNumber = incrementLineNumber(oldLineNumber);
|
|
152
159
|
}
|
|
153
160
|
for (
|
|
154
|
-
let index =
|
|
155
|
-
index <
|
|
161
|
+
let index = afterIndex;
|
|
162
|
+
index < afterLines.length;
|
|
156
163
|
index += 1
|
|
157
164
|
) {
|
|
158
|
-
|
|
165
|
+
lines.push(
|
|
159
166
|
createLine({
|
|
160
167
|
kind: "add",
|
|
161
|
-
text:
|
|
162
|
-
newLineNumber
|
|
168
|
+
text: afterLines[index] ?? "",
|
|
169
|
+
newLineNumber,
|
|
163
170
|
}),
|
|
164
171
|
);
|
|
165
|
-
|
|
172
|
+
newLineNumber = incrementLineNumber(newLineNumber);
|
|
166
173
|
}
|
|
167
174
|
}
|
|
168
175
|
|
|
@@ -172,14 +179,15 @@ export function buildLineDiff(params: {
|
|
|
172
179
|
oldStartLine?: number;
|
|
173
180
|
newStartLine?: number;
|
|
174
181
|
}): ChatFileOperationLineViewModel[] {
|
|
175
|
-
const
|
|
176
|
-
const
|
|
182
|
+
const { afterText, beforeText, newStartLine, oldStartLine } = params;
|
|
183
|
+
const beforeLines = splitLines(beforeText);
|
|
184
|
+
const afterLines = splitLines(afterText);
|
|
177
185
|
if (beforeLines.length * afterLines.length > MAX_DIFF_MATRIX_CELLS) {
|
|
178
186
|
return buildFallbackDiffLines({
|
|
179
187
|
beforeLines,
|
|
180
188
|
afterLines,
|
|
181
|
-
oldStartLine
|
|
182
|
-
newStartLine
|
|
189
|
+
oldStartLine,
|
|
190
|
+
newStartLine,
|
|
183
191
|
});
|
|
184
192
|
}
|
|
185
193
|
|
|
@@ -190,8 +198,8 @@ export function buildLineDiff(params: {
|
|
|
190
198
|
const lines: ChatFileOperationLineViewModel[] = [];
|
|
191
199
|
let beforeIndex = 0;
|
|
192
200
|
let afterIndex = 0;
|
|
193
|
-
let oldLineNumber =
|
|
194
|
-
let newLineNumber =
|
|
201
|
+
let oldLineNumber = oldStartLine;
|
|
202
|
+
let newLineNumber = newStartLine;
|
|
195
203
|
while (beforeIndex < beforeLines.length && afterIndex < afterLines.length) {
|
|
196
204
|
if (beforeLines[beforeIndex] === afterLines[afterIndex]) {
|
|
197
205
|
lines.push(
|
|
@@ -20,10 +20,11 @@ export function buildCaption(params: {
|
|
|
20
20
|
operation?: string | null;
|
|
21
21
|
lines: ChatFileOperationLineViewModel[];
|
|
22
22
|
}): string | undefined {
|
|
23
|
-
const
|
|
24
|
-
const
|
|
23
|
+
const { lines, operation } = params;
|
|
24
|
+
const additions = lines.filter((line) => line.kind === "add").length;
|
|
25
|
+
const deletions = lines.filter((line) => line.kind === "remove").length;
|
|
25
26
|
const parts: string[] = [];
|
|
26
|
-
const normalizedOperation =
|
|
27
|
+
const normalizedOperation = operation?.trim().toLowerCase() ?? "";
|
|
27
28
|
if (normalizedOperation && normalizedOperation !== "update") {
|
|
28
29
|
parts.push(normalizedOperation);
|
|
29
30
|
}
|
|
@@ -46,20 +47,21 @@ export function readDefaultDiffStartLines(params: {
|
|
|
46
47
|
oldStartLine?: number;
|
|
47
48
|
newStartLine?: number;
|
|
48
49
|
} {
|
|
49
|
-
const
|
|
50
|
+
const { afterText, beforeText, newStartLine: rawNewStartLine, oldStartLine: rawOldStartLine, operation } = params;
|
|
51
|
+
const normalizedOperation = operation?.trim().toLowerCase() ?? "";
|
|
50
52
|
const oldStartLine =
|
|
51
|
-
typeof
|
|
52
|
-
?
|
|
53
|
+
typeof rawOldStartLine === "number"
|
|
54
|
+
? rawOldStartLine
|
|
53
55
|
: (normalizedOperation === "delete" ||
|
|
54
56
|
normalizedOperation === "remove") &&
|
|
55
|
-
|
|
57
|
+
beforeText != null
|
|
56
58
|
? 1
|
|
57
59
|
: undefined;
|
|
58
60
|
const newStartLine =
|
|
59
|
-
typeof
|
|
60
|
-
?
|
|
61
|
+
typeof rawNewStartLine === "number"
|
|
62
|
+
? rawNewStartLine
|
|
61
63
|
: (normalizedOperation === "write" || normalizedOperation === "add") &&
|
|
62
|
-
|
|
64
|
+
afterText != null
|
|
63
65
|
? 1
|
|
64
66
|
: undefined;
|
|
65
67
|
return { oldStartLine, newStartLine };
|
|
@@ -83,16 +85,17 @@ export function buildParsedPatchBlock(params: {
|
|
|
83
85
|
operation: string | null;
|
|
84
86
|
lines: ChatFileOperationLineViewModel[];
|
|
85
87
|
}): ParsedBlock {
|
|
86
|
-
const
|
|
88
|
+
const { lines, operation, path } = params;
|
|
89
|
+
const limited = limitLines(lines);
|
|
87
90
|
return {
|
|
88
|
-
path
|
|
91
|
+
path,
|
|
89
92
|
display: "diff",
|
|
90
93
|
caption: buildCaption({
|
|
91
|
-
operation
|
|
92
|
-
lines
|
|
94
|
+
operation,
|
|
95
|
+
lines,
|
|
93
96
|
}),
|
|
94
97
|
lines: limited.lines,
|
|
95
|
-
...(limited.truncated ? { fullLines:
|
|
98
|
+
...(limited.truncated ? { fullLines: lines } : {}),
|
|
96
99
|
truncated: limited.truncated,
|
|
97
100
|
};
|
|
98
101
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useMemo, useState, type ReactNode } from 'react';
|
|
2
2
|
import { Plus } from 'lucide-react';
|
|
3
|
-
import {
|
|
3
|
+
import { ChatSessionTypeMenu } from "@/features/chat/features/session-type/components/chat-session-type-menu";
|
|
4
4
|
import { Popover, PopoverContent, PopoverTrigger } from '@/shared/components/ui/popover';
|
|
5
5
|
import { IconActionButton } from '@/shared/components/ui/actions/icon-action-button';
|
|
6
6
|
import type { ChatInputSnapshot } from '@/features/chat/stores/chat-input.store';
|
|
@@ -77,8 +77,9 @@ export function ChatSidebarProjectGroups(props: ChatSidebarProjectGroupsProps) {
|
|
|
77
77
|
align="end"
|
|
78
78
|
className="w-56 rounded-2xl border border-gray-200/80 bg-white p-1.5 shadow-[0_24px_60px_-28px_rgba(15,23,42,0.38)]"
|
|
79
79
|
>
|
|
80
|
-
<
|
|
80
|
+
<ChatSessionTypeMenu
|
|
81
81
|
options={sessionTypeOptions}
|
|
82
|
+
selectedSessionType={preferredSessionType}
|
|
82
83
|
onSelect={(sessionType) => {
|
|
83
84
|
onCreateSession(sessionType, group.projectRoot);
|
|
84
85
|
setOpenProjectRoot(null);
|
|
@@ -2,6 +2,7 @@ import { render, screen, waitFor } from '@testing-library/react';
|
|
|
2
2
|
import userEvent from '@testing-library/user-event';
|
|
3
3
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
4
4
|
import { ChatSessionProjectBadge } from '@/features/chat/features/session/components/session-header/chat-session-project-badge';
|
|
5
|
+
import { createPopoverAvailableHeightLimit } from '@/shared/components/ui/popover';
|
|
5
6
|
|
|
6
7
|
const mocks = vi.hoisted(() => ({
|
|
7
8
|
updateSessionProject: vi.fn(),
|
|
@@ -15,6 +16,17 @@ vi.mock('@/features/chat/features/session/components/session-header/chat-session
|
|
|
15
16
|
ChatSessionProjectDialog: () => null,
|
|
16
17
|
}));
|
|
17
18
|
|
|
19
|
+
function renderProjectBadge() {
|
|
20
|
+
return render(
|
|
21
|
+
<ChatSessionProjectBadge
|
|
22
|
+
sessionKey="session-1"
|
|
23
|
+
projectName="project-alpha"
|
|
24
|
+
projectRoot="/tmp/project-alpha"
|
|
25
|
+
persistToServer
|
|
26
|
+
/>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
18
30
|
describe('ChatSessionProjectBadge', () => {
|
|
19
31
|
beforeEach(() => {
|
|
20
32
|
mocks.updateSessionProject.mockReset();
|
|
@@ -24,31 +36,26 @@ describe('ChatSessionProjectBadge', () => {
|
|
|
24
36
|
it('shows project actions inside the badge popover', async () => {
|
|
25
37
|
const user = userEvent.setup();
|
|
26
38
|
|
|
27
|
-
|
|
28
|
-
<ChatSessionProjectBadge
|
|
29
|
-
sessionKey="session-1"
|
|
30
|
-
projectName="project-alpha"
|
|
31
|
-
projectRoot="/tmp/project-alpha"
|
|
32
|
-
persistToServer
|
|
33
|
-
/>
|
|
34
|
-
);
|
|
39
|
+
renderProjectBadge();
|
|
35
40
|
|
|
36
41
|
await user.click(screen.getByRole('button', { name: 'Set Project Directory' }));
|
|
37
42
|
|
|
38
43
|
expect(screen.getAllByText('Set Project Directory').length).toBeGreaterThan(0);
|
|
39
44
|
expect(screen.getByText('Clear Project Directory')).toBeTruthy();
|
|
40
|
-
|
|
45
|
+
const projectRoot = screen.getByText('/tmp/project-alpha');
|
|
46
|
+
expect(projectRoot).toBeTruthy();
|
|
47
|
+
|
|
48
|
+
const boundedPopover = projectRoot.closest('[style*="max-height"]') as HTMLElement | null;
|
|
49
|
+
expect(boundedPopover?.style.maxHeight).toBe(
|
|
50
|
+
createPopoverAvailableHeightLimit('18rem')
|
|
51
|
+
);
|
|
52
|
+
expect(boundedPopover?.style.maxHeight).toContain('max(0px');
|
|
53
|
+
expect(boundedPopover?.style.maxHeight).toContain('100vh');
|
|
54
|
+
expect(boundedPopover?.style.maxHeight).toContain('2rem');
|
|
41
55
|
});
|
|
42
56
|
|
|
43
57
|
it('uses the neutral header tag styling instead of a highlighted accent color', () => {
|
|
44
|
-
|
|
45
|
-
<ChatSessionProjectBadge
|
|
46
|
-
sessionKey="session-1"
|
|
47
|
-
projectName="project-alpha"
|
|
48
|
-
projectRoot="/tmp/project-alpha"
|
|
49
|
-
persistToServer
|
|
50
|
-
/>
|
|
51
|
-
);
|
|
58
|
+
renderProjectBadge();
|
|
52
59
|
|
|
53
60
|
const trigger = screen.getByRole('button', { name: 'Set Project Directory' });
|
|
54
61
|
expect(trigger.className).toContain('border-gray-200');
|
|
@@ -59,14 +66,7 @@ describe('ChatSessionProjectBadge', () => {
|
|
|
59
66
|
it('clears the current project from the badge popover', async () => {
|
|
60
67
|
const user = userEvent.setup();
|
|
61
68
|
|
|
62
|
-
|
|
63
|
-
<ChatSessionProjectBadge
|
|
64
|
-
sessionKey="session-1"
|
|
65
|
-
projectName="project-alpha"
|
|
66
|
-
projectRoot="/tmp/project-alpha"
|
|
67
|
-
persistToServer
|
|
68
|
-
/>
|
|
69
|
-
);
|
|
69
|
+
renderProjectBadge();
|
|
70
70
|
|
|
71
71
|
await user.click(screen.getByRole('button', { name: 'Set Project Directory' }));
|
|
72
72
|
await user.click(screen.getByText('Clear Project Directory'));
|
package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx
CHANGED
|
@@ -3,7 +3,12 @@ import { ChevronDown, FolderOpen, FolderX, Pencil } from 'lucide-react';
|
|
|
3
3
|
import { useChatSessionProject } from '@/features/chat/features/session/hooks/use-chat-session-project';
|
|
4
4
|
import { ChatSessionHeaderMenuItem } from './chat-session-header-menu-item';
|
|
5
5
|
import { ChatSessionProjectDialog } from './chat-session-project-dialog';
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
Popover,
|
|
8
|
+
PopoverContent,
|
|
9
|
+
PopoverTrigger,
|
|
10
|
+
createPopoverAvailableHeightLimit,
|
|
11
|
+
} from '@/shared/components/ui/popover';
|
|
7
12
|
import { t } from '@/shared/lib/i18n';
|
|
8
13
|
|
|
9
14
|
type ChatSessionProjectBadgeProps = {
|
|
@@ -13,6 +18,10 @@ type ChatSessionProjectBadgeProps = {
|
|
|
13
18
|
persistToServer: boolean;
|
|
14
19
|
};
|
|
15
20
|
|
|
21
|
+
const SESSION_PROJECT_MENU_STYLE = {
|
|
22
|
+
maxHeight: createPopoverAvailableHeightLimit('18rem'),
|
|
23
|
+
};
|
|
24
|
+
|
|
16
25
|
export function ChatSessionProjectBadge({
|
|
17
26
|
sessionKey,
|
|
18
27
|
projectName,
|
|
@@ -57,7 +66,11 @@ export function ChatSessionProjectBadge({
|
|
|
57
66
|
</span>
|
|
58
67
|
</button>
|
|
59
68
|
</PopoverTrigger>
|
|
60
|
-
<PopoverContent
|
|
69
|
+
<PopoverContent
|
|
70
|
+
align="start"
|
|
71
|
+
className="w-72 p-2"
|
|
72
|
+
style={SESSION_PROJECT_MENU_STYLE}
|
|
73
|
+
>
|
|
61
74
|
<div className="px-3 pb-2 pt-1">
|
|
62
75
|
<div className="text-[11px] font-medium uppercase tracking-wider text-gray-500">
|
|
63
76
|
{projectName}
|
package/src/features/chat/features/session-type/components/__tests__/chat-session-type-menu.test.tsx
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { render, screen } from "@testing-library/react";
|
|
2
|
+
import { describe, expect, it, vi } from "vitest";
|
|
3
|
+
import { ChatSessionTypeMenu } from "@/features/chat/features/session-type/components/chat-session-type-menu";
|
|
4
|
+
import { createPopoverAvailableHeightLimit } from "@/shared/components/ui/popover";
|
|
5
|
+
|
|
6
|
+
const options = [
|
|
7
|
+
{ value: "native", label: "Native", ready: true },
|
|
8
|
+
{ value: "codex", label: "Codex", ready: true },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
describe("ChatSessionTypeMenu", () => {
|
|
12
|
+
it("keeps the session type menu height bounded", () => {
|
|
13
|
+
render(<ChatSessionTypeMenu options={options} onSelect={vi.fn()} />);
|
|
14
|
+
|
|
15
|
+
const createMenu = screen.getByText("Session Type").parentElement;
|
|
16
|
+
expect(createMenu?.style.maxHeight).toBe(
|
|
17
|
+
createPopoverAvailableHeightLimit("18rem"),
|
|
18
|
+
);
|
|
19
|
+
expect(createMenu?.style.maxHeight).toContain("max(0px");
|
|
20
|
+
expect(createMenu?.style.maxHeight).toContain("100vh");
|
|
21
|
+
expect(createMenu?.style.maxHeight).toContain("2rem");
|
|
22
|
+
expect(createMenu?.className).toContain("overflow-y-auto");
|
|
23
|
+
});
|
|
24
|
+
});
|