mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-20 01:02:02 +00:00
feat: display web docs progress
This commit is contained in:
parent
ce0b90f232
commit
03feba0a4f
11 changed files with 195 additions and 32 deletions
|
|
@ -57,6 +57,7 @@ dependencies {
|
|||
implementation(platform(libs.jackson.bom))
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jdk8")
|
||||
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310")
|
||||
implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||
implementation(libs.flexmark.all) {
|
||||
// vulnerable transitive dependency
|
||||
exclude(group = "org.jsoup", module = "jsoup")
|
||||
|
|
|
|||
|
|
@ -25,4 +25,6 @@ public final class Icons {
|
|||
public static final Icon Ollama = IconLoader.getIcon("/icons/ollama.svg", Icons.class);
|
||||
public static final Icon User = IconLoader.getIcon("/icons/user.svg", Icons.class);
|
||||
public static final Icon Upload = IconLoader.getIcon("/icons/upload.svg", Icons.class);
|
||||
public static final Icon GreenCheckmark =
|
||||
IconLoader.getIcon("/icons/greenCheckmark.svg", Icons.class);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ import ee.carlrobert.codegpt.toolwindow.ui.ChatToolWindowLandingPanel;
|
|||
import ee.carlrobert.codegpt.ui.OverlayUtil;
|
||||
import ee.carlrobert.codegpt.ui.textarea.AppliedActionInlay;
|
||||
import ee.carlrobert.codegpt.ui.textarea.UserInputPanel;
|
||||
import ee.carlrobert.codegpt.ui.textarea.suggestion.item.CreateDocumentationActionItem;
|
||||
import ee.carlrobert.codegpt.ui.textarea.suggestion.item.DocumentationActionItem;
|
||||
import ee.carlrobert.codegpt.ui.textarea.suggestion.item.PersonaActionItem;
|
||||
import ee.carlrobert.codegpt.ui.textarea.suggestion.item.WebSearchActionItem;
|
||||
|
|
@ -175,7 +176,8 @@ public class ChatToolWindowTabPanel implements Disposable {
|
|||
.withReloadAction(() -> reloadMessage(message, conversation, conversationType))
|
||||
.withDeleteAction(() -> removeMessage(message.getId(), conversation))
|
||||
.addContent(
|
||||
new ChatMessageResponseBody(project, true, false, message.isWebSearchIncluded(), this));
|
||||
new ChatMessageResponseBody(project, true, false, message.isWebSearchIncluded(),
|
||||
message.getDocumentationDetails() != null, this));
|
||||
}
|
||||
|
||||
private void reloadMessage(
|
||||
|
|
@ -265,7 +267,8 @@ public class ChatToolWindowTabPanel implements Disposable {
|
|||
|
||||
var addedDocumentation = CodeGPTKeys.ADDED_DOCUMENTATION.get(project);
|
||||
var appliedInlayExists = appliedInlayActions.stream()
|
||||
.anyMatch(it -> it.getSuggestion() instanceof DocumentationActionItem);
|
||||
.anyMatch(it -> it.getSuggestion() instanceof DocumentationActionItem
|
||||
|| it.getSuggestion() instanceof CreateDocumentationActionItem);
|
||||
if (addedDocumentation != null && appliedInlayExists) {
|
||||
message.setDocumentationDetails(addedDocumentation);
|
||||
CodeGPTKeys.ADDED_DOCUMENTATION.set(project, null);
|
||||
|
|
@ -273,7 +276,7 @@ public class ChatToolWindowTabPanel implements Disposable {
|
|||
|
||||
var addedPersona = CodeGPTKeys.ADDED_PERSONA.get(project);
|
||||
var personaInlayExists = appliedInlayActions.stream()
|
||||
.anyMatch(it -> it.getSuggestion() instanceof PersonaActionItem);
|
||||
.anyMatch(it -> it.getSuggestion() instanceof PersonaActionItem);
|
||||
if (addedPersona != null && personaInlayExists) {
|
||||
message.setPersonaDetails(addedPersona);
|
||||
CodeGPTKeys.ADDED_PERSONA.set(project, null);
|
||||
|
|
|
|||
|
|
@ -121,8 +121,7 @@ abstract class ToolWindowCompletionResponseEventListener implements
|
|||
|
||||
@Override
|
||||
public void handleCodeGPTEvent(CodeGPTEvent event) {
|
||||
ApplicationManager.getApplication().invokeLater(() ->
|
||||
responseContainer.displayWebSearchItem(event.getEvent().getDetails()));
|
||||
responseContainer.handleCodeGPTEvent(event);
|
||||
}
|
||||
|
||||
private void stopStreaming(ChatMessageResponseBody responseContainer) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import static ee.carlrobert.codegpt.util.MarkdownUtil.convertMdToHtml;
|
|||
import static java.lang.String.format;
|
||||
import static javax.swing.event.HyperlinkEvent.EventType.ACTIVATED;
|
||||
|
||||
import com.intellij.icons.AllIcons.General;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||
|
|
@ -14,12 +15,19 @@ import com.intellij.openapi.util.io.FileUtil;
|
|||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.ui.components.JBLabel;
|
||||
import com.intellij.util.ui.AsyncProcessIcon;
|
||||
import com.intellij.util.ui.JBFont;
|
||||
import com.intellij.util.ui.JBUI;
|
||||
import com.vladsch.flexmark.ast.FencedCodeBlock;
|
||||
import com.vladsch.flexmark.parser.Parser;
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle;
|
||||
import ee.carlrobert.codegpt.Icons;
|
||||
import ee.carlrobert.codegpt.actions.ActionType;
|
||||
import ee.carlrobert.codegpt.events.Details;
|
||||
import ee.carlrobert.codegpt.events.AnalysisCompletedEventDetails;
|
||||
import ee.carlrobert.codegpt.events.AnalysisFailedEventDetails;
|
||||
import ee.carlrobert.codegpt.events.CodeGPTEvent;
|
||||
import ee.carlrobert.codegpt.events.EventDetails;
|
||||
import ee.carlrobert.codegpt.events.WebSearchEventDetails;
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettingsConfigurable;
|
||||
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.StreamParser;
|
||||
|
|
@ -29,11 +37,16 @@ import ee.carlrobert.codegpt.ui.UIUtil;
|
|||
import ee.carlrobert.codegpt.util.EditorUtil;
|
||||
import ee.carlrobert.codegpt.util.MarkdownUtil;
|
||||
import java.awt.BorderLayout;
|
||||
import java.awt.Component;
|
||||
import java.awt.FlowLayout;
|
||||
import java.util.Objects;
|
||||
import javax.swing.Box;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.DefaultListModel;
|
||||
import javax.swing.Icon;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.JTextPane;
|
||||
import javax.swing.SwingConstants;
|
||||
|
||||
public class ChatMessageResponseBody extends JPanel {
|
||||
|
||||
|
|
@ -41,8 +54,10 @@ public class ChatMessageResponseBody extends JPanel {
|
|||
private final Disposable parentDisposable;
|
||||
private final StreamParser streamParser;
|
||||
private final boolean readOnly;
|
||||
private final DefaultListModel<Details> webpageListModel = new DefaultListModel<>();
|
||||
private final DefaultListModel<WebSearchEventDetails> webpageListModel = new DefaultListModel<>();
|
||||
private final WebpageList webpageList = new WebpageList(webpageListModel);
|
||||
private final JPanel webDocProgressContainer = new JPanel();
|
||||
private final AsyncProcessIcon spinner = new AsyncProcessIcon("sign_in_spinner");
|
||||
private ResponseEditorPanel currentlyProcessedEditorPanel;
|
||||
private JTextPane currentlyProcessedTextPane;
|
||||
private JPanel webpageListPanel;
|
||||
|
|
@ -56,7 +71,7 @@ public class ChatMessageResponseBody extends JPanel {
|
|||
Project project,
|
||||
boolean withGhostText,
|
||||
Disposable parentDisposable) {
|
||||
this(project, withGhostText, false, false, parentDisposable);
|
||||
this(project, withGhostText, false, false, false, parentDisposable);
|
||||
}
|
||||
|
||||
public ChatMessageResponseBody(
|
||||
|
|
@ -64,13 +79,14 @@ public class ChatMessageResponseBody extends JPanel {
|
|||
boolean withGhostText,
|
||||
boolean readOnly,
|
||||
boolean webSearchIncluded,
|
||||
boolean webDocIncluded,
|
||||
Disposable parentDisposable) {
|
||||
super(new BorderLayout());
|
||||
this.project = project;
|
||||
this.parentDisposable = parentDisposable;
|
||||
this.streamParser = new StreamParser();
|
||||
this.readOnly = readOnly;
|
||||
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
|
||||
setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
|
||||
setOpaque(false);
|
||||
|
||||
if (webSearchIncluded) {
|
||||
|
|
@ -78,6 +94,12 @@ public class ChatMessageResponseBody extends JPanel {
|
|||
add(webpageListPanel);
|
||||
}
|
||||
|
||||
if (webDocIncluded) {
|
||||
webDocProgressContainer.setLayout(new BoxLayout(webDocProgressContainer, BoxLayout.Y_AXIS));
|
||||
webDocProgressContainer.setBorder(JBUI.Borders.emptyBottom(8));
|
||||
add(webDocProgressContainer);
|
||||
}
|
||||
|
||||
if (withGhostText) {
|
||||
prepareProcessingText(!readOnly);
|
||||
currentlyProcessedTextPane.setText(
|
||||
|
|
@ -161,10 +183,30 @@ public class ChatMessageResponseBody extends JPanel {
|
|||
});
|
||||
}
|
||||
|
||||
public void displayWebSearchItem(Details details) {
|
||||
webpageListModel.addElement(details);
|
||||
webpageList.revalidate();
|
||||
webpageList.repaint();
|
||||
public void handleCodeGPTEvent(CodeGPTEvent codegptEvent) {
|
||||
ApplicationManager.getApplication()
|
||||
.invokeLater(() -> {
|
||||
var event = codegptEvent.getEvent();
|
||||
if (event.getDetails() instanceof WebSearchEventDetails webSearchEventDetails) {
|
||||
displayWebSearchItem(webSearchEventDetails);
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.getType()) {
|
||||
case WEB_SEARCH_ITEM -> {
|
||||
if (event.getDetails() != null
|
||||
&& event.getDetails() instanceof WebSearchEventDetails eventDetails) {
|
||||
displayWebSearchItem(eventDetails);
|
||||
}
|
||||
}
|
||||
|
||||
case ANALYZE_WEB_DOC_STARTED -> showWebDocsProgress();
|
||||
case ANALYZE_WEB_DOC_COMPLETED -> completeWebDocsProgress(event.getDetails());
|
||||
case ANALYZE_WEB_DOC_FAILED -> failWebDocsProgress(event.getDetails());
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void hideCaret() {
|
||||
|
|
@ -236,6 +278,45 @@ public class ChatMessageResponseBody extends JPanel {
|
|||
add(currentlyProcessedEditorPanel);
|
||||
}
|
||||
|
||||
private void displayWebSearchItem(WebSearchEventDetails details) {
|
||||
webpageListModel.addElement(details);
|
||||
webpageList.revalidate();
|
||||
webpageList.repaint();
|
||||
}
|
||||
|
||||
private void showWebDocsProgress() {
|
||||
var wrapper = new JPanel(new FlowLayout(FlowLayout.LEADING, 0, 0));
|
||||
wrapper.add(spinner);
|
||||
wrapper.add(Box.createHorizontalStrut(4));
|
||||
wrapper.add(new JBLabel(
|
||||
CodeGPTBundle.get("chatMessageResponseBody.webDocs.startProgress.label")).withFont(
|
||||
JBFont.small()));
|
||||
updateWebDocsProgress(wrapper);
|
||||
}
|
||||
|
||||
private void completeWebDocsProgress(EventDetails eventDetails) {
|
||||
if (eventDetails instanceof AnalysisCompletedEventDetails defaultEventDetails) {
|
||||
updateWebDocsProgressLabel(defaultEventDetails.getDescription(), Icons.GreenCheckmark);
|
||||
}
|
||||
}
|
||||
|
||||
private void failWebDocsProgress(EventDetails eventDetails) {
|
||||
if (eventDetails instanceof AnalysisFailedEventDetails failedEventDetails) {
|
||||
updateWebDocsProgressLabel(failedEventDetails.getError(), General.Error);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateWebDocsProgressLabel(String text, Icon icon) {
|
||||
updateWebDocsProgress(new JBLabel(text, icon, SwingConstants.LEADING).withFont(JBFont.small()));
|
||||
}
|
||||
|
||||
private void updateWebDocsProgress(Component content) {
|
||||
webDocProgressContainer.removeAll();
|
||||
webDocProgressContainer.add(JBUI.Panels.simplePanel(content));
|
||||
webDocProgressContainer.revalidate();
|
||||
webDocProgressContainer.repaint();
|
||||
}
|
||||
|
||||
private JTextPane createTextPane(String text, boolean caretVisible) {
|
||||
var textPane = UIUtil.createTextPane(text, false, event -> {
|
||||
if (FileUtil.exists(event.getDescription()) && ACTIVATED.equals(event.getEventType())) {
|
||||
|
|
@ -258,7 +339,7 @@ public class ChatMessageResponseBody extends JPanel {
|
|||
var title = new JPanel(new BorderLayout());
|
||||
title.setOpaque(false);
|
||||
title.setBorder(JBUI.Borders.empty(8, 0));
|
||||
title.add(new JBLabel(CodeGPTBundle.get("chatMessageResponseBody.webPagesTitle"))
|
||||
title.add(new JBLabel(CodeGPTBundle.get("chatMessageResponseBody.webPages.title"))
|
||||
.withFont(JBUI.Fonts.miniFont()), BorderLayout.LINE_START);
|
||||
var listPanel = new JPanel(new BorderLayout());
|
||||
listPanel.add(webpageList, BorderLayout.LINE_START);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ import ee.carlrobert.codegpt.CodeGPTBundle;
|
|||
import ee.carlrobert.codegpt.CodeGPTKeys;
|
||||
import ee.carlrobert.codegpt.Icons;
|
||||
import ee.carlrobert.codegpt.conversations.message.Message;
|
||||
import ee.carlrobert.codegpt.events.Details;
|
||||
import ee.carlrobert.codegpt.events.WebSearchEventDetails;
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings;
|
||||
import ee.carlrobert.codegpt.toolwindow.ui.WebpageList;
|
||||
import java.awt.BorderLayout;
|
||||
|
|
@ -66,9 +66,10 @@ public class UserMessagePanel extends JPanel {
|
|||
var panel = new JPanel(new BorderLayout());
|
||||
panel.setOpaque(false);
|
||||
if (addedDocumentation != null) {
|
||||
var listModel = new DefaultListModel<Details>();
|
||||
listModel.addElement(new Details(UUID.randomUUID().toString(), addedDocumentation.getName(),
|
||||
addedDocumentation.getUrl(), addedDocumentation.getUrl()));
|
||||
var listModel = new DefaultListModel<WebSearchEventDetails>();
|
||||
listModel.addElement(
|
||||
new WebSearchEventDetails(UUID.randomUUID(), addedDocumentation.getName(),
|
||||
addedDocumentation.getUrl(), addedDocumentation.getUrl()));
|
||||
panel.add(createWebpageListPanel(new WebpageList(listModel)), BorderLayout.NORTH);
|
||||
}
|
||||
|
||||
|
|
@ -99,6 +100,7 @@ public class UserMessagePanel extends JPanel {
|
|||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
parentDisposable)
|
||||
.withResponse(prompt);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,19 +2,86 @@ package ee.carlrobert.codegpt.events
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.core.JsonParser
|
||||
import com.fasterxml.jackson.databind.DeserializationContext
|
||||
import com.fasterxml.jackson.databind.JsonNode
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer
|
||||
import com.fasterxml.jackson.module.kotlin.registerKotlinModule
|
||||
import ee.carlrobert.codegpt.events.Event.EventType
|
||||
import java.util.*
|
||||
|
||||
data class CodeGPTEvent @JsonCreator constructor(
|
||||
@JsonProperty("event") val event: Event
|
||||
)
|
||||
|
||||
@JsonDeserialize(using = EventDeserializer::class)
|
||||
data class Event @JsonCreator constructor(
|
||||
@JsonProperty("details") val details: Details,
|
||||
@JsonProperty("type") val type: String
|
||||
)
|
||||
val details: EventDetails?,
|
||||
val type: EventType
|
||||
) {
|
||||
enum class EventType {
|
||||
ANALYZE_WEB_DOC_STARTED,
|
||||
ANALYZE_WEB_DOC_COMPLETED,
|
||||
ANALYZE_WEB_DOC_FAILED,
|
||||
WEB_SEARCH_ITEM
|
||||
}
|
||||
}
|
||||
|
||||
data class Details @JsonCreator constructor(
|
||||
@JsonProperty("id") val id: String,
|
||||
@JsonProperty("name") val name: String,
|
||||
@JsonProperty("url") val url: String,
|
||||
@JsonProperty("displayUrl") val displayUrl: String
|
||||
)
|
||||
interface EventDetails
|
||||
|
||||
data class WebSearchEventDetails(
|
||||
val id: UUID,
|
||||
val name: String,
|
||||
val url: String,
|
||||
val displayUrl: String
|
||||
) : EventDetails
|
||||
|
||||
data class AnalysisCompletedEventDetails(
|
||||
val id: UUID,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val rerankedResults: List<String>
|
||||
) : EventDetails
|
||||
|
||||
data class AnalysisFailedEventDetails(
|
||||
val id: UUID,
|
||||
val name: String,
|
||||
val description: String,
|
||||
val error: String
|
||||
) : EventDetails
|
||||
|
||||
data class DefaultEventDetails(
|
||||
val id: UUID,
|
||||
val name: String,
|
||||
val description: String
|
||||
) : EventDetails
|
||||
|
||||
class EventDeserializer : StdDeserializer<Event>(Event::class.java) {
|
||||
private val objectMapper = ObjectMapper().registerKotlinModule()
|
||||
|
||||
override fun deserialize(parser: JsonParser, ctxt: DeserializationContext): Event {
|
||||
val node: JsonNode = parser.codec.readTree(parser)
|
||||
val type = EventType.valueOf(node.get("type").asText())
|
||||
val detailsNode = node.get("details") ?: return Event(null, type)
|
||||
val eventDetails = when (type) {
|
||||
EventType.WEB_SEARCH_ITEM -> {
|
||||
objectMapper.treeToValue(detailsNode, WebSearchEventDetails::class.java)
|
||||
}
|
||||
|
||||
EventType.ANALYZE_WEB_DOC_COMPLETED -> {
|
||||
objectMapper.treeToValue(detailsNode, AnalysisCompletedEventDetails::class.java)
|
||||
}
|
||||
|
||||
EventType.ANALYZE_WEB_DOC_FAILED -> {
|
||||
objectMapper.treeToValue(detailsNode, AnalysisFailedEventDetails::class.java)
|
||||
}
|
||||
|
||||
else -> {
|
||||
objectMapper.treeToValue(detailsNode, DefaultEventDetails::class.java)
|
||||
}
|
||||
}
|
||||
return Event(eventDetails, type)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,14 +16,15 @@ import com.intellij.ui.dsl.builder.panel
|
|||
import com.intellij.ui.jcef.JBCefApp
|
||||
import com.intellij.util.ui.JBUI
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle
|
||||
import ee.carlrobert.codegpt.events.Details
|
||||
import ee.carlrobert.codegpt.events.WebSearchEventDetails
|
||||
import org.cef.browser.CefRendering
|
||||
import java.awt.*
|
||||
import java.awt.event.MouseAdapter
|
||||
import java.awt.event.MouseEvent
|
||||
import javax.swing.*
|
||||
|
||||
class WebpageList(model: DefaultListModel<Details>) : JBList<Details>(model) {
|
||||
class WebpageList(model: DefaultListModel<WebSearchEventDetails>) :
|
||||
JBList<WebSearchEventDetails>(model) {
|
||||
|
||||
init {
|
||||
setModel(model)
|
||||
|
|
@ -135,7 +136,7 @@ class WebpageListCellRenderer : DefaultListCellRenderer() {
|
|||
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus).apply {
|
||||
setOpaque(false)
|
||||
}.let { component ->
|
||||
if (component is JLabel && value is Details) {
|
||||
if (component is JLabel && value is WebSearchEventDetails) {
|
||||
component.apply {
|
||||
icon = AllIcons.General.Web
|
||||
iconTextGap = 4
|
||||
|
|
|
|||
3
src/main/resources/icons/greenCheckmark.svg
Normal file
3
src/main/resources/icons/greenCheckmark.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.5 8.25L6 11.75L13.5 4.25" stroke="#369650" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 202 B |
3
src/main/resources/icons/greenCheckmark_dark.svg
Normal file
3
src/main/resources/icons/greenCheckmark_dark.svg
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M2.5 8.25L6 11.75L13.5 4.25" stroke="#57965C" stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 202 B |
|
|
@ -248,7 +248,8 @@ smartTextPane.submitButton.title=Send Message
|
|||
smartTextPane.submitButton.description=Send message
|
||||
smartTextPane.stopButton.title=Stop
|
||||
smartTextPane.stopButton.description=Stop completion
|
||||
chatMessageResponseBody.webPagesTitle=WEB PAGES
|
||||
chatMessageResponseBody.webPages.title=WEB PAGES
|
||||
chatMessageResponseBody.webDocs.startProgress.label=Analyzing web content...
|
||||
addDocumentation.popup.title=Add Documentation
|
||||
addDocumentation.popup.form.name.label=Name:
|
||||
addDocumentation.popup.form.url.label=URL:
|
||||
|
|
@ -262,4 +263,4 @@ suggestionGroupItem.docs.displayName=Docs
|
|||
suggestionActionItem.webSearch.displayName=Web
|
||||
suggestionActionItem.viewDocumentations.displayName=View all docs
|
||||
suggestionActionItem.createPersona.displayName=Create new persona
|
||||
suggestionActionItem.createDocumentation.displayName=Create new documentation
|
||||
suggestionActionItem.createDocumentation.displayName=Create new documentation
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue