Skip to content

Commit 9b07794

Browse files
Lightning00BladeDevtools-frontend LUCI CQ
authored andcommitted
Refactor code around AI Assistance
Simplifies code where ever possible to reduce diff with the feature implementation. Bug: 436146297 Change-Id: I51fd196446a45fabdd297ce830ca4abb7d0d8c45 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7179780 Reviewed-by: Alex Rudenko <[email protected]> Reviewed-by: Ergün Erdoğmuş <[email protected]> Commit-Queue: Nikolay Vitkov <[email protected]>
1 parent 482573e commit 9b07794

File tree

6 files changed

+122
-125
lines changed

6 files changed

+122
-125
lines changed

front_end/models/ai_assistance/AiConversation.ts

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,15 @@ import type {ChangeManager} from './ChangeManager.js';
2323
export const NOT_FOUND_IMAGE_DATA = '';
2424
const MAX_TITLE_LENGTH = 80;
2525

26-
export class AiConversation {
27-
static generateContextDetailsMarkdown(details: ContextDetail[]): string {
28-
const detailsMarkdown: string[] = [];
29-
for (const detail of details) {
30-
const text = `\`\`\`\`${detail.codeLang || ''}\n${detail.text.trim()}\n\`\`\`\``;
31-
detailsMarkdown.push(`**${detail.title}:**\n${text}`);
32-
}
33-
return detailsMarkdown.join('\n\n');
26+
export function generateContextDetailsMarkdown(details: ContextDetail[]): string {
27+
const detailsMarkdown: string[] = [];
28+
for (const detail of details) {
29+
const text = `\`\`\`\`${detail.codeLang || ''}\n${detail.text.trim()}\n\`\`\`\``;
30+
detailsMarkdown.push(`**${detail.title}:**\n${text}`);
3431
}
35-
32+
return detailsMarkdown.join('\n\n');
33+
}
34+
export class AiConversation {
3635
static fromSerializedConversation(serializedConversation: SerializedConversation): AiConversation {
3736
const history = serializedConversation.history.map(entry => {
3837
if (entry.type === ResponseType.SIDE_EFFECT) {
@@ -142,7 +141,7 @@ export class AiConversation {
142141
case ResponseType.CONTEXT: {
143142
contentParts.push(`### ${item.title}`);
144143
if (item.details && item.details.length > 0) {
145-
contentParts.push(AiConversation.generateContextDetailsMarkdown(item.details));
144+
contentParts.push(generateContextDetailsMarkdown(item.details));
146145
}
147146
break;
148147
}

front_end/models/ai_assistance/ChangeManager.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,15 @@ export class ChangeManager {
3535
readonly #stylesheetChanges = new Map<Protocol.CSS.StyleSheetId, Change[]>();
3636
readonly #backupStylesheetChanges = new Map<Protocol.CSS.StyleSheetId, Change[]>();
3737

38+
constructor() {
39+
SDK.TargetManager.TargetManager.instance().addModelListener(
40+
SDK.ResourceTreeModel.ResourceTreeModel,
41+
SDK.ResourceTreeModel.Events.PrimaryPageChanged,
42+
this.clear,
43+
this,
44+
);
45+
}
46+
3847
async stashChanges(): Promise<void> {
3948
for (const [cssModel, stylesheetMap] of this.#cssModelToStylesheetId.entries()) {
4049
const stylesheetIds = Array.from(stylesheetMap.values());

front_end/models/ai_assistance/agents/StylingAgent.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -276,12 +276,6 @@ export class StylingAgent extends AiAgent<SDK.DOMModel.DOMNode> {
276276
this.#createExtensionScope = opts.createExtensionScope ?? ((changes: ChangeManager) => {
277277
return new ExtensionScope(changes, this.id, this.context?.getItem() ?? null);
278278
});
279-
SDK.TargetManager.TargetManager.instance().addModelListener(
280-
SDK.ResourceTreeModel.ResourceTreeModel,
281-
SDK.ResourceTreeModel.Events.PrimaryPageChanged,
282-
this.onPrimaryPageChanged,
283-
this,
284-
);
285279

286280
this.declareFunction<{
287281
elements: string[],
@@ -418,10 +412,6 @@ const data = {
418412
});
419413
}
420414

421-
onPrimaryPageChanged(): void {
422-
void this.#changes.clear();
423-
}
424-
425415
async generateObservation(
426416
action: string,
427417
{

front_end/panels/ai_assistance/AiAssistancePanel.test.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -410,14 +410,15 @@ describeWithMockConnection('AI Assistance Panel', () => {
410410
const fakeParsedTrace = {insights: new Map(), data: {Meta: {mainFrameId: ''}}} as Trace.TraceModel.ParsedTrace;
411411
const context = AiAssistanceModel.PerformanceAgent.PerformanceTraceContext.fromParsedTrace(fakeParsedTrace);
412412
UI.Context.Context.instance().setFlavor(AiAssistanceModel.AIContext.AgentFocus, context.getItem());
413-
// Resets the any prior clears from setup
414-
chatView.clearTextInput.reset();
413+
415414
void panel.handleAction('drjones.performance-panel-context');
416-
const nextInput = await view.nextInput;
417-
assert(nextInput.state === AiAssistancePanel.ViewState.CHAT_VIEW);
415+
let nextInput = await view.nextInput;
418416
// Now clear the context and check we cleared out the text
419417
UI.Context.Context.instance().setFlavor(AiAssistanceModel.AIContext.AgentFocus, null);
420-
sinon.assert.callCount(chatView.clearTextInput, 1);
418+
419+
nextInput = await view.nextInput;
420+
assert(nextInput.state === AiAssistancePanel.ViewState.CHAT_VIEW);
421+
assert.isTrue(nextInput.props.isTextInputDisabled);
421422
});
422423
});
423424

front_end/panels/ai_assistance/AiAssistancePanel.ts

Lines changed: 44 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -483,10 +483,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
483483

484484
// Messages displayed in the `ChatView` component.
485485
#messages: ChatMessage[] = [];
486-
// Indicates whether the new conversation context is blocked due to cross-origin restrictions.
487-
// This happens when the conversation's context has a different
488-
// origin than the selected context.
489-
#blockedByCrossOrigin = false;
486+
490487
// Whether the UI should show loading or not.
491488
#isLoading = false;
492489
// Selected conversation context. The reason we keep this as a
@@ -545,7 +542,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
545542
};
546543
}
547544

548-
if (this.#conversation?.type) {
545+
if (this.#conversation) {
549546
const emptyStateSuggestions = await getEmptyStateSuggestions(this.#selectedContext, this.#conversation);
550547
const markdownRenderer = getMarkdownRenderer(this.#selectedContext, this.#conversation);
551548
return {
@@ -659,7 +656,7 @@ export class AiAssistancePanel extends UI.Panel.Panel {
659656
const isSourcesPanelVisible = viewManager.isViewVisible('sources');
660657
const isPerformancePanelVisible = viewManager.isViewVisible('timeline');
661658

662-
let targetConversationType: AiAssistanceModel.AiHistoryStorage.ConversationType|undefined = undefined;
659+
let targetConversationType: AiAssistanceModel.AiHistoryStorage.ConversationType|undefined;
663660
if (isElementsPanelVisible && hostConfig.devToolsFreestyler?.enabled) {
664661
targetConversationType = AiAssistanceModel.AiHistoryStorage.ConversationType.STYLING;
665662
} else if (isNetworkPanelVisible && hostConfig.devToolsAiAssistanceNetworkAgent?.enabled) {
@@ -716,8 +713,6 @@ export class AiAssistancePanel extends UI.Panel.Panel {
716713
this.#conversation = conversation;
717714
}
718715

719-
this.#onContextSelectionChanged();
720-
721716
this.requestUpdate();
722717
}
723718

@@ -874,10 +869,8 @@ export class AiAssistancePanel extends UI.Panel.Panel {
874869
#handleUISourceCodeFlavorChange =
875870
(ev: Common.EventTarget.EventTargetEvent<Workspace.UISourceCode.UISourceCode>): void => {
876871
const newFile = ev.data;
877-
if (!newFile) {
878-
return;
879-
}
880-
if (this.#selectedFile?.getItem() === newFile) {
872+
873+
if (!newFile || this.#selectedFile?.getItem() === newFile) {
881874
return;
882875
}
883876
this.#selectedFile = new AiAssistanceModel.FileAgent.FileContext(ev.data);
@@ -1167,15 +1160,11 @@ export class AiAssistancePanel extends UI.Panel.Panel {
11671160
serializedConversation =>
11681161
AiAssistanceModel.AiConversation.AiConversation.fromSerializedConversation(serializedConversation));
11691162
for (const conversation of historicalConversations.reverse()) {
1170-
if (conversation.isEmpty) {
1171-
continue;
1172-
}
1173-
const title = conversation.title;
1174-
if (!title) {
1163+
if (conversation.isEmpty || !conversation.title) {
11751164
continue;
11761165
}
11771166

1178-
contextMenu.defaultSection().appendCheckboxItem(title, () => {
1167+
contextMenu.defaultSection().appendCheckboxItem(conversation.title, () => {
11791168
void this.#openHistoricConversation(conversation);
11801169
}, {checked: (this.#conversation === conversation), jslogContext: 'freestyler.history-item'});
11811170
}
@@ -1308,10 +1297,9 @@ export class AiAssistancePanel extends UI.Panel.Panel {
13081297
this.#imageInput = {isLoading: true};
13091298
this.requestUpdate();
13101299
}, SHOW_LOADING_STATE_TIMEOUT);
1311-
const reader = new FileReader();
1312-
let dataUrl: string|undefined;
13131300
try {
1314-
dataUrl = await new Promise<string>((resolve, reject) => {
1301+
const reader = new FileReader();
1302+
const dataUrl = await new Promise<string>((resolve, reject) => {
13151303
reader.onload = () => {
13161304
if (typeof reader.result === 'string') {
13171305
resolve(reader.result);
@@ -1321,31 +1309,22 @@ export class AiAssistancePanel extends UI.Panel.Panel {
13211309
};
13221310
reader.readAsDataURL(file);
13231311
});
1312+
const commaIndex = dataUrl.indexOf(',');
1313+
const bytes = dataUrl.substring(commaIndex + 1);
1314+
this.#imageInput = {
1315+
isLoading: false,
1316+
data: bytes,
1317+
mimeType: file.type,
1318+
inputType: AiAssistanceModel.AiAgent.MultimodalInputType.UPLOADED_IMAGE
1319+
};
13241320
} catch {
1325-
clearTimeout(showLoadingTimeout);
13261321
this.#imageInput = undefined;
1327-
this.requestUpdate();
1328-
void this.updateComplete.then(() => {
1329-
this.#viewOutput.chatView?.focusTextInput();
1330-
});
13311322
Snackbars.Snackbar.Snackbar.show({
13321323
message: lockedString(UIStringsNotTranslate.uploadImageFailureMessage),
13331324
});
1334-
return;
13351325
}
13361326

13371327
clearTimeout(showLoadingTimeout);
1338-
if (!dataUrl) {
1339-
return;
1340-
}
1341-
const commaIndex = dataUrl.indexOf(',');
1342-
const bytes = dataUrl.substring(commaIndex + 1);
1343-
this.#imageInput = {
1344-
isLoading: false,
1345-
data: bytes,
1346-
mimeType: file.type,
1347-
inputType: AiAssistanceModel.AiAgent.MultimodalInputType.UPLOADED_IMAGE
1348-
};
13491328
this.requestUpdate();
13501329
void this.updateComplete.then(() => {
13511330
this.#viewOutput.chatView?.focusTextInput();
@@ -1357,21 +1336,18 @@ export class AiAssistancePanel extends UI.Panel.Panel {
13571336
this.#runAbortController = new AbortController();
13581337
}
13591338

1360-
#onContextSelectionChanged(): void {
1339+
// Indicates whether the new conversation context is blocked due to cross-origin restrictions.
1340+
// This happens when the conversation's context has a different
1341+
// origin than the selected context.
1342+
get #blockedByCrossOrigin(): boolean {
13611343
if (!this.#conversation) {
1362-
this.#blockedByCrossOrigin = false;
1363-
return;
1344+
return false;
13641345
}
13651346
this.#selectedContext = this.#getConversationContext(this.#conversation);
13661347
if (!this.#selectedContext) {
1367-
this.#blockedByCrossOrigin = false;
1368-
1369-
// Clear out any text the user has entered into the input but never
1370-
// submitted now they have no active context
1371-
this.#viewOutput.chatView?.clearTextInput();
1372-
return;
1348+
return false;
13731349
}
1374-
this.#blockedByCrossOrigin = !this.#selectedContext.isOriginAllowed(this.#conversation.origin);
1350+
return !this.#selectedContext.isOriginAllowed(this.#conversation.origin);
13751351
}
13761352

13771353
#getConversationContext(conversation?: AiAssistanceModel.AiConversation.AiConversation):
@@ -1398,8 +1374,10 @@ export class AiAssistancePanel extends UI.Panel.Panel {
13981374
}
13991375

14001376
async #startConversation(
1401-
text: string, imageInput?: Host.AidaClient.Part,
1402-
multimodalInputType?: AiAssistanceModel.AiAgent.MultimodalInputType): Promise<void> {
1377+
text: string,
1378+
imageInput?: Host.AidaClient.Part,
1379+
multimodalInputType?: AiAssistanceModel.AiAgent.MultimodalInputType,
1380+
): Promise<void> {
14031381
if (!this.#conversation) {
14041382
return;
14051383
}
@@ -1413,28 +1391,26 @@ export class AiAssistancePanel extends UI.Panel.Panel {
14131391
// invariants do not hold anymore.
14141392
throw new Error('cross-origin context data should not be included');
14151393
}
1416-
if (this.#conversation?.isEmpty) {
1394+
if (this.#conversation.isEmpty) {
14171395
Badges.UserBadges.instance().recordAction(Badges.BadgeAction.STARTED_AI_CONVERSATION);
14181396
}
1419-
const image = isAiAssistanceMultimodalInputEnabled() ? imageInput : undefined;
1420-
const imageId = image ? crypto.randomUUID() : undefined;
1421-
const multimodalInput = image && imageId && multimodalInputType ? {
1422-
input: image,
1423-
id: imageId,
1397+
const multimodalInput = isAiAssistanceMultimodalInputEnabled() && imageInput && multimodalInputType ? {
1398+
input: imageInput,
1399+
id: crypto.randomUUID(),
14241400
type: multimodalInputType,
14251401
} :
1426-
undefined;
1427-
if (this.#conversation) {
1428-
void VisualLogging.logFunctionCall(`start-conversation-${this.#conversation.type}`, 'ui');
1429-
}
1402+
undefined;
14301403

1431-
const generator = this.#conversation.run(
1432-
text, {
1433-
signal,
1434-
selected: context,
1435-
},
1436-
multimodalInput);
1437-
await this.#doConversation(generator);
1404+
void VisualLogging.logFunctionCall(`start-conversation-${this.#conversation.type}`, 'ui');
1405+
1406+
await this.#doConversation(
1407+
this.#conversation.run(
1408+
text, {
1409+
signal,
1410+
selected: context,
1411+
},
1412+
multimodalInput),
1413+
);
14381414
}
14391415

14401416
async #doConversation(
@@ -1607,8 +1583,7 @@ export function getResponseMarkdown(message: ModelChatMessage): string {
16071583
contentParts.push(`### ${step.title}`);
16081584
}
16091585
if (step.contextDetails) {
1610-
contentParts.push(
1611-
AiAssistanceModel.AiConversation.AiConversation.generateContextDetailsMarkdown(step.contextDetails));
1586+
contentParts.push(AiAssistanceModel.AiConversation.generateContextDetailsMarkdown(step.contextDetails));
16121587
}
16131588
if (step.thought) {
16141589
contentParts.push(step.thought);

0 commit comments

Comments
 (0)