Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 21 additions & 18 deletions ui/src/plugins/com.google.PerfettoMcp/chat_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ import {
GenerateContentResponse,
GenerateContentResponseUsageMetadata,
} from '@google/genai';
import {Trace} from '../../public/trace';
import {TextInput} from '../../widgets/text_input';
import { Trace } from '../../public/trace';
import { TextInput } from '../../widgets/text_input';
import markdownit from 'markdown-it';
import {Button, ButtonVariant} from '../../widgets/button';
import {Intent} from '../../widgets/common';
import {Setting} from '../../public/settings';
import { Button, ButtonVariant } from '../../widgets/button';
import { Intent } from '../../widgets/common';
import { Setting } from '../../public/settings';

// Interface for a single message in the chat display

Expand All @@ -50,7 +50,7 @@ export class ChatPage implements m.ClassComponent<ChatPageAttrs> {
private md: markdownit;
private usage?: GenerateContentResponseUsageMetadata;

constructor({attrs}: m.CVnode<ChatPageAttrs>) {
constructor({ attrs }: m.CVnode<ChatPageAttrs>) {
this.chat = attrs.chat;
this.showThoughts = attrs.showThoughts;
this.showTokens = attrs.showTokens;
Expand Down Expand Up @@ -102,7 +102,7 @@ export class ChatPage implements m.ClassComponent<ChatPageAttrs> {
lastResponse.text += text;
this.messages[this.messages.length - 1] = lastResponse;
} else {
this.messages.push({role: 'ai', text: text});
this.messages.push({ role: 'ai', text: text });
}
}

Expand All @@ -112,21 +112,24 @@ export class ChatPage implements m.ClassComponent<ChatPageAttrs> {
// Prevent sending empty messages or sending while a request is in flight
if (trimmedInput === '' || this.isLoading) return;

this.messages.push({role: 'user', text: trimmedInput});
this.messages.push({ role: 'user', text: trimmedInput });
this.isLoading = true;
this.userInput = '';
m.redraw();

try {
const responseStream = await this.chat.sendMessageStream({
message: trimmedInput,
message: {
role: 'user',
parts: [{ text: trimmedInput }],
},
});

for await (const part of responseStream) {
this.processResponse(part);
}

this.messages.push({role: 'spacer', text: ''});
this.messages.push({ role: 'spacer', text: '' });
m.redraw();
} catch (error) {
console.error('AI API call failed:', error);
Expand Down Expand Up @@ -210,14 +213,14 @@ export class ChatPage implements m.ClassComponent<ChatPageAttrs> {

this.showTokens.get()
? [
m('.pf-ai-chat-panel__tokens', [
m('div.pf-ai-chat-panel__tokens__label', 'Tokens'),
m(
'div.pf-ai-chat-panel__tokens__count',
this.usage?.totalTokenCount ?? '--',
),
]),
]
m('.pf-ai-chat-panel__tokens', [
m('div.pf-ai-chat-panel__tokens__label', 'Tokens'),
m(
'div.pf-ai-chat-panel__tokens__count',
this.usage?.totalTokenCount ?? '--',
),
]),
]
: [],

m(Button, {
Expand Down
71 changes: 49 additions & 22 deletions ui/src/plugins/com.google.PerfettoMcp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,24 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import {Trace} from '../../public/trace';
import {App} from '../../public/app';
import {PerfettoPlugin} from '../../public/plugin';
import {McpServer} from '@modelcontextprotocol/sdk/server/mcp.js';
import {Client} from '@modelcontextprotocol/sdk/client/index.js';
import {InMemoryTransport} from '@modelcontextprotocol/sdk/inMemory.js';
import { Trace } from '../../public/trace';
import { App } from '../../public/app';
import { PerfettoPlugin } from '../../public/plugin';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
import {
CallableTool,
FunctionCallingConfigMode,
GoogleGenAI,
mcpToTool,
} from '@google/genai';
import {registerTraceTools} from './tracetools';
import {z} from 'zod';
import {Setting} from 'src/public/settings';
import {ChatPage} from './chat_page';
import { registerTraceTools } from './tracetools';
import { z } from 'zod';
import { Setting } from 'src/public/settings';
import { ChatPage } from './chat_page';
import m from 'mithril';
import {registerUiTools} from './uitools';
import { registerUiTools } from './uitools';

export default class PerfettoMcpPlugin implements PerfettoPlugin {
static readonly id = 'com.google.PerfettoMcp';
Expand Down Expand Up @@ -75,9 +75,9 @@ export default class PerfettoMcpPlugin implements PerfettoPlugin {
PerfettoMcpPlugin.modelNameSetting = app.settings.register({
id: `${PerfettoMcpPlugin.id}#ModelNameSetting`,
name: 'Gemini Model',
description: 'The Gemini model to use, such as gemini-2.5-pro.',
description: 'The Gemini model to use, such as gemini-3-flash-preview.',
schema: z.string(),
defaultValue: 'gemini-2.5-pro',
defaultValue: 'gemini-3-flash-preview',
requiresReload: true,
});

Expand All @@ -90,7 +90,7 @@ export default class PerfettoMcpPlugin implements PerfettoPlugin {
defaultValue: '',
requiresReload: true,
render: (setting) => {
const handleFileSelect = (event: {target: HTMLInputElement}) => {
const handleFileSelect = (event: { target: HTMLInputElement }) => {
const file = event.target.files?.[0];

if (!file) {
Expand Down Expand Up @@ -146,27 +146,54 @@ export default class PerfettoMcpPlugin implements PerfettoPlugin {

const tool: CallableTool = mcpToTool(client);

const ai = new GoogleGenAI({apiKey: PerfettoMcpPlugin.tokenSetting.get()});
const ai = new GoogleGenAI({ apiKey: PerfettoMcpPlugin.tokenSetting.get() });

const chat = await ai.chats.create({
model: PerfettoMcpPlugin.modelNameSetting.get(),
config: {
systemInstruction:
'You are an expert in analyzing perfetto traces. \n\n' +
PerfettoMcpPlugin.promptSetting.get(),
systemInstruction: {
role: 'system',
parts: [
{
text:
'You are an expert in analyzing perfetto traces. \n\n' +
PerfettoMcpPlugin.promptSetting.get(),
},
],
},
tools: [tool],
toolConfig: {
functionCallingConfig: {
mode: FunctionCallingConfigMode.AUTO,
},
},
thinkingConfig: {
includeThoughts: true,
thinkingBudget: -1, // Automatic
},
thinkingConfig: PerfettoMcpPlugin.thoughtsSetting.get()
? {
includeThoughts: true,
thinkingBudget: 1024,
}
: undefined,
automaticFunctionCalling: {
maximumRemoteCalls: 20,
},
safetySettings: [
{
category: 'HATE_SPEECH',
threshold: 'BLOCK_NONE',
},
{
category: 'HARASSMENT',
threshold: 'BLOCK_NONE',
},
{
category: 'SEXUALLY_EXPLICIT',
threshold: 'BLOCK_NONE',
},
{
category: 'DANGEROUS_CONTENT',
threshold: 'BLOCK_NONE',
},
],
},
});

Expand Down
Loading
Loading