diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6dbb4e2a..43e166f7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java index a976b908..b73e9eb1 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java @@ -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 { @Override - public void onSerpResults(List 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 diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java index 8e97e693..034b2037 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java @@ -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( diff --git a/src/main/java/ee/carlrobert/codegpt/completions/CompletionResponseEventListener.java b/src/main/java/ee/carlrobert/codegpt/completions/CompletionResponseEventListener.java index 87c94c06..367af192 100644 --- a/src/main/java/ee/carlrobert/codegpt/completions/CompletionResponseEventListener.java +++ b/src/main/java/ee/carlrobert/codegpt/completions/CompletionResponseEventListener.java @@ -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 results, Message message) { + default void handleCodeGPTEvent(CodeGPTEvent event) { } } diff --git a/src/main/java/ee/carlrobert/codegpt/conversations/message/Message.java b/src/main/java/ee/carlrobert/codegpt/conversations/message/Message.java index e2f610d2..d22ed2cb 100644 --- a/src/main/java/ee/carlrobert/codegpt/conversations/message/Message.java +++ b/src/main/java/ee/carlrobert/codegpt/conversations/message/Message.java @@ -17,6 +17,7 @@ public class Message { private List serpResults; private List 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) { diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java index 7e711562..f9f2a5a8 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java @@ -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; } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ToolWindowCompletionResponseEventListener.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ToolWindowCompletionResponseEventListener.java index 66448965..e909da3d 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ToolWindowCompletionResponseEventListener.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ToolWindowCompletionResponseEventListener.java @@ -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> 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 results, Message message) { - serpResultsMapping.put(message.getId(), results); + public void handleCodeGPTEvent(CodeGPTEvent event) { + responseContainer.displayWebSearchItem(event.getEvent().getDetails()); } private void stopStreaming(ChatMessageResponseBody responseContainer) { diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java index edad0bc5..5510ff52 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java @@ -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
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 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 serpResults) { - var titles = serpResults.stream() - .map(result -> format( - "
  • %s
  • ", - result.getUrl(), - result.getName())) - .collect(Collectors.joining()); - return format( - "" - + "

    Search results:

    " - + "
      %s
    " - + "", - 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); + } } diff --git a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/UserMessagePanel.java b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/UserMessagePanel.java index 5db6de56..a65161a4 100644 --- a/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/UserMessagePanel.java +++ b/src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/UserMessagePanel.java @@ -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() { diff --git a/src/main/kotlin/ee/carlrobert/codegpt/events/CodeGPTEvent.kt b/src/main/kotlin/ee/carlrobert/codegpt/events/CodeGPTEvent.kt new file mode 100644 index 00000000..0a3a6820 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/events/CodeGPTEvent.kt @@ -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 +) diff --git a/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/WebpageList.kt b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/WebpageList.kt new file mode 100644 index 00000000..a7f66af0 --- /dev/null +++ b/src/main/kotlin/ee/carlrobert/codegpt/toolwindow/ui/WebpageList.kt @@ -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
    ) : JBList
    (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() + .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 + } + } +} diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/CustomTextPaneKeyAdapter.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/CustomTextPaneKeyAdapter.kt index 8430c235..86896e09 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/CustomTextPaneKeyAdapter.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/CustomTextPaneKeyAdapter.kt @@ -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 = AtomicReference(null) override fun keyReleased(e: KeyEvent) { diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionList.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionList.kt index de614e7b..e8c18dde 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionList.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionList.kt @@ -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() diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionListCellRenderer.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionListCellRenderer.kt index 1f55c478..728a8a55 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionListCellRenderer.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionListCellRenderer.kt @@ -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, diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionsPopupManager.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionsPopupManager.kt index c76c5ce1..824369e8 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionsPopupManager.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/SuggestionsPopupManager.kt @@ -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) { diff --git a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/UserInputPanel.kt b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/UserInputPanel.kt index 59cb6ba1..c718d33b 100644 --- a/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/UserInputPanel.kt +++ b/src/main/kotlin/ee/carlrobert/codegpt/ui/textarea/UserInputPanel.kt @@ -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 = "" } } diff --git a/src/main/resources/messages/codegpt.properties b/src/main/resources/messages/codegpt.properties index b8761b41..9bb2c652 100644 --- a/src/main/resources/messages/codegpt.properties +++ b/src/main/resources/messages/codegpt.properties @@ -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 \ No newline at end of file +smartTextPane.stopButton.description=Stop completion +chatMessageResponseBody.webPagesTitle=WEB PAGES \ No newline at end of file