Skip to content

Commit

Permalink
Add panel explainer plugin extension link
Browse files Browse the repository at this point in the history
Adds an extension link to the plugin which appears in the panel menu and
produces a modal explaining the panel, by sending the JSON
representation to OpenAI.
  • Loading branch information
sd2k committed Jul 7, 2023
1 parent f546e56 commit 1cd2535
Show file tree
Hide file tree
Showing 8 changed files with 113 additions and 12 deletions.
5 changes: 3 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"react": "17.0.2",
"react-dom": "17.0.2",
"react-router-dom": "^5.2.0",
"react-use": "^17.4.0",
"rxjs": "7.8.0",
"tslib": "2.5.0"
},
Expand Down
2 changes: 1 addition & 1 deletion src/components/AppConfig/AppConfig.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ const updatePluginAndReload = async (pluginId: string, data: Partial<PluginMeta<
};

export const updatePlugin = async (pluginId: string, data: Partial<PluginMeta>) => {
const response = await getBackendSrv().fetch({
const response = getBackendSrv().fetch({
url: `/api/plugins/${pluginId}/settings`,
method: 'POST',
data,
Expand Down
1 change: 1 addition & 0 deletions src/extensions/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { panelExplainer } from './panelExplainer';
63 changes: 63 additions & 0 deletions src/extensions/panelExplainer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import React from 'react';

import { PluginExtensionLinkConfig, PluginExtensionPanelContext, PluginExtensionPoints, PluginExtensionTypes } from "@grafana/data"
import { getBackendSrv } from '@grafana/runtime';
import { useAsync } from 'react-use';
import { Spinner } from '@grafana/ui';
import { Dashboard } from '@grafana/schema';
import { chatCompletions } from 'utils/utils.api';

interface ExplainPanelModalProps {
context: PluginExtensionPanelContext,
}

const panelExplainerPrompt = 'Given the following JSON representation of a Grafana panel, explain what it shows. Be fairly brief in your summary, and use the present tense.';

const ExplainPanelModal = ({ context }: ExplainPanelModalProps) => {
const backendSrv = getBackendSrv();
const state = useAsync(async () => {
const dashboardJSON = await backendSrv.get(`/api/dashboards/uid/${context.dashboard.uid}`) as { dashboard: Dashboard };
const panelJSON = dashboardJSON.dashboard.panels?.find(
// @ts-ignore
(panel) => panel.id === context.id
);
const userPrompt = JSON.stringify(panelJSON, null, 2);
const response = await chatCompletions({
model: 'gpt-3.5-turbo',
systemPrompt: panelExplainerPrompt,
userPrompt,
});
return response;
});
if (state.loading) {
return <Spinner />
}
if (state.error || state.value === undefined) {
return null;
}
return (
<div>
{state.value}
</div>
)
}

export const panelExplainer: PluginExtensionLinkConfig<PluginExtensionPanelContext> = {
title: 'Explain this panel',
description: 'Explain this panel',
type: PluginExtensionTypes.link,
extensionPointId: PluginExtensionPoints.DashboardPanelMenu,
onClick: (event, { context, openModal }) => {
if (event !== undefined) {
event.preventDefault();
}
if (context === undefined) {
return;
}
openModal({
title: 'Panel explanation',
body: () => <ExplainPanelModal context={context} />,
});
},
};

17 changes: 11 additions & 6 deletions src/module.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { AppPlugin } from '@grafana/data';

import { App } from './components/App';
import { AppConfig } from './components/AppConfig';
import { panelExplainer } from './extensions';

export const plugin = new AppPlugin<{}>().setRootPage(App).addConfigPage({
title: 'Configuration',
icon: 'cog',
body: AppConfig,
id: 'configuration',
});
export const plugin = new AppPlugin<{}>()
.setRootPage(App)
.addConfigPage({
title: 'Configuration',
icon: 'cog',
body: AppConfig,
id: 'configuration',
})
.configureExtensionLink(panelExplainer);
9 changes: 6 additions & 3 deletions src/plugin.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
{
"$schema": "https://raw.githubusercontent.com/grafana/grafana/master/docs/sources/developers/plugins/plugin.schema.json",
"type": "app",
"name": "Llm",
"name": "LLM",
"id": "grafana-llm-app",
"backend": true,
"executable": "gpx_llm",
"preload": true,
"info": {
"keywords": ["app"],
"description": "Plugin to easily allow llm based extensions to grafana",
"keywords": [
"app"
],
"description": "Plugin to easily allow LLM based extensions to grafana",
"author": {
"name": "Grafana"
},
Expand Down
27 changes: 27 additions & 0 deletions src/utils/utils.api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { getBackendSrv } from "@grafana/runtime";

export interface ChatCompletionsProps {
model: string;
systemPrompt: string;
userPrompt: string;
}
interface Choice {
message: {
content: string;
}
}

interface ChatCompletionsResponse {
choices: Choice[];
}

export const chatCompletions = async ({ model, systemPrompt, userPrompt }: ChatCompletionsProps): Promise<string> => {
const response = await getBackendSrv().post<ChatCompletionsResponse>('/api/plugins/grafana-llm-app/resources/openai/v1/chat/completions', {
model,
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userPrompt },
],
}, { headers: { 'Content-Type': 'application/json' } });
return response.choices[0].message.content;
}

0 comments on commit 1cd2535

Please sign in to comment.