From 714aec2275f8df1e517a45e50ecf16bd8ca0e9c3 Mon Sep 17 00:00:00 2001
From: Nathan Marrs <nathanielmarrs@gmail.com>
Date: Tue, 31 Oct 2023 13:52:46 -0600
Subject: [PATCH] Auto-generate: Update generation character limits, improve
 generation history UX (#76849)

---
 .../GenAI/GenAIDashDescriptionButton.tsx      |  4 +++-
 .../components/GenAI/GenAIDashTitleButton.tsx |  4 +++-
 .../components/GenAI/GenAIHistory.tsx         |  3 +--
 .../GenAI/GenAIPanelDescriptionButton.tsx     |  6 ++++--
 .../GenAI/GenAIPanelTitleButton.tsx           | 21 +++++++------------
 .../GenAI/MinimalisticPagination.tsx          |  1 +
 .../dashboard/components/GenAI/utils.ts       | 15 +++++++++++++
 7 files changed, 34 insertions(+), 20 deletions(-)

diff --git a/public/app/features/dashboard/components/GenAI/GenAIDashDescriptionButton.tsx b/public/app/features/dashboard/components/GenAI/GenAIDashDescriptionButton.tsx
index b039735c06e..4f9cc429ed0 100644
--- a/public/app/features/dashboard/components/GenAI/GenAIDashDescriptionButton.tsx
+++ b/public/app/features/dashboard/components/GenAI/GenAIDashDescriptionButton.tsx
@@ -11,6 +11,8 @@ interface GenAIDashDescriptionButtonProps {
   dashboard: DashboardModel;
 }
 
+const DASHBOARD_DESCRIPTION_CHAR_LIMIT = 300;
+
 const DESCRIPTION_GENERATION_STANDARD_PROMPT =
   'You are an expert in creating Grafana Dashboards.\n' +
   'Your goal is to write a descriptive and concise dashboard description.\n' +
@@ -19,7 +21,7 @@ const DESCRIPTION_GENERATION_STANDARD_PROMPT =
   'If the dashboard has no panels, the description should be "Empty dashboard"\n' +
   'There should be no numbers in the description except where they are important.\n' +
   'The dashboard description should not have the dashboard title or any quotation marks in it.\n' +
-  'The description should be, at most, 140 characters.\n' +
+  `The description should be, at most, ${DASHBOARD_DESCRIPTION_CHAR_LIMIT} characters.\n` +
   'Respond with only the description of the dashboard.';
 
 export const GenAIDashDescriptionButton = ({ onGenerate, dashboard }: GenAIDashDescriptionButtonProps) => {
diff --git a/public/app/features/dashboard/components/GenAI/GenAIDashTitleButton.tsx b/public/app/features/dashboard/components/GenAI/GenAIDashTitleButton.tsx
index f39e7ce37bd..cebd5cb9478 100644
--- a/public/app/features/dashboard/components/GenAI/GenAIDashTitleButton.tsx
+++ b/public/app/features/dashboard/components/GenAI/GenAIDashTitleButton.tsx
@@ -11,6 +11,8 @@ interface GenAIDashTitleButtonProps {
   onGenerate: (description: string) => void;
 }
 
+const DASH_TITLE_CHAR_LIMIT = 50;
+
 const TITLE_GENERATION_STANDARD_PROMPT =
   'You are an expert in creating Grafana Dashboards.\n' +
   'Your goal is to write a concise dashboard title.\n' +
@@ -19,7 +21,7 @@ const TITLE_GENERATION_STANDARD_PROMPT =
   'If the dashboard has no panels, the title should be "Empty dashboard"\n' +
   'There should be no numbers in the title.\n' +
   'The dashboard title should not have quotation marks in it.\n' +
-  'The title should be, at most, 50 characters.\n' +
+  `The title should be, at most, ${DASH_TITLE_CHAR_LIMIT} characters.\n` +
   'Respond with only the title of the dashboard.';
 
 export const GenAIDashTitleButton = ({ onGenerate, dashboard }: GenAIDashTitleButtonProps) => {
diff --git a/public/app/features/dashboard/components/GenAI/GenAIHistory.tsx b/public/app/features/dashboard/components/GenAI/GenAIHistory.tsx
index 51a2d1072e3..71006a897d0 100644
--- a/public/app/features/dashboard/components/GenAI/GenAIHistory.tsx
+++ b/public/app/features/dashboard/components/GenAI/GenAIHistory.tsx
@@ -16,12 +16,11 @@ import {
   VerticalGroup,
 } from '@grafana/ui';
 
-import { getFeedbackMessage } from './GenAIPanelTitleButton';
 import { GenerationHistoryCarousel } from './GenerationHistoryCarousel';
 import { QuickFeedback } from './QuickFeedback';
 import { StreamStatus, useOpenAIStream } from './hooks';
 import { AutoGenerateItem, EventTrackingSrc, reportAutoGenerateInteraction } from './tracking';
-import { Message, DEFAULT_OAI_MODEL, QuickFeedbackType, sanitizeReply } from './utils';
+import { getFeedbackMessage, Message, DEFAULT_OAI_MODEL, QuickFeedbackType, sanitizeReply } from './utils';
 
 export interface GenAIHistoryProps {
   history: string[];
diff --git a/public/app/features/dashboard/components/GenAI/GenAIPanelDescriptionButton.tsx b/public/app/features/dashboard/components/GenAI/GenAIPanelDescriptionButton.tsx
index 570443e8759..fa42e40c336 100644
--- a/public/app/features/dashboard/components/GenAI/GenAIPanelDescriptionButton.tsx
+++ b/public/app/features/dashboard/components/GenAI/GenAIPanelDescriptionButton.tsx
@@ -12,6 +12,8 @@ interface GenAIPanelDescriptionButtonProps {
   panel: PanelModel;
 }
 
+const PANEL_DESCRIPTION_CHAR_LIMIT = 200;
+
 const DESCRIPTION_GENERATION_STANDARD_PROMPT =
   'You are an expert in creating Grafana Panels.\n' +
   'You will be given the title and description of the dashboard the panel is in as well as the JSON for the panel.\n' +
@@ -19,7 +21,7 @@ const DESCRIPTION_GENERATION_STANDARD_PROMPT =
   'The panel description is meant to explain the purpose of the panel, not just its attributes.\n' +
   'Do not refer to the panel; simply describe its purpose.\n' +
   'There should be no numbers in the description except for thresholds.\n' +
-  'The description should be, at most, 140 characters.';
+  `The description should be, at most, ${PANEL_DESCRIPTION_CHAR_LIMIT} characters.`;
 
 export const GenAIPanelDescriptionButton = ({ onGenerate, panel }: GenAIPanelDescriptionButtonProps) => {
   const messages = React.useMemo(() => getMessages(panel), [panel]);
@@ -48,7 +50,7 @@ function getMessages(panel: PanelModel): Message[] {
       role: Role.system,
     },
     {
-      content: `The panel is part of a dashboard with the description: ${dashboard.title}`,
+      content: `The panel is part of a dashboard with the description: ${dashboard.description}`,
       role: Role.system,
     },
     {
diff --git a/public/app/features/dashboard/components/GenAI/GenAIPanelTitleButton.tsx b/public/app/features/dashboard/components/GenAI/GenAIPanelTitleButton.tsx
index 8e58103d826..3cb314623ac 100644
--- a/public/app/features/dashboard/components/GenAI/GenAIPanelTitleButton.tsx
+++ b/public/app/features/dashboard/components/GenAI/GenAIPanelTitleButton.tsx
@@ -5,17 +5,19 @@ import { PanelModel } from '../../state';
 
 import { GenAIButton } from './GenAIButton';
 import { EventTrackingSrc } from './tracking';
-import { Message, QuickFeedbackType, Role } from './utils';
+import { Message, Role } from './utils';
 
 interface GenAIPanelTitleButtonProps {
   onGenerate: (title: string) => void;
   panel: PanelModel;
 }
 
+const PANEL_TITLE_CHAR_LIMIT = 50;
+
 const TITLE_GENERATION_STANDARD_PROMPT =
   'You are an expert in creating Grafana Panels.' +
-  'Your goal is to write short, descriptive, and concise panel title for a panel.' +
-  'The title should be shorter than 50 characters.';
+  'Your goal is to write short, descriptive, and concise panel title.' +
+  `The title should be shorter than ${PANEL_TITLE_CHAR_LIMIT} characters.`;
 
 export const GenAIPanelTitleButton = ({ onGenerate, panel }: GenAIPanelTitleButtonProps) => {
   const messages = React.useMemo(() => getMessages(panel), [panel]);
@@ -44,21 +46,12 @@ function getMessages(panel: PanelModel): Message[] {
       role: Role.system,
     },
     {
-      content: `The panel is part of a dashboard with the description: ${dashboard.title}`,
+      content: `The panel is part of a dashboard with the description: ${dashboard.description}`,
       role: Role.system,
     },
     {
       content: `Use this JSON object which defines the panel: ${JSON.stringify(panel.getSaveModel())}`,
-      role: Role.user,
+      role: Role.system,
     },
   ];
 }
-
-export const getFeedbackMessage = (previousResponse: string, feedback: string | QuickFeedbackType): Message[] => {
-  return [
-    {
-      role: Role.system,
-      content: `Your previous response was: ${previousResponse}. The user has provided the following feedback: ${feedback}. Re-generate your response according to the provided feedback.`,
-    },
-  ];
-};
diff --git a/public/app/features/dashboard/components/GenAI/MinimalisticPagination.tsx b/public/app/features/dashboard/components/GenAI/MinimalisticPagination.tsx
index 623532656ee..ce59b2a69de 100644
--- a/public/app/features/dashboard/components/GenAI/MinimalisticPagination.tsx
+++ b/public/app/features/dashboard/components/GenAI/MinimalisticPagination.tsx
@@ -51,5 +51,6 @@ const getStyles = (theme: GrafanaTheme2) => ({
     display: 'flex',
     flexDirection: 'row',
     gap: 16,
+    userSelect: 'none',
   }),
 });
diff --git a/public/app/features/dashboard/components/GenAI/utils.ts b/public/app/features/dashboard/components/GenAI/utils.ts
index 3d695ba0538..ca5607c4d90 100644
--- a/public/app/features/dashboard/components/GenAI/utils.ts
+++ b/public/app/features/dashboard/components/GenAI/utils.ts
@@ -65,6 +65,21 @@ export async function isLLMPluginEnabled() {
   return llms.openai.enabled().then((response) => response.ok);
 }
 
+/**
+ * Get the message to be sent to OpenAI to generate a new response.
+ * @param previousResponse
+ * @param feedback
+ * @returns Message[] to be sent to OpenAI to generate a new response
+ */
+export const getFeedbackMessage = (previousResponse: string, feedback: string | QuickFeedbackType): Message[] => {
+  return [
+    {
+      role: Role.system,
+      content: `Your previous response was: ${previousResponse}. The user has provided the following feedback: ${feedback}. Re-generate your response according to the provided feedback.`,
+    },
+  ];
+};
+
 /**
  *
  * @param dashboard Dashboard to generate a title or description for