feat: web search support (#641)

* feat: web search support

* fix: enable web search only for codegpt provider

* fix: checkstyle

* feat: improve list cell design
This commit is contained in:
Carl-Robert 2024-07-30 15:53:45 +03:00 committed by Carl-Robert Linnupuu
parent 070b711e87
commit 032f2942e0
17 changed files with 290 additions and 77 deletions

View file

@ -12,7 +12,7 @@ jsoup = "1.17.2"
jtokkit = "1.0.0"
junit = "5.10.2"
kotlin = "2.0.0"
llm-client = "0.8.10"
llm-client = "0.8.11"
okio = "3.9.0"
tree-sitter = "0.22.6a"

View file

@ -1,11 +1,12 @@
package ee.carlrobert.codegpt.completions;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import ee.carlrobert.codegpt.events.CodeGPTEvent;
import ee.carlrobert.codegpt.settings.GeneralSettings;
import ee.carlrobert.codegpt.settings.GeneralSettingsState;
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
import ee.carlrobert.llm.client.openai.completion.ErrorDetails;
import ee.carlrobert.llm.client.you.completion.YouCompletionEventListener;
import ee.carlrobert.llm.client.you.completion.YouSerpResult;
import ee.carlrobert.llm.completion.CompletionEventListener;
import java.util.List;
import javax.swing.SwingWorker;
@ -67,7 +68,7 @@ public class CompletionRequestHandler {
protected Void doInBackground() {
var settings = GeneralSettings.getCurrentState();
try {
eventSource = startCall(callParameters, new YouRequestCompletionEventListener());
eventSource = startCall(callParameters, new RequestCompletionEventListener());
} catch (TotalUsageExceededException e) {
completionResponseEventListener.handleTokensExceeded(
callParameters.getConversation(),
@ -86,11 +87,16 @@ public class CompletionRequestHandler {
}
}
class YouRequestCompletionEventListener implements YouCompletionEventListener {
class RequestCompletionEventListener implements CompletionEventListener<String> {
@Override
public void onSerpResults(List<YouSerpResult> results) {
completionResponseEventListener.handleSerpResults(results, callParameters.getMessage());
public void onEvent(String data) {
try {
var event = new ObjectMapper().readValue(data, CodeGPTEvent.class);
completionResponseEventListener.handleCodeGPTEvent(event);
} catch (JsonProcessingException e) {
// ignore
}
}
@Override

View file

@ -217,11 +217,17 @@ public class CompletionRequestProvider {
@Nullable String model,
CallParameters callParameters) {
var configuration = ConfigurationSettings.getCurrentState();
return new OpenAIChatCompletionRequest.Builder(buildOpenAIMessages(model, callParameters))
var requestBuilder = new OpenAIChatCompletionRequest.Builder(
buildOpenAIMessages(model, callParameters))
.setModel(model)
.setMaxTokens(configuration.getMaxTokens())
.setStream(true)
.setTemperature(configuration.getTemperature()).build();
.setTemperature(configuration.getTemperature());
if (callParameters.getMessage().isWebSearchIncluded()) {
// tri-state boolean
requestBuilder.setWebSearchIncluded(true);
}
return requestBuilder.build();
}
public GoogleCompletionRequest buildGoogleChatCompletionRequest(

View file

@ -2,9 +2,8 @@ package ee.carlrobert.codegpt.completions;
import ee.carlrobert.codegpt.conversations.Conversation;
import ee.carlrobert.codegpt.conversations.message.Message;
import ee.carlrobert.codegpt.events.CodeGPTEvent;
import ee.carlrobert.llm.client.openai.completion.ErrorDetails;
import ee.carlrobert.llm.client.you.completion.YouSerpResult;
import java.util.List;
public interface CompletionResponseEventListener {
@ -20,6 +19,6 @@ public interface CompletionResponseEventListener {
default void handleCompleted(String fullMessage, CallParameters callParameters) {
}
default void handleSerpResults(List<YouSerpResult> results, Message message) {
default void handleCodeGPTEvent(CodeGPTEvent event) {
}
}

View file

@ -17,6 +17,7 @@ public class Message {
private List<YouSerpResult> serpResults;
private List<String> referencedFilePaths;
private @Nullable String imageFilePath;
private boolean webSearchIncluded;
public Message(String prompt, String response) {
this(prompt);
@ -81,6 +82,14 @@ public class Message {
this.imageFilePath = imageFilePath;
}
public boolean isWebSearchIncluded() {
return webSearchIncluded;
}
public void setWebSearchIncluded(boolean webSearchIncluded) {
this.webSearchIncluded = webSearchIncluded;
}
@Override
public boolean equals(Object obj) {
if (obj == this) {

View file

@ -173,7 +173,8 @@ public class ChatToolWindowTabPanel implements Disposable {
return new ResponsePanel()
.withReloadAction(() -> reloadMessage(message, conversation, conversationType))
.withDeleteAction(() -> removeMessage(message.getId(), conversation))
.addContent(new ChatMessageResponseBody(project, true, this));
.addContent(
new ChatMessageResponseBody(project, true, false, message.isWebSearchIncluded(), this));
}
private void reloadMessage(
@ -244,7 +245,7 @@ public class ChatToolWindowTabPanel implements Disposable {
requestHandler.call(callParameters);
}
private Unit handleSubmit(String text) {
private Unit handleSubmit(String text, boolean webSearchIncluded) {
var message = new Message(text);
var editor = EditorUtil.getSelectedEditor(project);
if (editor != null) {
@ -257,6 +258,7 @@ public class ChatToolWindowTabPanel implements Disposable {
}
}
message.setUserMessage(text);
message.setWebSearchIncluded(webSearchIncluded);
sendMessage(message, ConversationType.DEFAULT);
return Unit.INSTANCE;
}

View file

@ -10,6 +10,7 @@ import ee.carlrobert.codegpt.completions.CompletionResponseEventListener;
import ee.carlrobert.codegpt.conversations.Conversation;
import ee.carlrobert.codegpt.conversations.ConversationService;
import ee.carlrobert.codegpt.conversations.message.Message;
import ee.carlrobert.codegpt.events.CodeGPTEvent;
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
import ee.carlrobert.codegpt.toolwindow.chat.ui.ChatMessageResponseBody;
import ee.carlrobert.codegpt.toolwindow.chat.ui.ResponsePanel;
@ -17,11 +18,6 @@ import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.TotalTokensPanel;
import ee.carlrobert.codegpt.ui.OverlayUtil;
import ee.carlrobert.codegpt.ui.textarea.UserInputPanel;
import ee.carlrobert.llm.client.openai.completion.ErrorDetails;
import ee.carlrobert.llm.client.you.completion.YouSerpResult;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import javax.swing.SwingUtilities;
abstract class ToolWindowCompletionResponseEventListener implements
@ -31,7 +27,6 @@ abstract class ToolWindowCompletionResponseEventListener implements
ToolWindowCompletionResponseEventListener.class);
private final StringBuilder messageBuilder = new StringBuilder();
private final Map<UUID, List<YouSerpResult>> serpResultsMapping = new HashMap<>();
private final EncodingManager encodingManager;
private final ConversationService conversationService;
private final ResponsePanel responsePanel;
@ -113,20 +108,11 @@ abstract class ToolWindowCompletionResponseEventListener implements
@Override
public void handleCompleted(String fullMessage, CallParameters callParameters) {
var message = callParameters.getMessage();
conversationService.saveMessage(fullMessage, callParameters);
var serpResults = serpResultsMapping.get(message.getId());
var containsResults = serpResults != null && !serpResults.isEmpty();
if (containsResults) {
message.setSerpResults(serpResults);
}
SwingUtilities.invokeLater(() -> {
try {
responsePanel.enableActions();
if (containsResults) {
responseContainer.displaySerpResults(serpResults);
}
totalTokensPanel.updateUserPromptTokens(textArea.getText());
totalTokensPanel.updateConversationTokens(callParameters.getConversation());
} finally {
@ -136,8 +122,8 @@ abstract class ToolWindowCompletionResponseEventListener implements
}
@Override
public void handleSerpResults(List<YouSerpResult> results, Message message) {
serpResultsMapping.put(message.getId(), results);
public void handleCodeGPTEvent(CodeGPTEvent event) {
responseContainer.displayWebSearchItem(event.getEvent().getDetails());
}
private void stopStreaming(ChatMessageResponseBody responseContainer) {

View file

@ -12,23 +12,25 @@ import com.intellij.openapi.project.Project;
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.JBUI;
import com.vladsch.flexmark.ast.FencedCodeBlock;
import com.vladsch.flexmark.parser.Parser;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.actions.ActionType;
import ee.carlrobert.codegpt.events.Details;
import ee.carlrobert.codegpt.settings.GeneralSettingsConfigurable;
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
import ee.carlrobert.codegpt.toolwindow.chat.StreamParser;
import ee.carlrobert.codegpt.toolwindow.chat.editor.ResponseEditorPanel;
import ee.carlrobert.codegpt.toolwindow.ui.WebpageList;
import ee.carlrobert.codegpt.ui.UIUtil;
import ee.carlrobert.codegpt.util.EditorUtil;
import ee.carlrobert.codegpt.util.MarkdownUtil;
import ee.carlrobert.llm.client.you.completion.YouSerpResult;
import java.awt.BorderLayout;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import javax.swing.BoxLayout;
import javax.swing.DefaultListModel;
import javax.swing.JPanel;
import javax.swing.JTextPane;
@ -38,6 +40,7 @@ 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 ResponseEditorPanel currentlyProcessedEditorPanel;
private JTextPane currentlyProcessedTextPane;
private boolean responseReceived;
@ -50,13 +53,14 @@ public class ChatMessageResponseBody extends JPanel {
Project project,
boolean withGhostText,
Disposable parentDisposable) {
this(project, withGhostText, false, parentDisposable);
this(project, withGhostText, false, false, parentDisposable);
}
public ChatMessageResponseBody(
Project project,
boolean withGhostText,
boolean readOnly,
boolean webSearchIncluded,
Disposable parentDisposable) {
super(new BorderLayout());
this.project = project;
@ -66,6 +70,18 @@ public class ChatMessageResponseBody extends JPanel {
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
setOpaque(false);
if (webSearchIncluded) {
var title = new JPanel(new BorderLayout());
title.setOpaque(false);
title.setBorder(JBUI.Borders.empty(8, 0));
title.add(new JBLabel(CodeGPTBundle.get("chatMessageResponseBody.webPagesTitle"))
.withFont(JBUI.Fonts.miniFont()), BorderLayout.LINE_START);
add(title);
var listPanel = new JPanel(new BorderLayout());
listPanel.add(new WebpageList(webpageListModel), BorderLayout.LINE_START);
add(listPanel);
}
if (withGhostText) {
prepareProcessingText(!readOnly);
currentlyProcessedTextPane.setText(
@ -136,18 +152,6 @@ public class ChatMessageResponseBody extends JPanel {
}
}
public void displaySerpResults(List<YouSerpResult> serpResults) {
var html = getSearchResultsHtml(serpResults);
if (responseReceived) {
add(createTextPane(html, false));
} else {
if (currentlyProcessedTextPane == null) {
prepareProcessingText(false);
}
currentlyProcessedTextPane.setText(html);
}
}
public void clear() {
removeAll();
@ -161,21 +165,6 @@ public class ChatMessageResponseBody extends JPanel {
revalidate();
}
private String getSearchResultsHtml(List<YouSerpResult> serpResults) {
var titles = serpResults.stream()
.map(result -> format(
"<li style=\"margin-bottom: 4px;\"><a href=\"%s\">%s</a></li>",
result.getUrl(),
result.getName()))
.collect(Collectors.joining());
return format(
"<html>"
+ "<p><strong>Search results:</strong></p>"
+ "<ol>%s</ol>"
+ "</html>",
titles);
}
private void processResponse(String markdownInput, boolean codeResponse, boolean caretVisible) {
responseReceived = true;
@ -239,4 +228,8 @@ public class ChatMessageResponseBody extends JPanel {
textPane.setBorder(JBUI.Borders.empty());
return textPane;
}
public void displayWebSearchItem(Details details) {
webpageListModel.addElement(details);
}
}

View file

@ -59,7 +59,13 @@ public class UserMessagePanel extends JPanel {
Project project,
String prompt,
Disposable parentDisposable) {
return new ChatMessageResponseBody(project, false, true, parentDisposable).withResponse(prompt);
return new ChatMessageResponseBody(
project,
false,
true,
false,
parentDisposable)
.withResponse(prompt);
}
private JBLabel createDisplayNameLabel() {

View file

@ -0,0 +1,20 @@
package ee.carlrobert.codegpt.events
import com.fasterxml.jackson.annotation.JsonCreator
import com.fasterxml.jackson.annotation.JsonProperty
data class CodeGPTEvent @JsonCreator constructor(
@JsonProperty("event") val event: Event
)
data class Event @JsonCreator constructor(
@JsonProperty("details") val details: Details,
@JsonProperty("type") val type: String
)
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
)

View file

@ -0,0 +1,158 @@
package ee.carlrobert.codegpt.toolwindow.ui
import com.intellij.icons.AllIcons
import com.intellij.ide.BrowserUtil
import com.intellij.ide.IdeBundle
import com.intellij.openapi.components.service
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.ui.popup.util.MinimizeButton
import com.intellij.ui.JBColor
import com.intellij.ui.components.ActionLink
import com.intellij.ui.components.JBLabel
import com.intellij.ui.components.JBList
import com.intellij.ui.dsl.builder.AlignX
import com.intellij.ui.dsl.builder.RightGap
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 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) {
init {
setModel(model)
setupUI()
setupMouseListener()
}
override fun getPreferredSize(): Dimension {
val parentWidth = parent?.width ?: super.getPreferredSize().width
return Dimension(parentWidth, super.getPreferredSize().height)
}
private fun setupUI() {
border = JBUI.Borders.emptyBottom(8)
cellRenderer = WebpageListCellRenderer()
setEmptyText("")
}
override fun paint(g: Graphics) {
super.paint(g)
if (model.size == 0) {
g.font = emptyText.component.font
g.color = JBColor.gray
if (g is Graphics2D) {
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON
)
}
g.drawString(
CodeGPTBundle.get("shared.loading"),
insets.left,
insets.top + g.fontMetrics.ascent + 5
)
}
}
private fun setupMouseListener() {
addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
handleMouseClick(e)
}
override fun mouseExited(e: MouseEvent) {
putClientProperty("hoveredIndex", -1)
repaint()
}
})
}
private fun handleMouseClick(e: MouseEvent) {
val index = locationToIndex(e.point)
if (index >= 0) {
val details = model.getElementAt(index)
val browser = JBCefApp.getInstance().createClient()
.cefClient
.createBrowser(details.url, CefRendering.DEFAULT, false, null)
val popupPanel = JPanel(BorderLayout()).apply {
add(browser.uiComponent, BorderLayout.CENTER)
add(panel {
row {
text(CodeGPTBundle.get("shared.escToCancel")).applyToComponent {
font = JBUI.Fonts.smallFont()
}
cell(ActionLink(CodeGPTBundle.get("shared.website")) {
BrowserUtil.open(details.url)
}
.apply {
setExternalLinkIcon()
font = JBUI.Fonts.smallFont()
})
.align(AlignX.RIGHT)
}
}.apply {
border = JBUI.Borders.empty(0, 8)
}, BorderLayout.SOUTH)
preferredSize = Dimension(800, 600)
}
service<JBPopupFactory>()
.createComponentPopupBuilder(popupPanel, null)
.setTitle(details.name)
.setMovable(true)
.setCancelKeyEnabled(true)
.setCancelOnClickOutside(true)
.setCancelOnWindowDeactivation(true)
.setRequestFocus(true)
.setCancelButton(MinimizeButton(IdeBundle.message("tooltip.hide")))
.setMinSize(Dimension(800, 600))
.setResizable(true)
.createPopup()
.showInFocusCenter()
e.consume()
}
}
}
class WebpageListCellRenderer : DefaultListCellRenderer() {
override fun getListCellRendererComponent(
list: JList<*>?,
value: Any?,
index: Int,
isSelected: Boolean,
cellHasFocus: Boolean
): Component =
super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus).apply {
setOpaque(false)
}.let { component ->
if (component is JLabel && value is Details) {
component.apply {
icon = AllIcons.General.Web
iconTextGap = 4
font = JBUI.Fonts.smallFont()
text = value.name
}
panel {
row {
cell(component).gap(RightGap.SMALL)
cell(JBLabel(value.displayUrl)
.withFont(JBUI.Fonts.miniFont())
.apply { foreground = JBColor.gray })
}
}.apply {
isOpaque = false
}
} else {
component
}
}
}

View file

@ -5,6 +5,7 @@ import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.jetbrains.rd.util.AtomicReference
import ee.carlrobert.codegpt.conversations.Conversation
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@ -15,10 +16,11 @@ import javax.swing.text.StyledDocument
class CustomTextPaneKeyAdapter(
private val project: Project,
private val textPane: CustomTextPane
private val textPane: CustomTextPane,
onWebSearchIncluded: () -> Unit
) : KeyAdapter() {
private val suggestionsPopupManager = SuggestionsPopupManager(project, textPane)
private val suggestionsPopupManager = SuggestionsPopupManager(project, textPane, onWebSearchIncluded)
private val popupOpenedAtRange: AtomicReference<TextRange?> = AtomicReference(null)
override fun keyReleased(e: KeyEvent) {

View file

@ -58,7 +58,7 @@ class SuggestionList(
private fun handleEnterKey() {
val item = model.getElementAt(selectedIndex)
if (item is SuggestionItem.ActionItem && item.action.enabled || item !is SuggestionItem.ActionItem) {
if (item is SuggestionItem.ActionItem && item.action.enabled() || item !is SuggestionItem.ActionItem) {
onSelected(item)
}
}
@ -80,7 +80,7 @@ class SuggestionList(
val index = locationToIndex(e.point)
if (index >= 0) {
val item = model.getElementAt(index)
if (item is SuggestionItem.ActionItem && item.action.enabled || item !is SuggestionItem.ActionItem) {
if (item is SuggestionItem.ActionItem && item.action.enabled() || item !is SuggestionItem.ActionItem) {
onSelected(item)
}
e.consume()

View file

@ -10,7 +10,9 @@ import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.dsl.gridLayout.UnscaledGaps
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.JBUI.CurrentTheme.GotItTooltip
import ee.carlrobert.codegpt.settings.GeneralSettings
import ee.carlrobert.codegpt.settings.persona.PersonaSettings
import ee.carlrobert.codegpt.settings.service.ServiceType
import java.awt.Component
import java.awt.Dimension
import javax.swing.*
@ -78,7 +80,7 @@ class SuggestionListCellRenderer(
return createDefaultPanel(
component.apply {
disabledIcon = item.action.icon
isEnabled = item.action.enabled
isEnabled = item.action.enabled()
},
item.action.icon,
item.action.displayName,

View file

@ -17,6 +17,8 @@ import com.intellij.ui.dsl.gridLayout.UnscaledGaps
import com.intellij.util.ui.JBUI
import com.intellij.vcsUtil.showAbove
import ee.carlrobert.codegpt.CodeGPTBundle
import ee.carlrobert.codegpt.settings.GeneralSettings
import ee.carlrobert.codegpt.settings.service.ServiceType
import ee.carlrobert.codegpt.settings.persona.PersonaDetails
import ee.carlrobert.codegpt.settings.persona.PersonaSettings
import ee.carlrobert.codegpt.settings.persona.PersonasConfigurable
@ -38,13 +40,17 @@ enum class DefaultAction(
val displayName: String,
val code: String,
val icon: Icon,
val enabled: Boolean = true
val enabled: () -> Boolean = { true }
) {
FILES("Files →", "file:", AllIcons.FileTypes.Any_type),
FOLDERS("Folders →", "folder:", AllIcons.Nodes.Folder),
PERSONAS("Personas →", "persona:", AllIcons.General.User),
DOCS("Docs (coming soon) →", "docs:", AllIcons.Toolwindows.Documentation, false),
SEARCH_WEB("Web (coming soon)", "", AllIcons.General.Web, false),
SEARCH_WEB("Web", "web", AllIcons.General.Web, {
GeneralSettings.getSelectedService() == ServiceType.CODEGPT
}),
DOCS("Docs (coming soon) →", "docs:", AllIcons.Toolwindows.Documentation, {
false
}),
CREATE_NEW_PERSONA("Create new persona", "", AllIcons.General.Add),
}
@ -59,13 +65,14 @@ val DEFAULT_ACTIONS = mutableListOf(
SuggestionItem.ActionItem(DefaultAction.FILES),
SuggestionItem.ActionItem(DefaultAction.FOLDERS),
SuggestionItem.ActionItem(DefaultAction.PERSONAS),
SuggestionItem.ActionItem(DefaultAction.DOCS),
SuggestionItem.ActionItem(DefaultAction.SEARCH_WEB),
SuggestionItem.ActionItem(DefaultAction.DOCS),
)
class SuggestionsPopupManager(
private val project: Project,
private val textPane: CustomTextPane,
private val onWebSearchIncluded: () -> Unit
) {
private var currentActionStrategy: SuggestionStrategy = DefaultSuggestionStrategy()
@ -151,6 +158,12 @@ class SuggestionsPopupManager(
)
return
}
if (item.action == DefaultAction.SEARCH_WEB) {
hidePopup()
onWebSearchIncluded()
textPane.appendHighlightedText(item.action.code, withWhitespace = true)
return
}
appliedActions.add(item)
currentActionStrategy = when (item.action) {

View file

@ -26,12 +26,16 @@ import javax.swing.JPanel
class UserInputPanel(
private val project: Project,
private val onSubmit: (String) -> Unit,
private val onSubmit: (String, Boolean) -> Unit,
private val onStop: () -> Unit
) : JPanel(BorderLayout()) {
private val textPane = CustomTextPane { handleSubmit() }
.apply { addKeyListener(CustomTextPaneKeyAdapter(project, this)) }
.apply {
addKeyListener(CustomTextPaneKeyAdapter(project, this) {
webSearchIncluded = true
})
}
private val submitButton = IconActionButton(
object : AnAction(
@ -56,6 +60,7 @@ class UserInputPanel(
}
).apply { isEnabled = false }
private val imageActionSupported = AtomicBooleanProperty(isImageActionSupported())
private var webSearchIncluded: Boolean = false
val text: String
get() = textPane.text
@ -99,9 +104,12 @@ class UserInputPanel(
override fun getInsets(): Insets = JBUI.insets(4)
private fun handleSubmit() {
val text = textPane.text.trim()
val text = textPane.text
// TODO
.replace("@web", "")
.trim()
if (text.isNotEmpty()) {
onSubmit(text)
onSubmit(text, webSearchIncluded)
textPane.text = ""
}
}

View file

@ -221,6 +221,8 @@ shared.configuration=Configuration
shared.port=Port:
shared.discard=Discard
shared.notification.doNotShowAgain=Do not show again
shared.loading=Loading...
shared.website=Website
codeCompletion.progress.title=Code completion in progress
imageAttachmentNotification.content=New image detected on desktop. Would you like to attach it to your current conversation?
imageAttachmentNotification.action=Attach image
@ -244,4 +246,5 @@ editCodePopover.followUpButton.title=Submit Follow-up
smartTextPane.submitButton.title=Send Message
smartTextPane.submitButton.description=Send message
smartTextPane.stopButton.title=Stop
smartTextPane.stopButton.description=Stop completion
smartTextPane.stopButton.description=Stop completion
chatMessageResponseBody.webPagesTitle=WEB PAGES