chrome-devtools-frontend 1.0.1559913 → 1.0.1561528
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/front_end/core/host/InspectorFrontendHostStub.ts +0 -3
- package/front_end/core/platform/ArrayUtilities.ts +13 -0
- package/front_end/core/root/Runtime.ts +0 -5
- package/front_end/core/sdk/DOMModel.ts +8 -0
- package/front_end/core/sdk/NetworkManager.ts +4 -0
- package/front_end/core/sdk/NetworkRequest.ts +9 -0
- package/front_end/core/sdk/OverlayModel.ts +20 -9
- package/front_end/entrypoints/main/MainImpl.ts +2 -1
- package/front_end/generated/InspectorBackendCommands.ts +4 -2
- package/front_end/generated/protocol-mapping.d.ts +7 -0
- package/front_end/generated/protocol-proxy-api.d.ts +5 -0
- package/front_end/generated/protocol.ts +24 -0
- package/front_end/models/ai_assistance/data_formatters/PerformanceTraceFormatter.snapshot.txt +23 -22
- package/front_end/models/badges/UserBadges.ts +48 -16
- package/front_end/models/greendev/Prototypes.ts +6 -1
- package/front_end/models/trace/extras/TraceTree.ts +1 -1
- package/front_end/models/trace/handlers/PageLoadMetricsHandler.ts +8 -3
- package/front_end/panels/ai_assistance/AiAssistancePanel.ts +11 -142
- package/front_end/panels/ai_assistance/PatchWidget.ts +90 -72
- package/front_end/panels/ai_assistance/ai_assistance.ts +1 -0
- package/front_end/panels/ai_assistance/components/ChatInput.ts +701 -0
- package/front_end/panels/ai_assistance/components/ChatView.ts +71 -1268
- package/front_end/panels/ai_assistance/components/UserActionRow.ts +514 -31
- package/front_end/panels/ai_assistance/components/chatInput.css +387 -0
- package/front_end/panels/ai_assistance/components/chatView.css +38 -599
- package/front_end/panels/ai_assistance/components/userActionRow.css +230 -0
- package/front_end/panels/autofill/AutofillView.ts +2 -2
- package/front_end/panels/changes/ChangesView.ts +15 -1
- package/front_end/panels/changes/changesView.css +6 -0
- package/front_end/panels/common/BadgeNotification.ts +44 -58
- package/front_end/panels/common/CopyChangesToPrompt.ts +233 -0
- package/front_end/panels/common/common.ts +1 -0
- package/front_end/panels/elements/ElementsTreeElement.ts +183 -359
- package/front_end/panels/elements/ElementsTreeOutline.ts +0 -6
- package/front_end/panels/elements/ShortcutTreeElement.ts +57 -50
- package/front_end/panels/elements/StylePropertiesSection.ts +1 -3
- package/front_end/panels/elements/components/AdornerManager.ts +5 -149
- package/front_end/panels/issues/HiddenIssuesRow.ts +1 -2
- package/front_end/panels/issues/IssueKindView.ts +2 -4
- package/front_end/panels/issues/IssueView.ts +2 -4
- package/front_end/panels/network/NetworkDataGridNode.ts +65 -1
- package/front_end/panels/network/NetworkLogView.ts +2 -4
- package/front_end/panels/network/NetworkLogViewColumns.ts +9 -0
- package/front_end/panels/screencast/ScreencastApp.ts +1 -0
- package/front_end/panels/settings/SettingsScreen.ts +3 -2
- package/front_end/panels/timeline/CompatibilityTracksAppender.ts +14 -1
- package/front_end/panels/timeline/ThirdPartyTreeView.ts +1 -4
- package/front_end/panels/timeline/TimelineController.ts +185 -3
- package/front_end/panels/timeline/TimelineFlameChartDataProvider.ts +52 -25
- package/front_end/panels/timeline/TimelineFlameChartView.ts +1 -0
- package/front_end/panels/timeline/TimelinePanel.ts +17 -104
- package/front_end/panels/timeline/TimelineTreeView.ts +1 -0
- package/front_end/panels/timeline/components/insights/BaseInsightComponent.ts +2 -2
- package/front_end/panels/timeline/components/insights/Table.ts +3 -3
- package/front_end/panels/whats_new/ReleaseNoteText.ts +15 -9
- package/front_end/panels/whats_new/resources/WNDT.md +6 -6
- package/front_end/third_party/chromium/README.chromium +1 -1
- package/front_end/third_party/codemirror.next/rebuild.sh +1 -1
- package/front_end/third_party/lit/rebuild.sh +1 -1
- package/front_end/third_party/puppeteer/README.chromium +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts +2 -3
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/api/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPRequest.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPRequest.js +9 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPRequest.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPResponse.d.ts +3 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPResponse.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPResponse.js +9 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/HTTPResponse.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Request.d.ts +3 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Request.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Request.js +10 -0
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/bidi/core/Request.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js +8 -4
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/cdp/Browser.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/generated/injected.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/injected/injected.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js +3 -3
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/revisions.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/Mutex.d.ts +2 -2
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/cjs/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.d.ts +10 -1
- package/front_end/third_party/puppeteer/package/lib/es5-iife/puppeteer-core-browser.js +13 -7
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts +2 -3
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/api/Page.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPRequest.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPRequest.js +9 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPRequest.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPResponse.d.ts +3 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPResponse.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPResponse.js +9 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/HTTPResponse.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Request.d.ts +3 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Request.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Request.js +10 -0
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/bidi/core/Request.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js +8 -4
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/cdp/Browser.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.d.ts.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/generated/injected.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.d.ts +3 -3
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js +3 -3
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/revisions.js.map +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.d.ts +1 -1
- package/front_end/third_party/puppeteer/package/lib/esm/puppeteer/util/version.js +1 -1
- package/front_end/third_party/puppeteer/package/lib/types.d.ts +10 -1
- package/front_end/third_party/puppeteer/package/package.json +3 -3
- package/front_end/third_party/puppeteer/package/src/api/Page.ts +2 -3
- package/front_end/third_party/puppeteer/package/src/bidi/HTTPRequest.ts +13 -0
- package/front_end/third_party/puppeteer/package/src/bidi/HTTPResponse.ts +10 -0
- package/front_end/third_party/puppeteer/package/src/bidi/core/Request.ts +15 -0
- package/front_end/third_party/puppeteer/package/src/cdp/Browser.ts +9 -4
- package/front_end/third_party/puppeteer/package/src/generated/injected.ts +1 -1
- package/front_end/third_party/puppeteer/package/src/revisions.ts +3 -3
- package/front_end/third_party/puppeteer/package/src/util/version.ts +1 -1
- package/front_end/ui/components/adorners/Adorner.ts +8 -68
- package/front_end/ui/legacy/TabbedPane.ts +1 -1
- package/front_end/ui/visual_logging/KnownContextValues.ts +3 -0
- package/package.json +2 -1
|
@@ -8,230 +8,35 @@ import '../../../ui/components/spinners/spinners.js';
|
|
|
8
8
|
|
|
9
9
|
import * as Host from '../../../core/host/host.js';
|
|
10
10
|
import * as i18n from '../../../core/i18n/i18n.js';
|
|
11
|
-
import * as Platform from '../../../core/platform/platform.js';
|
|
12
|
-
import * as
|
|
13
|
-
import * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
|
|
14
|
-
import * as GreenDev from '../../../models/greendev/greendev.js';
|
|
15
|
-
import * as Trace from '../../../models/trace/trace.js';
|
|
16
|
-
import * as Workspace from '../../../models/workspace/workspace.js';
|
|
17
|
-
import * as PanelsCommon from '../../../panels/common/common.js';
|
|
18
|
-
import * as PanelUtils from '../../../panels/utils/utils.js';
|
|
19
|
-
import * as Marked from '../../../third_party/marked/marked.js';
|
|
11
|
+
import type * as Platform from '../../../core/platform/platform.js';
|
|
12
|
+
import type * as AiAssistanceModel from '../../../models/ai_assistance/ai_assistance.js';
|
|
20
13
|
import * as Buttons from '../../../ui/components/buttons/buttons.js';
|
|
21
|
-
import type * as MarkdownView from '../../../ui/components/markdown_view/markdown_view.js';
|
|
22
14
|
import type {MarkdownLitRenderer} from '../../../ui/components/markdown_view/MarkdownView.js';
|
|
23
15
|
import * as UI from '../../../ui/legacy/legacy.js';
|
|
24
16
|
import * as Lit from '../../../ui/lit/lit.js';
|
|
25
|
-
import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
|
|
26
17
|
import {PatchWidget} from '../PatchWidget.js';
|
|
27
18
|
|
|
19
|
+
import {ChatInput} from './ChatInput.js';
|
|
28
20
|
import chatViewStyles from './chatView.css.js';
|
|
29
|
-
import {UserActionRow} from './UserActionRow.js';
|
|
21
|
+
import {type ChatMessage, type ModelChatMessage, UserActionRow} from './UserActionRow.js';
|
|
30
22
|
|
|
31
|
-
|
|
23
|
+
export {ChatInput, type ImageInputData} from './ChatInput.js';
|
|
32
24
|
|
|
33
|
-
const
|
|
34
|
-
/**
|
|
35
|
-
* @description The footer disclaimer that links to more information about the AI feature.
|
|
36
|
-
*/
|
|
37
|
-
learnAbout: 'Learn about AI in DevTools',
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* @description Label added to the text input to describe the context for screen readers. Not shown visibly on screen.
|
|
41
|
-
*/
|
|
42
|
-
inputTextAriaDescription: 'You can also use one of the suggested prompts above to start your conversation',
|
|
43
|
-
/**
|
|
44
|
-
* @description Label added to the button that reveals the selected context item in DevTools
|
|
45
|
-
*/
|
|
46
|
-
revealContextDescription: 'Reveal the selected context item in DevTools',
|
|
47
|
-
} as const;
|
|
25
|
+
const {html, Directives: {ref, repeat, createRef}} = Lit;
|
|
48
26
|
|
|
49
27
|
/*
|
|
50
28
|
* Strings that don't need to be translated at this time.
|
|
51
29
|
*/
|
|
52
30
|
const UIStringsNotTranslate = {
|
|
53
|
-
/**
|
|
54
|
-
* @description Title for the send icon button.
|
|
55
|
-
*/
|
|
56
|
-
sendButtonTitle: 'Send',
|
|
57
|
-
/**
|
|
58
|
-
* @description Title for the start new chat
|
|
59
|
-
*/
|
|
60
|
-
startNewChat: 'Start new chat',
|
|
61
|
-
/**
|
|
62
|
-
* @description Title for the cancel icon button.
|
|
63
|
-
*/
|
|
64
|
-
cancelButtonTitle: 'Cancel',
|
|
65
|
-
/**
|
|
66
|
-
* @description Label for the "select an element" button.
|
|
67
|
-
*/
|
|
68
|
-
selectAnElement: 'Select an element',
|
|
69
|
-
/**
|
|
70
|
-
* @description Label for the "select an element" button.
|
|
71
|
-
*/
|
|
72
|
-
noElementSelected: 'No element selected',
|
|
73
31
|
/**
|
|
74
32
|
* @description Text for the empty state of the AI assistance panel.
|
|
75
33
|
*/
|
|
76
34
|
emptyStateText: 'How can I help you?',
|
|
77
|
-
/**
|
|
78
|
-
* @description The error message when the request to the LLM failed for some reason.
|
|
79
|
-
*/
|
|
80
|
-
systemError:
|
|
81
|
-
'Something unforeseen happened and I can no longer continue. Try your request again and see if that resolves the issue. If this keeps happening, update Chrome to the latest version.',
|
|
82
|
-
/**
|
|
83
|
-
* @description The error message when the LLM gets stuck in a loop (max steps reached).
|
|
84
|
-
*/
|
|
85
|
-
maxStepsError: 'Seems like I am stuck with the investigation. It would be better if you start over.',
|
|
86
|
-
/**
|
|
87
|
-
* @description Displayed when the user stop the response
|
|
88
|
-
*/
|
|
89
|
-
stoppedResponse: 'You stopped this response',
|
|
90
|
-
/**
|
|
91
|
-
* @description Prompt for user to confirm code execution that may affect the page.
|
|
92
|
-
*/
|
|
93
|
-
sideEffectConfirmationDescription: 'This code may modify page content. Continue?',
|
|
94
|
-
/**
|
|
95
|
-
* @description Button text that confirm code execution that may affect the page.
|
|
96
|
-
*/
|
|
97
|
-
positiveSideEffectConfirmation: 'Continue',
|
|
98
|
-
/**
|
|
99
|
-
* @description Button text that cancels code execution that may affect the page.
|
|
100
|
-
*/
|
|
101
|
-
negativeSideEffectConfirmation: 'Cancel',
|
|
102
|
-
/**
|
|
103
|
-
* @description The generic name of the AI agent (do not translate)
|
|
104
|
-
*/
|
|
105
|
-
ai: 'AI',
|
|
106
|
-
/**
|
|
107
|
-
* @description The fallback text when we can't find the user full name
|
|
108
|
-
*/
|
|
109
|
-
you: 'You',
|
|
110
|
-
/**
|
|
111
|
-
* @description The fallback text when a step has no title yet
|
|
112
|
-
*/
|
|
113
|
-
investigating: 'Investigating',
|
|
114
|
-
/**
|
|
115
|
-
* @description Prefix to the title of each thinking step of a user action is required to continue
|
|
116
|
-
*/
|
|
117
|
-
paused: 'Paused',
|
|
118
|
-
/**
|
|
119
|
-
* @description Heading text for the code block that shows the executed code.
|
|
120
|
-
*/
|
|
121
|
-
codeExecuted: 'Code executed',
|
|
122
|
-
/**
|
|
123
|
-
* @description Heading text for the code block that shows the code to be executed after side effect confirmation.
|
|
124
|
-
*/
|
|
125
|
-
codeToExecute: 'Code to execute',
|
|
126
|
-
/**
|
|
127
|
-
* @description Heading text for the code block that shows the returned data.
|
|
128
|
-
*/
|
|
129
|
-
dataReturned: 'Data returned',
|
|
130
|
-
/**
|
|
131
|
-
* @description Aria label for the check mark icon to be read by screen reader
|
|
132
|
-
*/
|
|
133
|
-
completed: 'Completed',
|
|
134
|
-
/**
|
|
135
|
-
* @description Aria label for the cancel icon to be read by screen reader
|
|
136
|
-
*/
|
|
137
|
-
canceled: 'Canceled',
|
|
138
|
-
/**
|
|
139
|
-
* @description Text displayed when the chat input is disabled due to reading past conversation.
|
|
140
|
-
*/
|
|
141
|
-
pastConversation: 'You\'re viewing a past conversation.',
|
|
142
|
-
/**
|
|
143
|
-
* @description Title for the take screenshot button.
|
|
144
|
-
*/
|
|
145
|
-
takeScreenshotButtonTitle: 'Take screenshot',
|
|
146
|
-
/**
|
|
147
|
-
* @description Title for the remove image input button.
|
|
148
|
-
*/
|
|
149
|
-
removeImageInputButtonTitle: 'Remove image input',
|
|
150
|
-
/**
|
|
151
|
-
* @description Alt text for the image input (displayed in the chat messages) that has been sent to the model.
|
|
152
|
-
*/
|
|
153
|
-
imageInputSentToTheModel: 'Image input sent to the model',
|
|
154
|
-
/**
|
|
155
|
-
* @description Alt text for the account avatar.
|
|
156
|
-
*/
|
|
157
|
-
accountAvatar: 'Account avatar',
|
|
158
|
-
/**
|
|
159
|
-
* @description Title for the x-link which wraps the image input rendered in chat messages.
|
|
160
|
-
*/
|
|
161
|
-
openImageInNewTab: 'Open image in a new tab',
|
|
162
|
-
/**
|
|
163
|
-
* @description Alt text for image when it is not available.
|
|
164
|
-
*/
|
|
165
|
-
imageUnavailable: 'Image unavailable',
|
|
166
|
-
/**
|
|
167
|
-
* @description Title for the add image button.
|
|
168
|
-
*/
|
|
169
|
-
addImageButtonTitle: 'Add image',
|
|
170
35
|
} as const;
|
|
171
36
|
|
|
172
|
-
const str_ = i18n.i18n.registerUIStrings('panels/ai_assistance/components/ChatView.ts', UIStrings);
|
|
173
|
-
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
|
174
37
|
const lockedString = i18n.i18n.lockedString;
|
|
175
38
|
|
|
176
39
|
const SCROLL_ROUNDING_OFFSET = 1;
|
|
177
|
-
const RELEVANT_DATA_LINK_FOOTER_ID = 'relevant-data-link-footer';
|
|
178
|
-
const RELEVANT_DATA_LINK_CHAT_ID = 'relevant-data-link-chat';
|
|
179
|
-
|
|
180
|
-
export interface Step {
|
|
181
|
-
isLoading: boolean;
|
|
182
|
-
thought?: string;
|
|
183
|
-
title?: string;
|
|
184
|
-
code?: string;
|
|
185
|
-
output?: string;
|
|
186
|
-
canceled?: boolean;
|
|
187
|
-
sideEffect?: ConfirmSideEffectDialog;
|
|
188
|
-
contextDetails?: [AiAssistanceModel.AiAgent.ContextDetail, ...AiAssistanceModel.AiAgent.ContextDetail[]];
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
interface ConfirmSideEffectDialog {
|
|
192
|
-
onAnswer: (result: boolean) => void;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export const enum ChatMessageEntity {
|
|
196
|
-
MODEL = 'model',
|
|
197
|
-
USER = 'user',
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
export type ImageInputData = {
|
|
201
|
-
isLoading: true,
|
|
202
|
-
}|{
|
|
203
|
-
isLoading: false,
|
|
204
|
-
data: string,
|
|
205
|
-
mimeType: string,
|
|
206
|
-
inputType: AiAssistanceModel.AiAgent.MultimodalInputType,
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
export interface AnswerPart {
|
|
210
|
-
type: 'answer';
|
|
211
|
-
text: string;
|
|
212
|
-
suggestions?: [string, ...string[]];
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
export interface StepPart {
|
|
216
|
-
type: 'step';
|
|
217
|
-
step: Step;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
export type ModelMessagePart = AnswerPart|StepPart;
|
|
221
|
-
|
|
222
|
-
export interface UserChatMessage {
|
|
223
|
-
entity: ChatMessageEntity.USER;
|
|
224
|
-
text: string;
|
|
225
|
-
imageInput?: Host.AidaClient.Part;
|
|
226
|
-
}
|
|
227
|
-
export interface ModelChatMessage {
|
|
228
|
-
entity: ChatMessageEntity.MODEL;
|
|
229
|
-
parts: ModelMessagePart[];
|
|
230
|
-
error?: AiAssistanceModel.AiAgent.ErrorType;
|
|
231
|
-
rpcId?: Host.AidaClient.RpcGlobalId;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
export type ChatMessage = UserChatMessage|ModelChatMessage;
|
|
235
40
|
|
|
236
41
|
export interface Props {
|
|
237
42
|
onTextSubmit:
|
|
@@ -243,10 +48,6 @@ export interface Props {
|
|
|
243
48
|
onContextClick: () => void;
|
|
244
49
|
onNewConversation: () => void;
|
|
245
50
|
onCopyResponseClick: (message: ModelChatMessage) => void;
|
|
246
|
-
onTakeScreenshot?: () => void;
|
|
247
|
-
onRemoveImageInput?: () => void;
|
|
248
|
-
onTextInputChange: (input: string) => void;
|
|
249
|
-
onLoadImage?: (file: File) => Promise<void>;
|
|
250
51
|
changeManager: AiAssistanceModel.ChangeManager.ChangeManager;
|
|
251
52
|
inspectElementToggled: boolean;
|
|
252
53
|
messages: ChatMessage[];
|
|
@@ -259,12 +60,10 @@ export interface Props {
|
|
|
259
60
|
blockedByCrossOrigin: boolean;
|
|
260
61
|
changeSummary?: string;
|
|
261
62
|
multimodalInputEnabled?: boolean;
|
|
262
|
-
imageInput?: ImageInputData;
|
|
263
63
|
isTextInputDisabled: boolean;
|
|
264
64
|
emptyStateSuggestions: AiAssistanceModel.AiAgent.ConversationSuggestion[];
|
|
265
65
|
inputPlaceholder: Platform.UIString.LocalizedString;
|
|
266
66
|
disclaimerText: Platform.UIString.LocalizedString;
|
|
267
|
-
isTextInputEmpty: boolean;
|
|
268
67
|
isArtifactsSidebarOpen: boolean;
|
|
269
68
|
uploadImageInputEnabled?: boolean;
|
|
270
69
|
markdownRenderer: MarkdownLitRenderer;
|
|
@@ -276,7 +75,7 @@ export class ChatView extends HTMLElement {
|
|
|
276
75
|
#scrollTop?: number;
|
|
277
76
|
#props: Props;
|
|
278
77
|
#messagesContainerElement?: Element;
|
|
279
|
-
#mainElementRef
|
|
78
|
+
#mainElementRef = createRef<HTMLElement>();
|
|
280
79
|
#messagesContainerResizeObserver = new ResizeObserver(() => this.#handleMessagesContainerResize());
|
|
281
80
|
/**
|
|
282
81
|
* Indicates whether the chat scroll position should be pinned to the bottom.
|
|
@@ -295,6 +94,7 @@ export class ChatView extends HTMLElement {
|
|
|
295
94
|
* whether to pin the content to the bottom.
|
|
296
95
|
*/
|
|
297
96
|
#isProgrammaticScroll = false;
|
|
97
|
+
#inputRef = createRef<UI.Widget.WidgetElement<ChatInput>>();
|
|
298
98
|
|
|
299
99
|
constructor(props: Props) {
|
|
300
100
|
super();
|
|
@@ -371,16 +171,6 @@ export class ChatView extends HTMLElement {
|
|
|
371
171
|
this.#mainElementRef.value.scrollTop = scrollTop;
|
|
372
172
|
}
|
|
373
173
|
|
|
374
|
-
#setInputText(text: string): void {
|
|
375
|
-
const textArea = this.#shadow.querySelector('.chat-input') as HTMLTextAreaElement;
|
|
376
|
-
if (!textArea) {
|
|
377
|
-
return;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
textArea.value = text;
|
|
381
|
-
this.#props.onTextInputChange(text);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
174
|
#handleMessageContainerRef(el: Element|undefined): void {
|
|
385
175
|
this.#messagesContainerElement = el;
|
|
386
176
|
|
|
@@ -410,121 +200,19 @@ export class ChatView extends HTMLElement {
|
|
|
410
200
|
ev.target.scrollTop + ev.target.clientHeight + SCROLL_ROUNDING_OFFSET > ev.target.scrollHeight;
|
|
411
201
|
};
|
|
412
202
|
|
|
413
|
-
#handleSubmit = (ev: SubmitEvent): void => {
|
|
414
|
-
ev.preventDefault();
|
|
415
|
-
if (this.#props.imageInput?.isLoading) {
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
|
|
419
|
-
const textArea = this.#shadow.querySelector('.chat-input') as HTMLTextAreaElement;
|
|
420
|
-
if (!textArea?.value) {
|
|
421
|
-
return;
|
|
422
|
-
}
|
|
423
|
-
const imageInput = !this.#props.imageInput?.isLoading && this.#props.imageInput?.data ?
|
|
424
|
-
{inlineData: {data: this.#props.imageInput.data, mimeType: this.#props.imageInput.mimeType}} :
|
|
425
|
-
undefined;
|
|
426
|
-
void this.#props.onTextSubmit(textArea.value, imageInput, this.#props.imageInput?.inputType);
|
|
427
|
-
textArea.value = '';
|
|
428
|
-
};
|
|
429
|
-
|
|
430
|
-
#handleTextAreaKeyDown = (ev: KeyboardEvent): void => {
|
|
431
|
-
if (!ev.target || !(ev.target instanceof HTMLTextAreaElement)) {
|
|
432
|
-
return;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
// Go to a new line on Shift+Enter. On Enter, submit unless the
|
|
436
|
-
// user is in IME composition.
|
|
437
|
-
if (ev.key === 'Enter' && !ev.shiftKey && !ev.isComposing) {
|
|
438
|
-
ev.preventDefault();
|
|
439
|
-
if (!ev.target?.value || this.#props.imageInput?.isLoading) {
|
|
440
|
-
return;
|
|
441
|
-
}
|
|
442
|
-
const imageInput = !this.#props.imageInput?.isLoading && this.#props.imageInput?.data ?
|
|
443
|
-
{inlineData: {data: this.#props.imageInput.data, mimeType: this.#props.imageInput.mimeType}} :
|
|
444
|
-
undefined;
|
|
445
|
-
void this.#props.onTextSubmit(ev.target.value, imageInput, this.#props.imageInput?.inputType);
|
|
446
|
-
ev.target.value = '';
|
|
447
|
-
}
|
|
448
|
-
};
|
|
449
|
-
|
|
450
|
-
#handleCancel = (ev: SubmitEvent): void => {
|
|
451
|
-
ev.preventDefault();
|
|
452
|
-
|
|
453
|
-
if (!this.#props.isLoading) {
|
|
454
|
-
return;
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
this.#props.onCancelClick();
|
|
458
|
-
};
|
|
459
|
-
|
|
460
|
-
#handleImageUpload = (ev: Event): void => {
|
|
461
|
-
ev.stopPropagation();
|
|
462
|
-
if (this.#props.onLoadImage) {
|
|
463
|
-
const fileSelector = UI.UIUtils.createFileSelectorElement(this.#props.onLoadImage.bind(this), '.jpeg,.jpg,.png');
|
|
464
|
-
fileSelector.click();
|
|
465
|
-
}
|
|
466
|
-
};
|
|
467
|
-
|
|
468
203
|
#handleSuggestionClick = (suggestion: string): void => {
|
|
469
|
-
this.#
|
|
204
|
+
this.#inputRef.value?.getWidget()?.setInputValue(suggestion);
|
|
205
|
+
this.#render();
|
|
470
206
|
this.focusTextInput();
|
|
471
207
|
Host.userMetrics.actionTaken(Host.UserMetrics.Action.AiAssistanceDynamicSuggestionClicked);
|
|
472
208
|
};
|
|
473
209
|
|
|
474
210
|
#render(): void {
|
|
475
|
-
const renderFooter = (): Lit.LitTemplate => {
|
|
476
|
-
const classes = Lit.Directives.classMap({
|
|
477
|
-
'chat-view-footer': true,
|
|
478
|
-
'is-read-only': this.#props.isReadOnly,
|
|
479
|
-
});
|
|
480
211
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
isLoading: this.#props.isLoading,
|
|
486
|
-
blockedByCrossOrigin: this.#props.blockedByCrossOrigin,
|
|
487
|
-
tooltipId: RELEVANT_DATA_LINK_FOOTER_ID,
|
|
488
|
-
disclaimerText: this.#props.disclaimerText
|
|
489
|
-
})}
|
|
490
|
-
</footer>
|
|
491
|
-
`;
|
|
492
|
-
// clang-format on
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
const renderInputOrReadOnlySection = (): Lit.LitTemplate => {
|
|
496
|
-
if (this.#props.isReadOnly) {
|
|
497
|
-
return renderReadOnlySection({
|
|
498
|
-
onNewConversation: this.#props.onNewConversation,
|
|
499
|
-
});
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
return renderChatInput({
|
|
503
|
-
isLoading: this.#props.isLoading,
|
|
504
|
-
blockedByCrossOrigin: this.#props.blockedByCrossOrigin,
|
|
505
|
-
isTextInputDisabled: this.#props.isTextInputDisabled,
|
|
506
|
-
inputPlaceholder: this.#props.inputPlaceholder,
|
|
507
|
-
disclaimerText: this.#props.disclaimerText,
|
|
508
|
-
selectedContext: this.#props.selectedContext,
|
|
509
|
-
inspectElementToggled: this.#props.inspectElementToggled,
|
|
510
|
-
multimodalInputEnabled: this.#props.multimodalInputEnabled,
|
|
511
|
-
conversationType: this.#props.conversationType,
|
|
512
|
-
imageInput: this.#props.imageInput,
|
|
513
|
-
isTextInputEmpty: this.#props.isTextInputEmpty,
|
|
514
|
-
uploadImageInputEnabled: this.#props.uploadImageInputEnabled,
|
|
515
|
-
onContextClick: this.#props.onContextClick,
|
|
516
|
-
onInspectElementClick: this.#props.onInspectElementClick,
|
|
517
|
-
onSubmit: this.#handleSubmit,
|
|
518
|
-
onTextAreaKeyDown: this.#handleTextAreaKeyDown,
|
|
519
|
-
onCancel: this.#handleCancel,
|
|
520
|
-
onNewConversation: this.#props.onNewConversation,
|
|
521
|
-
onTakeScreenshot: this.#props.onTakeScreenshot,
|
|
522
|
-
onRemoveImageInput: this.#props.onRemoveImageInput,
|
|
523
|
-
onTextInputChange: this.#props.onTextInputChange,
|
|
524
|
-
onImageUpload: this.#handleImageUpload,
|
|
525
|
-
additionalFloatyContext: this.#props.additionalFloatyContext,
|
|
526
|
-
});
|
|
527
|
-
};
|
|
212
|
+
const inputWidgetClasses = Lit.Directives.classMap({
|
|
213
|
+
'chat-input-widget': true,
|
|
214
|
+
sticky: !this.#props.isReadOnly,
|
|
215
|
+
});
|
|
528
216
|
|
|
529
217
|
// clang-format off
|
|
530
218
|
Lit.render(html`
|
|
@@ -547,474 +235,86 @@ export class ChatView extends HTMLElement {
|
|
|
547
235
|
onMessageContainerRef: this.#handleMessageContainerRef,
|
|
548
236
|
onCopyResponseClick: this.#props.onCopyResponseClick,
|
|
549
237
|
})}
|
|
550
|
-
|
|
238
|
+
<devtools-widget class=${inputWidgetClasses} .widgetConfig=${UI.Widget.widgetConfig(ChatInput, {
|
|
239
|
+
isLoading: this.#props.isLoading,
|
|
240
|
+
blockedByCrossOrigin: this.#props.blockedByCrossOrigin,
|
|
241
|
+
isTextInputDisabled: this.#props.isTextInputDisabled,
|
|
242
|
+
inputPlaceholder: this.#props.inputPlaceholder,
|
|
243
|
+
disclaimerText: this.#props.disclaimerText,
|
|
244
|
+
selectedContext: this.#props.selectedContext,
|
|
245
|
+
inspectElementToggled: this.#props.inspectElementToggled,
|
|
246
|
+
multimodalInputEnabled: this.#props.multimodalInputEnabled ?? false,
|
|
247
|
+
conversationType: this.#props.conversationType,
|
|
248
|
+
uploadImageInputEnabled: this.#props.uploadImageInputEnabled ?? false,
|
|
249
|
+
isReadOnly: this.#props.isReadOnly,
|
|
250
|
+
additionalFloatyContext: this.#props.additionalFloatyContext,
|
|
251
|
+
onContextClick: this.#props.onContextClick,
|
|
252
|
+
onInspectElementClick: this.#props.onInspectElementClick,
|
|
253
|
+
onTextSubmit: (
|
|
254
|
+
text: string, imageInput?: Host.AidaClient.Part,
|
|
255
|
+
multimodalInputType?: AiAssistanceModel.AiAgent.MultimodalInputType) => {
|
|
256
|
+
this.#props.onTextSubmit(text, imageInput, multimodalInputType);
|
|
257
|
+
this.#render();
|
|
258
|
+
},
|
|
259
|
+
onCancelClick: this.#props.onCancelClick,
|
|
260
|
+
onNewConversation: this.#props.onNewConversation,
|
|
261
|
+
})} ${ref(this.#inputRef)}></devtools-widget>
|
|
551
262
|
</main>
|
|
552
|
-
${renderFooter()}
|
|
553
263
|
</div>
|
|
554
264
|
`, this.#shadow, {host: this});
|
|
555
265
|
// clang-format on
|
|
556
266
|
}
|
|
557
267
|
}
|
|
558
268
|
|
|
559
|
-
function
|
|
560
|
-
|
|
561
|
-
ref?: (element?: Element) => void,
|
|
562
|
-
} = {}): Lit.TemplateResult {
|
|
563
|
-
let tokens = [];
|
|
564
|
-
try {
|
|
565
|
-
tokens = Marked.Marked.lexer(text);
|
|
566
|
-
for (const token of tokens) {
|
|
567
|
-
// Try to render all the tokens to make sure that
|
|
568
|
-
// they all have a template defined for them. If there
|
|
569
|
-
// isn't any template defined for a token, we'll fallback
|
|
570
|
-
// to rendering the text as plain text instead of markdown.
|
|
571
|
-
markdownRenderer.renderToken(token);
|
|
572
|
-
}
|
|
573
|
-
} catch {
|
|
574
|
-
// The tokens were not parsed correctly or
|
|
575
|
-
// one of the tokens are not supported, so we
|
|
576
|
-
// continue to render this as text.
|
|
577
|
-
return html`${text}`;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// clang-format off
|
|
581
|
-
return html`<devtools-markdown-view
|
|
582
|
-
.data=${{tokens, renderer: markdownRenderer, animationEnabled: animate} as MarkdownView.MarkdownView.MarkdownViewData}
|
|
583
|
-
${refFn ? ref(refFn) : Lit.nothing}>
|
|
584
|
-
</devtools-markdown-view>`;
|
|
585
|
-
// clang-format on
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
function renderTitle(step: Step): Lit.LitTemplate {
|
|
589
|
-
const paused =
|
|
590
|
-
step.sideEffect ? html`<span class="paused">${lockedString(UIStringsNotTranslate.paused)}: </span>` : Lit.nothing;
|
|
591
|
-
const actionTitle = step.title ?? `${lockedString(UIStringsNotTranslate.investigating)}…`;
|
|
592
|
-
|
|
593
|
-
return html`<span class="title">${paused}${actionTitle}</span>`;
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
function renderStepCode(step: Step): Lit.LitTemplate {
|
|
597
|
-
if (!step.code && !step.output) {
|
|
598
|
-
return Lit.nothing;
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
// If there is no "output" yet, it means we didn't execute the code yet (e.g. maybe it is still waiting for confirmation from the user)
|
|
602
|
-
// thus we show "Code to execute" text rather than "Code executed" text on the heading of the code block.
|
|
603
|
-
const codeHeadingText = (step.output && !step.canceled) ? lockedString(UIStringsNotTranslate.codeExecuted) :
|
|
604
|
-
lockedString(UIStringsNotTranslate.codeToExecute);
|
|
605
|
-
|
|
606
|
-
// If there is output, we don't show notice on this code block and instead show
|
|
607
|
-
// it in the data returned code block.
|
|
608
|
-
// clang-format off
|
|
609
|
-
const code = step.code ? html`<div class="action-result">
|
|
610
|
-
<devtools-code-block
|
|
611
|
-
.code=${step.code.trim()}
|
|
612
|
-
.codeLang=${'js'}
|
|
613
|
-
.displayNotice=${!Boolean(step.output)}
|
|
614
|
-
.header=${codeHeadingText}
|
|
615
|
-
.showCopyButton=${true}
|
|
616
|
-
></devtools-code-block>
|
|
617
|
-
</div>` :
|
|
618
|
-
Lit.nothing;
|
|
619
|
-
const output = step.output ? html`<div class="js-code-output">
|
|
620
|
-
<devtools-code-block
|
|
621
|
-
.code=${step.output}
|
|
622
|
-
.codeLang=${'js'}
|
|
623
|
-
.displayNotice=${true}
|
|
624
|
-
.header=${lockedString(UIStringsNotTranslate.dataReturned)}
|
|
625
|
-
.showCopyButton=${false}
|
|
626
|
-
></devtools-code-block>
|
|
627
|
-
</div>` :
|
|
628
|
-
Lit.nothing;
|
|
629
|
-
|
|
630
|
-
return html`<div class="step-code">${code}${output}</div>`;
|
|
631
|
-
// clang-format on
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
function renderStepDetails({
|
|
635
|
-
step,
|
|
636
|
-
markdownRenderer,
|
|
637
|
-
isLast,
|
|
638
|
-
}: {
|
|
639
|
-
step: Step,
|
|
640
|
-
markdownRenderer: MarkdownLitRenderer,
|
|
641
|
-
isLast: boolean,
|
|
642
|
-
}): Lit.LitTemplate {
|
|
643
|
-
const sideEffects = isLast && step.sideEffect ? renderSideEffectConfirmationUi(step) : Lit.nothing;
|
|
644
|
-
const thought = step.thought ? html`<p>${renderTextAsMarkdown(step.thought, markdownRenderer)}</p>` : Lit.nothing;
|
|
645
|
-
|
|
646
|
-
// clang-format off
|
|
647
|
-
const contextDetails = step.contextDetails ?
|
|
648
|
-
html`${Lit.Directives.repeat(
|
|
649
|
-
step.contextDetails,
|
|
650
|
-
contextDetail => {
|
|
651
|
-
return html`<div class="context-details">
|
|
652
|
-
<devtools-code-block
|
|
653
|
-
.code=${contextDetail.text}
|
|
654
|
-
.codeLang=${contextDetail.codeLang || ''}
|
|
655
|
-
.displayNotice=${false}
|
|
656
|
-
.header=${contextDetail.title}
|
|
657
|
-
.showCopyButton=${true}
|
|
658
|
-
></devtools-code-block>
|
|
659
|
-
</div>`;
|
|
660
|
-
},
|
|
661
|
-
)}` : Lit.nothing;
|
|
662
|
-
|
|
663
|
-
return html`<div class="step-details">
|
|
664
|
-
${thought}
|
|
665
|
-
${renderStepCode(step)}
|
|
666
|
-
${sideEffects}
|
|
667
|
-
${contextDetails}
|
|
668
|
-
</div>`;
|
|
669
|
-
// clang-format on
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
function renderStepBadge({step, isLoading, isLast}: {
|
|
673
|
-
step: Step,
|
|
674
|
-
isLoading: boolean,
|
|
675
|
-
isLast: boolean,
|
|
676
|
-
}): Lit.LitTemplate {
|
|
677
|
-
if (isLoading && isLast && !step.sideEffect) {
|
|
678
|
-
return html`<devtools-spinner></devtools-spinner>`;
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
let iconName = 'checkmark';
|
|
682
|
-
let ariaLabel: string|undefined = lockedString(UIStringsNotTranslate.completed);
|
|
683
|
-
let role: 'button'|undefined = 'button';
|
|
684
|
-
if (isLast && step.sideEffect) {
|
|
685
|
-
role = undefined;
|
|
686
|
-
ariaLabel = undefined;
|
|
687
|
-
iconName = 'pause-circle';
|
|
688
|
-
} else if (step.canceled) {
|
|
689
|
-
ariaLabel = lockedString(UIStringsNotTranslate.canceled);
|
|
690
|
-
iconName = 'cross';
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
return html`<devtools-icon
|
|
694
|
-
class="indicator"
|
|
695
|
-
role=${ifDefined(role)}
|
|
696
|
-
aria-label=${ifDefined(ariaLabel)}
|
|
697
|
-
.name=${iconName}
|
|
698
|
-
></devtools-icon>`;
|
|
699
|
-
}
|
|
700
|
-
|
|
701
|
-
function renderStep({step, isLoading, markdownRenderer, isLast}: {
|
|
702
|
-
step: Step,
|
|
703
|
-
isLoading: boolean,
|
|
704
|
-
markdownRenderer: MarkdownLitRenderer,
|
|
705
|
-
isLast: boolean,
|
|
706
|
-
}): Lit.LitTemplate {
|
|
707
|
-
const stepClasses = Lit.Directives.classMap({
|
|
708
|
-
step: true,
|
|
709
|
-
empty: !step.thought && !step.code && !step.contextDetails && !step.sideEffect,
|
|
710
|
-
paused: Boolean(step.sideEffect),
|
|
711
|
-
canceled: Boolean(step.canceled),
|
|
712
|
-
});
|
|
713
|
-
// clang-format off
|
|
714
|
-
return html`
|
|
715
|
-
<details class=${stepClasses}
|
|
716
|
-
jslog=${VisualLogging.section('step')}
|
|
717
|
-
.open=${Boolean(step.sideEffect)}>
|
|
718
|
-
<summary>
|
|
719
|
-
<div class="summary">
|
|
720
|
-
${renderStepBadge({ step, isLoading, isLast })}
|
|
721
|
-
${renderTitle(step)}
|
|
722
|
-
<devtools-icon
|
|
723
|
-
class="arrow"
|
|
724
|
-
name="chevron-down"
|
|
725
|
-
></devtools-icon>
|
|
726
|
-
</div>
|
|
727
|
-
</summary>
|
|
728
|
-
${renderStepDetails({step, markdownRenderer, isLast})}
|
|
729
|
-
</details>`;
|
|
730
|
-
// clang-format on
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
function renderSideEffectConfirmationUi(step: Step): Lit.LitTemplate {
|
|
734
|
-
if (!step.sideEffect) {
|
|
735
|
-
return Lit.nothing;
|
|
736
|
-
}
|
|
737
|
-
|
|
738
|
-
// clang-format off
|
|
739
|
-
return html`<div
|
|
740
|
-
class="side-effect-confirmation"
|
|
741
|
-
jslog=${VisualLogging.section('side-effect-confirmation')}
|
|
742
|
-
>
|
|
743
|
-
<p>${lockedString(UIStringsNotTranslate.sideEffectConfirmationDescription)}</p>
|
|
744
|
-
<div class="side-effect-buttons-container">
|
|
745
|
-
<devtools-button
|
|
746
|
-
.data=${
|
|
747
|
-
{
|
|
748
|
-
variant: Buttons.Button.Variant.OUTLINED,
|
|
749
|
-
jslogContext: 'decline-execute-code',
|
|
750
|
-
} as Buttons.Button.ButtonData
|
|
751
|
-
}
|
|
752
|
-
@click=${() => step.sideEffect?.onAnswer(false)}
|
|
753
|
-
>${lockedString(
|
|
754
|
-
UIStringsNotTranslate.negativeSideEffectConfirmation,
|
|
755
|
-
)}</devtools-button>
|
|
756
|
-
<devtools-button
|
|
757
|
-
.data=${
|
|
758
|
-
{
|
|
759
|
-
variant: Buttons.Button.Variant.PRIMARY,
|
|
760
|
-
jslogContext: 'accept-execute-code',
|
|
761
|
-
iconName: 'play',
|
|
762
|
-
} as Buttons.Button.ButtonData
|
|
763
|
-
}
|
|
764
|
-
@click=${() => step.sideEffect?.onAnswer(true)}
|
|
765
|
-
>${
|
|
766
|
-
lockedString(UIStringsNotTranslate.positiveSideEffectConfirmation)
|
|
767
|
-
}</devtools-button>
|
|
768
|
-
</div>
|
|
769
|
-
</div>`;
|
|
770
|
-
// clang-format on
|
|
771
|
-
}
|
|
772
|
-
|
|
773
|
-
function renderError(message: ModelChatMessage): Lit.LitTemplate {
|
|
774
|
-
if (message.error) {
|
|
775
|
-
let errorMessage;
|
|
776
|
-
switch (message.error) {
|
|
777
|
-
case AiAssistanceModel.AiAgent.ErrorType.UNKNOWN:
|
|
778
|
-
case AiAssistanceModel.AiAgent.ErrorType.BLOCK:
|
|
779
|
-
errorMessage = UIStringsNotTranslate.systemError;
|
|
780
|
-
break;
|
|
781
|
-
case AiAssistanceModel.AiAgent.ErrorType.MAX_STEPS:
|
|
782
|
-
errorMessage = UIStringsNotTranslate.maxStepsError;
|
|
783
|
-
break;
|
|
784
|
-
case AiAssistanceModel.AiAgent.ErrorType.ABORT:
|
|
785
|
-
return html`<p class="aborted" jslog=${VisualLogging.section('aborted')}>${
|
|
786
|
-
lockedString(UIStringsNotTranslate.stoppedResponse)}</p>`;
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
return html`<p class="error" jslog=${VisualLogging.section('error')}>${lockedString(errorMessage)}</p>`;
|
|
790
|
-
}
|
|
791
|
-
|
|
792
|
-
return Lit.nothing;
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
function renderChatMessage({
|
|
796
|
-
message,
|
|
269
|
+
function renderMainContents({
|
|
270
|
+
messages,
|
|
797
271
|
isLoading,
|
|
798
272
|
isReadOnly,
|
|
799
273
|
canShowFeedbackForm,
|
|
800
|
-
|
|
274
|
+
isTextInputDisabled,
|
|
275
|
+
suggestions,
|
|
801
276
|
userInfo,
|
|
802
277
|
markdownRenderer,
|
|
278
|
+
changeSummary,
|
|
279
|
+
changeManager,
|
|
803
280
|
onSuggestionClick,
|
|
804
281
|
onFeedbackSubmit,
|
|
805
282
|
onCopyResponseClick,
|
|
283
|
+
onMessageContainerRef,
|
|
806
284
|
}: {
|
|
807
|
-
|
|
285
|
+
messages: ChatMessage[],
|
|
808
286
|
isLoading: boolean,
|
|
809
287
|
isReadOnly: boolean,
|
|
810
288
|
canShowFeedbackForm: boolean,
|
|
811
|
-
|
|
289
|
+
isTextInputDisabled: boolean,
|
|
290
|
+
suggestions: AiAssistanceModel.AiAgent.ConversationSuggestion[],
|
|
812
291
|
userInfo: Pick<Host.InspectorFrontendHostAPI.SyncInformation, 'accountImage'|'accountFullName'>,
|
|
813
292
|
markdownRenderer: MarkdownLitRenderer,
|
|
293
|
+
changeManager: AiAssistanceModel.ChangeManager.ChangeManager,
|
|
814
294
|
onSuggestionClick: (suggestion: string) => void,
|
|
815
295
|
onFeedbackSubmit: (rpcId: Host.AidaClient.RpcGlobalId, rate: Host.AidaClient.Rating, feedback?: string) => void,
|
|
816
296
|
onCopyResponseClick: (message: ModelChatMessage) => void,
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
const name = userInfo.accountFullName || lockedString(UIStringsNotTranslate.you);
|
|
820
|
-
const image = userInfo.accountImage ?
|
|
821
|
-
html`<img src="data:image/png;base64, ${userInfo.accountImage}" alt=${UIStringsNotTranslate.accountAvatar} />` :
|
|
822
|
-
html`<devtools-icon
|
|
823
|
-
name="profile"
|
|
824
|
-
></devtools-icon>`;
|
|
825
|
-
const imageInput = message.imageInput && 'inlineData' in message.imageInput ?
|
|
826
|
-
renderImageChatMessage(message.imageInput.inlineData) :
|
|
827
|
-
Lit.nothing;
|
|
828
|
-
// clang-format off
|
|
829
|
-
return html`<section
|
|
830
|
-
class="chat-message query"
|
|
831
|
-
jslog=${VisualLogging.section('question')}
|
|
832
|
-
>
|
|
833
|
-
<div class="message-info">
|
|
834
|
-
${image}
|
|
835
|
-
<div class="message-name">
|
|
836
|
-
<h2>${name}</h2>
|
|
837
|
-
</div>
|
|
838
|
-
</div>
|
|
839
|
-
${imageInput}
|
|
840
|
-
<div class="message-content">${renderTextAsMarkdown(message.text, markdownRenderer)}</div>
|
|
841
|
-
</section>`;
|
|
842
|
-
// clang-format on
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
// clang-format off
|
|
846
|
-
return html`
|
|
847
|
-
<section
|
|
848
|
-
class="chat-message answer"
|
|
849
|
-
jslog=${VisualLogging.section('answer')}
|
|
850
|
-
>
|
|
851
|
-
<div class="message-info">
|
|
852
|
-
<devtools-icon name="smart-assistant"></devtools-icon>
|
|
853
|
-
<div class="message-name">
|
|
854
|
-
<h2>${lockedString(UIStringsNotTranslate.ai)}</h2>
|
|
855
|
-
</div>
|
|
856
|
-
</div>
|
|
857
|
-
${Lit.Directives.repeat(
|
|
858
|
-
message.parts,
|
|
859
|
-
(_, index) => index,
|
|
860
|
-
(part, index) => {
|
|
861
|
-
const isLastPart = index === message.parts.length - 1;
|
|
862
|
-
if (part.type === 'answer') {
|
|
863
|
-
return html`<p>${renderTextAsMarkdown(part.text, markdownRenderer, { animate: !isReadOnly && isLoading && isLast && isLastPart })}</p>`;
|
|
864
|
-
}
|
|
865
|
-
return renderStep({
|
|
866
|
-
step: part.step,
|
|
867
|
-
isLoading,
|
|
868
|
-
markdownRenderer,
|
|
869
|
-
isLast: isLastPart && isLast,
|
|
870
|
-
});
|
|
871
|
-
},
|
|
872
|
-
)}
|
|
873
|
-
${renderError(message)}
|
|
874
|
-
${isLast && isLoading
|
|
875
|
-
? Lit.nothing
|
|
876
|
-
: html`<devtools-widget class="actions" .widgetConfig=${UI.Widget.widgetConfig(UserActionRow, {
|
|
877
|
-
showRateButtons: message.rpcId !== undefined,
|
|
878
|
-
onFeedbackSubmit: (rating: Host.AidaClient.Rating, feedback?: string) => {
|
|
879
|
-
if (!message.rpcId) {
|
|
880
|
-
return;
|
|
881
|
-
}
|
|
882
|
-
onFeedbackSubmit(message.rpcId, rating, feedback);
|
|
883
|
-
},
|
|
884
|
-
suggestions: (isLast && !isReadOnly && message.parts.at(-1)?.type === 'answer') ? (message.parts.at(-1) as AnswerPart).suggestions : undefined,
|
|
885
|
-
onSuggestionClick,
|
|
886
|
-
onCopyResponseClick: () => onCopyResponseClick(message),
|
|
887
|
-
canShowFeedbackForm,
|
|
888
|
-
})}></devtools-widget>`
|
|
889
|
-
}
|
|
890
|
-
</section>
|
|
891
|
-
`;
|
|
892
|
-
// clang-format on
|
|
893
|
-
}
|
|
894
|
-
|
|
895
|
-
function renderImageChatMessage(inlineData: Host.AidaClient.MediaBlob): Lit.LitTemplate {
|
|
896
|
-
if (inlineData.data === AiAssistanceModel.AiConversation.NOT_FOUND_IMAGE_DATA) {
|
|
897
|
-
// clang-format off
|
|
898
|
-
return html`<div class="unavailable-image" title=${UIStringsNotTranslate.imageUnavailable}>
|
|
899
|
-
<devtools-icon name='file-image'></devtools-icon>
|
|
900
|
-
</div>`;
|
|
901
|
-
// clang-format on
|
|
902
|
-
}
|
|
903
|
-
const imageUrl = `data:${inlineData.mimeType};base64,${inlineData.data}`;
|
|
904
|
-
// clang-format off
|
|
905
|
-
return html`<x-link
|
|
906
|
-
class="image-link" title=${UIStringsNotTranslate.openImageInNewTab}
|
|
907
|
-
href=${imageUrl}
|
|
908
|
-
>
|
|
909
|
-
<img src=${imageUrl} alt=${UIStringsNotTranslate.imageInputSentToTheModel} />
|
|
910
|
-
</x-link>`;
|
|
911
|
-
// clang-format on
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
function renderContextIcon(context: AiAssistanceModel.AiAgent.ConversationContext<unknown>|null): Lit.LitTemplate {
|
|
915
|
-
if (!context) {
|
|
916
|
-
return Lit.nothing;
|
|
917
|
-
}
|
|
918
|
-
const item = context.getItem();
|
|
919
|
-
// FIXME: move this to presenter once PanelUtils are declarative. The instance
|
|
920
|
-
// checking should be in the presenter and the rendering in the view function.
|
|
921
|
-
if (item instanceof SDK.NetworkRequest.NetworkRequest) {
|
|
922
|
-
return PanelUtils.PanelUtils.getIconForNetworkRequest(item);
|
|
923
|
-
}
|
|
924
|
-
if (item instanceof Workspace.UISourceCode.UISourceCode) {
|
|
925
|
-
return PanelUtils.PanelUtils.getIconForSourceFile(item);
|
|
926
|
-
}
|
|
927
|
-
if (item instanceof AiAssistanceModel.AIContext.AgentFocus) {
|
|
928
|
-
return html`<devtools-icon name="performance" title="Performance"></devtools-icon>`;
|
|
929
|
-
}
|
|
930
|
-
if (item instanceof SDK.DOMModel.DOMNode) {
|
|
931
|
-
return Lit.nothing;
|
|
932
|
-
}
|
|
933
|
-
return Lit.nothing;
|
|
934
|
-
}
|
|
935
|
-
|
|
936
|
-
function renderContextTitle(
|
|
937
|
-
context: AiAssistanceModel.AiAgent.ConversationContext<unknown>, disabled: boolean): Lit.TemplateResult|string {
|
|
938
|
-
const item = context.getItem();
|
|
939
|
-
if (item instanceof SDK.DOMModel.DOMNode) {
|
|
940
|
-
// FIXME: move this to the model code.
|
|
941
|
-
const hiddenClassList = item.classNames().filter(
|
|
942
|
-
className => className.startsWith(AiAssistanceModel.Injected.AI_ASSISTANCE_CSS_CLASS_NAME));
|
|
943
|
-
return html`<devtools-widget .widgetConfig=${UI.Widget.widgetConfig(PanelsCommon.DOMLinkifier.DOMNodeLink, {
|
|
944
|
-
node: item,
|
|
945
|
-
options: {hiddenClassList, disabled}
|
|
946
|
-
})}></devtools-widget>`;
|
|
947
|
-
}
|
|
948
|
-
return context.getTitle();
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
function renderSelection({
|
|
952
|
-
selectedContext,
|
|
953
|
-
inspectElementToggled,
|
|
954
|
-
conversationType,
|
|
955
|
-
isTextInputDisabled,
|
|
956
|
-
onContextClick,
|
|
957
|
-
onInspectElementClick,
|
|
958
|
-
}: {
|
|
959
|
-
selectedContext: AiAssistanceModel.AiAgent.ConversationContext<unknown>|null,
|
|
960
|
-
inspectElementToggled: boolean,
|
|
961
|
-
isTextInputDisabled: boolean,
|
|
962
|
-
onContextClick: () => void | Promise<void>,
|
|
963
|
-
onInspectElementClick: () => void,
|
|
964
|
-
conversationType: AiAssistanceModel.AiHistoryStorage.ConversationType,
|
|
297
|
+
onMessageContainerRef: (el: Element|undefined) => void,
|
|
298
|
+
changeSummary?: string,
|
|
965
299
|
}): Lit.LitTemplate {
|
|
966
|
-
if (
|
|
967
|
-
return
|
|
300
|
+
if (messages.length > 0) {
|
|
301
|
+
return renderMessages({
|
|
302
|
+
messages,
|
|
303
|
+
isLoading,
|
|
304
|
+
isReadOnly,
|
|
305
|
+
canShowFeedbackForm,
|
|
306
|
+
userInfo,
|
|
307
|
+
markdownRenderer,
|
|
308
|
+
changeSummary,
|
|
309
|
+
changeManager,
|
|
310
|
+
onSuggestionClick,
|
|
311
|
+
onFeedbackSubmit,
|
|
312
|
+
onMessageContainerRef,
|
|
313
|
+
onCopyResponseClick
|
|
314
|
+
});
|
|
968
315
|
}
|
|
969
|
-
// TODO: currently the picker behavior is SDKNode specific.
|
|
970
|
-
const hasPickerBehavior = conversationType === AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING;
|
|
971
316
|
|
|
972
|
-
|
|
973
|
-
'not-selected': !selectedContext,
|
|
974
|
-
'resource-link': true,
|
|
975
|
-
'has-picker-behavior': hasPickerBehavior,
|
|
976
|
-
disabled: isTextInputDisabled,
|
|
977
|
-
});
|
|
978
|
-
|
|
979
|
-
const handleKeyDown = (ev: KeyboardEvent): void => {
|
|
980
|
-
if (ev.key === 'Enter' || ev.key === ' ') {
|
|
981
|
-
void onContextClick();
|
|
982
|
-
}
|
|
983
|
-
};
|
|
984
|
-
|
|
985
|
-
// clang-format off
|
|
986
|
-
return html`<div class="select-element">
|
|
987
|
-
${
|
|
988
|
-
hasPickerBehavior ? html`
|
|
989
|
-
<devtools-button
|
|
990
|
-
.data=${{
|
|
991
|
-
variant: Buttons.Button.Variant.ICON_TOGGLE,
|
|
992
|
-
size: Buttons.Button.Size.SMALL,
|
|
993
|
-
iconName: 'select-element',
|
|
994
|
-
toggledIconName: 'select-element',
|
|
995
|
-
toggleType: Buttons.Button.ToggleType.PRIMARY,
|
|
996
|
-
toggled: inspectElementToggled,
|
|
997
|
-
title: lockedString(UIStringsNotTranslate.selectAnElement),
|
|
998
|
-
jslogContext: 'select-element',
|
|
999
|
-
disabled: isTextInputDisabled,
|
|
1000
|
-
} as Buttons.Button.ButtonData}
|
|
1001
|
-
@click=${onInspectElementClick}
|
|
1002
|
-
></devtools-button>
|
|
1003
|
-
` : Lit.nothing
|
|
1004
|
-
}
|
|
1005
|
-
<div
|
|
1006
|
-
role=button
|
|
1007
|
-
class=${resourceClass}
|
|
1008
|
-
tabindex=${(hasPickerBehavior || isTextInputDisabled) ? '-1' : '0'}
|
|
1009
|
-
@click=${onContextClick}
|
|
1010
|
-
@keydown=${handleKeyDown}
|
|
1011
|
-
aria-description=${i18nString(UIStrings.revealContextDescription)}
|
|
1012
|
-
>
|
|
1013
|
-
${renderContextIcon(selectedContext)}
|
|
1014
|
-
<span class="title">${selectedContext ? renderContextTitle(selectedContext, isTextInputDisabled) : lockedString(UIStringsNotTranslate.noElementSelected)}</span>
|
|
1015
|
-
</div>
|
|
1016
|
-
</div>`;
|
|
1017
|
-
// clang-format on
|
|
317
|
+
return renderEmptyState({isTextInputDisabled, suggestions, onSuggestionClick});
|
|
1018
318
|
}
|
|
1019
319
|
|
|
1020
320
|
function renderMessages({
|
|
@@ -1062,19 +362,19 @@ function renderMessages({
|
|
|
1062
362
|
// clang-format off
|
|
1063
363
|
return html`
|
|
1064
364
|
<div class="messages-container" ${ref(onMessageContainerRef)}>
|
|
1065
|
-
${messages
|
|
1066
|
-
|
|
365
|
+
${repeat(messages, message =>
|
|
366
|
+
html`<devtools-widget .widgetConfig=${UI.Widget.widgetConfig(UserActionRow, {
|
|
1067
367
|
message,
|
|
1068
368
|
isLoading,
|
|
1069
369
|
isReadOnly,
|
|
1070
370
|
canShowFeedbackForm,
|
|
1071
|
-
isLast: array.at(-1) === message,
|
|
1072
371
|
userInfo,
|
|
1073
372
|
markdownRenderer,
|
|
373
|
+
isLastMessage: messages.at(-1) === message,
|
|
1074
374
|
onSuggestionClick,
|
|
1075
375
|
onFeedbackSubmit,
|
|
1076
376
|
onCopyResponseClick,
|
|
1077
|
-
})
|
|
377
|
+
})}></devtools-widget>`
|
|
1078
378
|
)}
|
|
1079
379
|
${renderPatchWidget()}
|
|
1080
380
|
</div>
|
|
@@ -1118,503 +418,6 @@ function renderEmptyState({isTextInputDisabled, suggestions, onSuggestionClick}:
|
|
|
1118
418
|
// clang-format on
|
|
1119
419
|
}
|
|
1120
420
|
|
|
1121
|
-
function renderReadOnlySection({onNewConversation}: {
|
|
1122
|
-
onNewConversation: () => void,
|
|
1123
|
-
}): Lit.LitTemplate {
|
|
1124
|
-
// clang-format off
|
|
1125
|
-
return html`<div
|
|
1126
|
-
class="chat-readonly-container"
|
|
1127
|
-
jslog=${VisualLogging.section('read-only')}
|
|
1128
|
-
>
|
|
1129
|
-
<span>${lockedString(UIStringsNotTranslate.pastConversation)}</span>
|
|
1130
|
-
<devtools-button
|
|
1131
|
-
aria-label=${lockedString(UIStringsNotTranslate.startNewChat)}
|
|
1132
|
-
class="chat-inline-button"
|
|
1133
|
-
@click=${onNewConversation}
|
|
1134
|
-
.data=${{
|
|
1135
|
-
variant: Buttons.Button.Variant.TEXT,
|
|
1136
|
-
title: lockedString(UIStringsNotTranslate.startNewChat),
|
|
1137
|
-
jslogContext: 'start-new-chat',
|
|
1138
|
-
} as Buttons.Button.ButtonData}
|
|
1139
|
-
>${lockedString(UIStringsNotTranslate.startNewChat)}</devtools-button>
|
|
1140
|
-
</div>`;
|
|
1141
|
-
// clang-format on
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
function renderChatInputButtons(
|
|
1145
|
-
{isLoading, blockedByCrossOrigin, isTextInputDisabled, isTextInputEmpty, imageInput, onCancel, onNewConversation}: {
|
|
1146
|
-
isLoading: boolean,
|
|
1147
|
-
blockedByCrossOrigin: boolean,
|
|
1148
|
-
isTextInputDisabled: boolean,
|
|
1149
|
-
isTextInputEmpty: boolean,
|
|
1150
|
-
onCancel: (ev: SubmitEvent) => void,
|
|
1151
|
-
onNewConversation: () => void,
|
|
1152
|
-
imageInput?: ImageInputData,
|
|
1153
|
-
}): Lit.TemplateResult {
|
|
1154
|
-
if (isLoading) {
|
|
1155
|
-
// clang-format off
|
|
1156
|
-
return html`<devtools-button
|
|
1157
|
-
class="chat-input-button"
|
|
1158
|
-
aria-label=${lockedString(UIStringsNotTranslate.cancelButtonTitle)}
|
|
1159
|
-
@click=${onCancel}
|
|
1160
|
-
.data=${
|
|
1161
|
-
{
|
|
1162
|
-
variant: Buttons.Button.Variant.ICON,
|
|
1163
|
-
size: Buttons.Button.Size.REGULAR,
|
|
1164
|
-
iconName: 'record-stop',
|
|
1165
|
-
title: lockedString(UIStringsNotTranslate.cancelButtonTitle),
|
|
1166
|
-
jslogContext: 'stop',
|
|
1167
|
-
} as Buttons.Button.ButtonData
|
|
1168
|
-
}
|
|
1169
|
-
></devtools-button>`;
|
|
1170
|
-
// clang-format on
|
|
1171
|
-
}
|
|
1172
|
-
if (blockedByCrossOrigin) {
|
|
1173
|
-
// clang-format off
|
|
1174
|
-
return html`
|
|
1175
|
-
<devtools-button
|
|
1176
|
-
class="start-new-chat-button"
|
|
1177
|
-
aria-label=${lockedString(UIStringsNotTranslate.startNewChat)}
|
|
1178
|
-
@click=${onNewConversation}
|
|
1179
|
-
.data=${
|
|
1180
|
-
{
|
|
1181
|
-
variant: Buttons.Button.Variant.OUTLINED,
|
|
1182
|
-
size: Buttons.Button.Size.SMALL,
|
|
1183
|
-
title: lockedString(UIStringsNotTranslate.startNewChat),
|
|
1184
|
-
jslogContext: 'start-new-chat',
|
|
1185
|
-
} as Buttons.Button.ButtonData
|
|
1186
|
-
}
|
|
1187
|
-
>${lockedString(UIStringsNotTranslate.startNewChat)}</devtools-button>
|
|
1188
|
-
`;
|
|
1189
|
-
// clang-format on
|
|
1190
|
-
}
|
|
1191
|
-
// clang-format off
|
|
1192
|
-
return html`<devtools-button
|
|
1193
|
-
class="chat-input-button"
|
|
1194
|
-
aria-label=${lockedString(UIStringsNotTranslate.sendButtonTitle)}
|
|
1195
|
-
.data=${
|
|
1196
|
-
{
|
|
1197
|
-
type: 'submit',
|
|
1198
|
-
variant: Buttons.Button.Variant.ICON,
|
|
1199
|
-
size: Buttons.Button.Size.REGULAR,
|
|
1200
|
-
disabled: isTextInputDisabled || isTextInputEmpty || imageInput?.isLoading,
|
|
1201
|
-
iconName: 'send',
|
|
1202
|
-
title: lockedString(UIStringsNotTranslate.sendButtonTitle),
|
|
1203
|
-
jslogContext: 'send',
|
|
1204
|
-
} as Buttons.Button.ButtonData
|
|
1205
|
-
}
|
|
1206
|
-
></devtools-button>`;
|
|
1207
|
-
}
|
|
1208
|
-
|
|
1209
|
-
function renderMultimodalInputButtons({
|
|
1210
|
-
multimodalInputEnabled,
|
|
1211
|
-
blockedByCrossOrigin,
|
|
1212
|
-
isTextInputDisabled,
|
|
1213
|
-
imageInput,
|
|
1214
|
-
uploadImageInputEnabled,
|
|
1215
|
-
onTakeScreenshot,
|
|
1216
|
-
onImageUpload,
|
|
1217
|
-
}: {
|
|
1218
|
-
isTextInputDisabled: boolean,
|
|
1219
|
-
blockedByCrossOrigin: boolean,
|
|
1220
|
-
multimodalInputEnabled?: boolean,
|
|
1221
|
-
imageInput?: ImageInputData,
|
|
1222
|
-
uploadImageInputEnabled?: boolean,
|
|
1223
|
-
onTakeScreenshot?: () => void,
|
|
1224
|
-
onImageUpload?: (ev: Event) => void,
|
|
1225
|
-
}): Lit.LitTemplate {
|
|
1226
|
-
if (!multimodalInputEnabled || blockedByCrossOrigin) {
|
|
1227
|
-
return Lit.nothing;
|
|
1228
|
-
}
|
|
1229
|
-
// clang-format off
|
|
1230
|
-
const addImageButton = uploadImageInputEnabled ? html`<devtools-button
|
|
1231
|
-
class="chat-input-button"
|
|
1232
|
-
aria-label=${lockedString(UIStringsNotTranslate.addImageButtonTitle)}
|
|
1233
|
-
@click=${onImageUpload}
|
|
1234
|
-
.data=${
|
|
1235
|
-
{
|
|
1236
|
-
variant: Buttons.Button.Variant.ICON,
|
|
1237
|
-
size: Buttons.Button.Size.REGULAR,
|
|
1238
|
-
disabled: isTextInputDisabled || imageInput?.isLoading,
|
|
1239
|
-
iconName: 'add-photo',
|
|
1240
|
-
title: lockedString(UIStringsNotTranslate.addImageButtonTitle),
|
|
1241
|
-
jslogContext: 'upload-image',
|
|
1242
|
-
} as Buttons.Button.ButtonData
|
|
1243
|
-
}
|
|
1244
|
-
></devtools-button>` : Lit.nothing;
|
|
1245
|
-
|
|
1246
|
-
return html`${addImageButton}<devtools-button
|
|
1247
|
-
class="chat-input-button"
|
|
1248
|
-
aria-label=${lockedString(UIStringsNotTranslate.takeScreenshotButtonTitle)}
|
|
1249
|
-
@click=${onTakeScreenshot}
|
|
1250
|
-
.data=${
|
|
1251
|
-
{
|
|
1252
|
-
variant: Buttons.Button.Variant.ICON,
|
|
1253
|
-
size: Buttons.Button.Size.REGULAR,
|
|
1254
|
-
disabled: isTextInputDisabled || imageInput?.isLoading,
|
|
1255
|
-
iconName: 'photo-camera',
|
|
1256
|
-
title: lockedString(UIStringsNotTranslate.takeScreenshotButtonTitle),
|
|
1257
|
-
jslogContext: 'take-screenshot',
|
|
1258
|
-
} as Buttons.Button.ButtonData
|
|
1259
|
-
}
|
|
1260
|
-
></devtools-button>`;
|
|
1261
|
-
// clang-format on
|
|
1262
|
-
}
|
|
1263
|
-
|
|
1264
|
-
function renderImageInput({
|
|
1265
|
-
multimodalInputEnabled,
|
|
1266
|
-
imageInput,
|
|
1267
|
-
isTextInputDisabled,
|
|
1268
|
-
onRemoveImageInput,
|
|
1269
|
-
}: {
|
|
1270
|
-
multimodalInputEnabled?: boolean,
|
|
1271
|
-
imageInput?: ImageInputData,
|
|
1272
|
-
isTextInputDisabled?: boolean,
|
|
1273
|
-
onRemoveImageInput?: () => void,
|
|
1274
|
-
}): Lit.LitTemplate {
|
|
1275
|
-
if (!multimodalInputEnabled || !imageInput || isTextInputDisabled) {
|
|
1276
|
-
return Lit.nothing;
|
|
1277
|
-
}
|
|
1278
|
-
// clang-format off
|
|
1279
|
-
const crossButton = html`<devtools-button
|
|
1280
|
-
aria-label=${lockedString(UIStringsNotTranslate.removeImageInputButtonTitle)}
|
|
1281
|
-
@click=${onRemoveImageInput}
|
|
1282
|
-
.data=${
|
|
1283
|
-
{
|
|
1284
|
-
variant: Buttons.Button.Variant.ICON,
|
|
1285
|
-
size: Buttons.Button.Size.MICRO,
|
|
1286
|
-
iconName: 'cross',
|
|
1287
|
-
title: lockedString(UIStringsNotTranslate.removeImageInputButtonTitle),
|
|
1288
|
-
} as Buttons.Button.ButtonData
|
|
1289
|
-
}
|
|
1290
|
-
></devtools-button>`;
|
|
1291
|
-
// clang-format on
|
|
1292
|
-
|
|
1293
|
-
if (imageInput.isLoading) {
|
|
1294
|
-
// clang-format off
|
|
1295
|
-
return html`<div class="image-input-container">
|
|
1296
|
-
${crossButton}
|
|
1297
|
-
<div class="loading">
|
|
1298
|
-
<devtools-spinner></devtools-spinner>
|
|
1299
|
-
</div>
|
|
1300
|
-
</div>`;
|
|
1301
|
-
// clang-format on
|
|
1302
|
-
}
|
|
1303
|
-
// clang-format off
|
|
1304
|
-
return html`
|
|
1305
|
-
<div class="image-input-container">
|
|
1306
|
-
${crossButton}
|
|
1307
|
-
<img src="data:${imageInput.mimeType};base64, ${imageInput.data}" alt="Image input" />
|
|
1308
|
-
</div>`;
|
|
1309
|
-
// clang-format on
|
|
1310
|
-
}
|
|
1311
|
-
|
|
1312
|
-
function renderRelevantDataDisclaimer({isLoading, blockedByCrossOrigin, tooltipId, disclaimerText}: {
|
|
1313
|
-
isLoading: boolean,
|
|
1314
|
-
blockedByCrossOrigin: boolean,
|
|
1315
|
-
tooltipId: string,
|
|
1316
|
-
disclaimerText: string,
|
|
1317
|
-
}): Lit.LitTemplate {
|
|
1318
|
-
const classes = Lit.Directives.classMap({
|
|
1319
|
-
'chat-input-disclaimer': true,
|
|
1320
|
-
'hide-divider': !isLoading && blockedByCrossOrigin,
|
|
1321
|
-
});
|
|
1322
|
-
// clang-format off
|
|
1323
|
-
return html`
|
|
1324
|
-
<p class=${classes}>
|
|
1325
|
-
<button
|
|
1326
|
-
class="link"
|
|
1327
|
-
role="link"
|
|
1328
|
-
aria-details=${tooltipId}
|
|
1329
|
-
jslog=${VisualLogging.link('open-ai-settings').track({
|
|
1330
|
-
click: true,
|
|
1331
|
-
})}
|
|
1332
|
-
@click=${() => {
|
|
1333
|
-
void UI.ViewManager.ViewManager.instance().showView('chrome-ai');
|
|
1334
|
-
}}
|
|
1335
|
-
>${lockedString('Relevant data')}</button> ${lockedString('is sent to Google')}
|
|
1336
|
-
${renderDisclaimerTooltip(tooltipId, disclaimerText)}
|
|
1337
|
-
</p>
|
|
1338
|
-
`;
|
|
1339
|
-
// clang-format on
|
|
1340
|
-
}
|
|
1341
|
-
|
|
1342
|
-
function renderChatInput({
|
|
1343
|
-
isLoading,
|
|
1344
|
-
blockedByCrossOrigin,
|
|
1345
|
-
isTextInputDisabled,
|
|
1346
|
-
inputPlaceholder,
|
|
1347
|
-
selectedContext,
|
|
1348
|
-
inspectElementToggled,
|
|
1349
|
-
multimodalInputEnabled,
|
|
1350
|
-
conversationType,
|
|
1351
|
-
imageInput,
|
|
1352
|
-
isTextInputEmpty,
|
|
1353
|
-
uploadImageInputEnabled,
|
|
1354
|
-
disclaimerText,
|
|
1355
|
-
additionalFloatyContext,
|
|
1356
|
-
onContextClick,
|
|
1357
|
-
onInspectElementClick,
|
|
1358
|
-
onSubmit,
|
|
1359
|
-
onTextAreaKeyDown,
|
|
1360
|
-
onCancel,
|
|
1361
|
-
onNewConversation,
|
|
1362
|
-
onTakeScreenshot,
|
|
1363
|
-
onRemoveImageInput,
|
|
1364
|
-
onTextInputChange,
|
|
1365
|
-
onImageUpload,
|
|
1366
|
-
}: {
|
|
1367
|
-
isLoading: boolean,
|
|
1368
|
-
blockedByCrossOrigin: boolean,
|
|
1369
|
-
isTextInputDisabled: boolean,
|
|
1370
|
-
inputPlaceholder: Platform.UIString.LocalizedString,
|
|
1371
|
-
selectedContext: AiAssistanceModel.AiAgent.ConversationContext<unknown>|null,
|
|
1372
|
-
inspectElementToggled: boolean,
|
|
1373
|
-
isTextInputEmpty: boolean,
|
|
1374
|
-
additionalFloatyContext: UI.Floaty.FloatyContextSelection[],
|
|
1375
|
-
disclaimerText: string,
|
|
1376
|
-
onContextClick: () => void,
|
|
1377
|
-
onInspectElementClick: () => void,
|
|
1378
|
-
onSubmit: (ev: SubmitEvent) => void,
|
|
1379
|
-
onTextAreaKeyDown: (ev: KeyboardEvent) => void,
|
|
1380
|
-
onCancel: (ev: SubmitEvent) => void,
|
|
1381
|
-
onNewConversation: () => void,
|
|
1382
|
-
onTextInputChange: (input: string) => void,
|
|
1383
|
-
conversationType: AiAssistanceModel.AiHistoryStorage.ConversationType,
|
|
1384
|
-
multimodalInputEnabled?: boolean,
|
|
1385
|
-
imageInput?: ImageInputData,
|
|
1386
|
-
uploadImageInputEnabled?: boolean,
|
|
1387
|
-
onTakeScreenshot?: () => void,
|
|
1388
|
-
onRemoveImageInput?: () => void,
|
|
1389
|
-
onImageUpload?: (ev: Event) => void,
|
|
1390
|
-
}): Lit.LitTemplate {
|
|
1391
|
-
const chatInputContainerCls = Lit.Directives.classMap({
|
|
1392
|
-
'chat-input-container': true,
|
|
1393
|
-
'single-line-layout': !selectedContext,
|
|
1394
|
-
disabled: isTextInputDisabled,
|
|
1395
|
-
});
|
|
1396
|
-
|
|
1397
|
-
// clang-format off
|
|
1398
|
-
return html` <form class="input-form" @submit=${onSubmit}>
|
|
1399
|
-
${renderFloatyExtraContext(additionalFloatyContext)}
|
|
1400
|
-
<div class=${chatInputContainerCls}>
|
|
1401
|
-
${renderImageInput({
|
|
1402
|
-
multimodalInputEnabled,
|
|
1403
|
-
imageInput,
|
|
1404
|
-
isTextInputDisabled,
|
|
1405
|
-
onRemoveImageInput,
|
|
1406
|
-
})}
|
|
1407
|
-
<textarea
|
|
1408
|
-
class="chat-input"
|
|
1409
|
-
.disabled=${isTextInputDisabled}
|
|
1410
|
-
wrap="hard"
|
|
1411
|
-
maxlength="10000"
|
|
1412
|
-
@keydown=${onTextAreaKeyDown}
|
|
1413
|
-
@input=${(event: KeyboardEvent) =>
|
|
1414
|
-
onTextInputChange((event.target as HTMLInputElement).value)}
|
|
1415
|
-
placeholder=${inputPlaceholder}
|
|
1416
|
-
jslog=${VisualLogging.textField('query').track({
|
|
1417
|
-
change: true,
|
|
1418
|
-
keydown: 'Enter',
|
|
1419
|
-
})}
|
|
1420
|
-
aria-description=${i18nString(UIStrings.inputTextAriaDescription)}
|
|
1421
|
-
${ref(el => {
|
|
1422
|
-
// If the elements is disabled reset the text to show
|
|
1423
|
-
// the place holder
|
|
1424
|
-
if (el && isTextInputDisabled) {
|
|
1425
|
-
(el as HTMLInputElement).value = '';
|
|
1426
|
-
}
|
|
1427
|
-
})}
|
|
1428
|
-
></textarea>
|
|
1429
|
-
<div class="chat-input-actions">
|
|
1430
|
-
<div class="chat-input-actions-left">
|
|
1431
|
-
${renderSelection({
|
|
1432
|
-
selectedContext,
|
|
1433
|
-
inspectElementToggled,
|
|
1434
|
-
conversationType,
|
|
1435
|
-
isTextInputDisabled,
|
|
1436
|
-
onContextClick,
|
|
1437
|
-
onInspectElementClick,
|
|
1438
|
-
})}
|
|
1439
|
-
</div>
|
|
1440
|
-
<div class="chat-input-actions-right">
|
|
1441
|
-
<div class="chat-input-disclaimer-container">
|
|
1442
|
-
${renderRelevantDataDisclaimer({
|
|
1443
|
-
isLoading,
|
|
1444
|
-
blockedByCrossOrigin,
|
|
1445
|
-
tooltipId: RELEVANT_DATA_LINK_CHAT_ID,
|
|
1446
|
-
disclaimerText,
|
|
1447
|
-
})}
|
|
1448
|
-
</div>
|
|
1449
|
-
${renderMultimodalInputButtons({
|
|
1450
|
-
multimodalInputEnabled,
|
|
1451
|
-
blockedByCrossOrigin,
|
|
1452
|
-
isTextInputDisabled,
|
|
1453
|
-
imageInput,
|
|
1454
|
-
uploadImageInputEnabled,
|
|
1455
|
-
onTakeScreenshot,
|
|
1456
|
-
onImageUpload,
|
|
1457
|
-
})}
|
|
1458
|
-
${renderChatInputButtons({
|
|
1459
|
-
isLoading,
|
|
1460
|
-
blockedByCrossOrigin,
|
|
1461
|
-
isTextInputDisabled,
|
|
1462
|
-
isTextInputEmpty,
|
|
1463
|
-
imageInput,
|
|
1464
|
-
onCancel,
|
|
1465
|
-
onNewConversation,
|
|
1466
|
-
})}
|
|
1467
|
-
</div>
|
|
1468
|
-
</div>
|
|
1469
|
-
</div>
|
|
1470
|
-
</form>`;
|
|
1471
|
-
// clang-format on
|
|
1472
|
-
}
|
|
1473
|
-
|
|
1474
|
-
function renderFloatyExtraContext(contexts: UI.Floaty.FloatyContextSelection[]): Lit.LitTemplate {
|
|
1475
|
-
if (!GreenDev.Prototypes.instance().isEnabled('inDevToolsFloaty')) {
|
|
1476
|
-
return Lit.nothing;
|
|
1477
|
-
}
|
|
1478
|
-
|
|
1479
|
-
// clang-format off
|
|
1480
|
-
return html`
|
|
1481
|
-
<ul class="floaty">
|
|
1482
|
-
${contexts.map(c => {
|
|
1483
|
-
function onDelete(e: MouseEvent): void {
|
|
1484
|
-
e.preventDefault();
|
|
1485
|
-
UI.Floaty.onFloatyContextDelete(c);
|
|
1486
|
-
}
|
|
1487
|
-
|
|
1488
|
-
return html`<li>
|
|
1489
|
-
<span class="context-item">
|
|
1490
|
-
${renderFloatyContext(c)}
|
|
1491
|
-
</span>
|
|
1492
|
-
<devtools-button
|
|
1493
|
-
class="floaty-delete-button"
|
|
1494
|
-
@click=${onDelete}
|
|
1495
|
-
.data=${{
|
|
1496
|
-
variant: Buttons.Button.Variant.ICON,
|
|
1497
|
-
iconName: 'cross',
|
|
1498
|
-
title: 'Delete',
|
|
1499
|
-
size: Buttons.Button.Size.SMALL,
|
|
1500
|
-
} as Buttons.Button.ButtonData}
|
|
1501
|
-
></devtools-button>
|
|
1502
|
-
</li>`;
|
|
1503
|
-
})}
|
|
1504
|
-
<li class="open-floaty">
|
|
1505
|
-
<devtools-button
|
|
1506
|
-
class="floaty-add-button"
|
|
1507
|
-
@click=${UI.Floaty.onFloatyOpen}
|
|
1508
|
-
.data=${{
|
|
1509
|
-
variant: Buttons.Button.Variant.ICON,
|
|
1510
|
-
iconName: 'select-element',
|
|
1511
|
-
title: 'Open context picker',
|
|
1512
|
-
size: Buttons.Button.Size.SMALL,
|
|
1513
|
-
} as Buttons.Button.ButtonData}
|
|
1514
|
-
></devtools-button>
|
|
1515
|
-
</li>
|
|
1516
|
-
</ul>
|
|
1517
|
-
`;
|
|
1518
|
-
// clang-format on
|
|
1519
|
-
}
|
|
1520
|
-
|
|
1521
|
-
function renderFloatyContext(context: UI.Floaty.FloatyContextSelection): Lit.TemplateResult {
|
|
1522
|
-
if (context instanceof SDK.NetworkRequest.NetworkRequest) {
|
|
1523
|
-
return html`${context.url()}`;
|
|
1524
|
-
}
|
|
1525
|
-
|
|
1526
|
-
if (context instanceof SDK.DOMModel.DOMNode) {
|
|
1527
|
-
return html`<devtools-widget .widgetConfig=${
|
|
1528
|
-
UI.Widget.widgetConfig(PanelsCommon.DOMLinkifier.DOMNodeLink, {node: context})}>`;
|
|
1529
|
-
}
|
|
1530
|
-
|
|
1531
|
-
if ('insight' in context) {
|
|
1532
|
-
return html`${context.insight.title}`;
|
|
1533
|
-
}
|
|
1534
|
-
|
|
1535
|
-
if ('event' in context && 'traceStartTime' in context) {
|
|
1536
|
-
const time = Trace.Types.Timing.Micro(context.event.ts - context.traceStartTime);
|
|
1537
|
-
return html`${context.event.name} @ ${i18n.TimeUtilities.formatMicroSecondsAsMillisFixed(time)}`;
|
|
1538
|
-
}
|
|
1539
|
-
|
|
1540
|
-
Platform.assertNever(context, 'Unsupported context');
|
|
1541
|
-
}
|
|
1542
|
-
|
|
1543
|
-
function renderMainContents({
|
|
1544
|
-
messages,
|
|
1545
|
-
isLoading,
|
|
1546
|
-
isReadOnly,
|
|
1547
|
-
canShowFeedbackForm,
|
|
1548
|
-
isTextInputDisabled,
|
|
1549
|
-
suggestions,
|
|
1550
|
-
userInfo,
|
|
1551
|
-
markdownRenderer,
|
|
1552
|
-
changeSummary,
|
|
1553
|
-
changeManager,
|
|
1554
|
-
onSuggestionClick,
|
|
1555
|
-
onFeedbackSubmit,
|
|
1556
|
-
onCopyResponseClick,
|
|
1557
|
-
onMessageContainerRef,
|
|
1558
|
-
}: {
|
|
1559
|
-
messages: ChatMessage[],
|
|
1560
|
-
isLoading: boolean,
|
|
1561
|
-
isReadOnly: boolean,
|
|
1562
|
-
canShowFeedbackForm: boolean,
|
|
1563
|
-
isTextInputDisabled: boolean,
|
|
1564
|
-
suggestions: AiAssistanceModel.AiAgent.ConversationSuggestion[],
|
|
1565
|
-
userInfo: Pick<Host.InspectorFrontendHostAPI.SyncInformation, 'accountImage'|'accountFullName'>,
|
|
1566
|
-
markdownRenderer: MarkdownLitRenderer,
|
|
1567
|
-
changeManager: AiAssistanceModel.ChangeManager.ChangeManager,
|
|
1568
|
-
onSuggestionClick: (suggestion: string) => void,
|
|
1569
|
-
onFeedbackSubmit: (rpcId: Host.AidaClient.RpcGlobalId, rate: Host.AidaClient.Rating, feedback?: string) => void,
|
|
1570
|
-
onCopyResponseClick: (message: ModelChatMessage) => void,
|
|
1571
|
-
onMessageContainerRef: (el: Element|undefined) => void,
|
|
1572
|
-
changeSummary?: string,
|
|
1573
|
-
}): Lit.LitTemplate {
|
|
1574
|
-
if (messages.length > 0) {
|
|
1575
|
-
return renderMessages({
|
|
1576
|
-
messages,
|
|
1577
|
-
isLoading,
|
|
1578
|
-
isReadOnly,
|
|
1579
|
-
canShowFeedbackForm,
|
|
1580
|
-
userInfo,
|
|
1581
|
-
markdownRenderer,
|
|
1582
|
-
changeSummary,
|
|
1583
|
-
changeManager,
|
|
1584
|
-
onSuggestionClick,
|
|
1585
|
-
onFeedbackSubmit,
|
|
1586
|
-
onMessageContainerRef,
|
|
1587
|
-
onCopyResponseClick
|
|
1588
|
-
});
|
|
1589
|
-
}
|
|
1590
|
-
|
|
1591
|
-
return renderEmptyState({isTextInputDisabled, suggestions, onSuggestionClick});
|
|
1592
|
-
}
|
|
1593
|
-
|
|
1594
|
-
function renderDisclaimerTooltip(id: string, disclaimerText: string): Lit.TemplateResult {
|
|
1595
|
-
// clang-format off
|
|
1596
|
-
return html`
|
|
1597
|
-
<devtools-tooltip
|
|
1598
|
-
id=${id}
|
|
1599
|
-
variant="rich"
|
|
1600
|
-
>
|
|
1601
|
-
<div class="info-tooltip-container">
|
|
1602
|
-
${disclaimerText}
|
|
1603
|
-
<button
|
|
1604
|
-
class="link tooltip-link"
|
|
1605
|
-
role="link"
|
|
1606
|
-
jslog=${VisualLogging.link('open-ai-settings').track({
|
|
1607
|
-
click: true,
|
|
1608
|
-
})}
|
|
1609
|
-
@click=${() => {
|
|
1610
|
-
void UI.ViewManager.ViewManager.instance().showView('chrome-ai');
|
|
1611
|
-
}}>${i18nString(UIStrings.learnAbout)}
|
|
1612
|
-
</button>
|
|
1613
|
-
</div>
|
|
1614
|
-
</devtools-tooltip>`;
|
|
1615
|
-
// clang-format on
|
|
1616
|
-
}
|
|
1617
|
-
|
|
1618
421
|
declare global {
|
|
1619
422
|
interface HTMLElementTagNameMap {
|
|
1620
423
|
'devtools-ai-chat-view': ChatView;
|