mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-11 04:50:31 +00:00
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:
parent
070b711e87
commit
032f2942e0
17 changed files with 290 additions and 77 deletions
|
|
@ -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"
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
20
src/main/kotlin/ee/carlrobert/codegpt/events/CodeGPTEvent.kt
Normal file
20
src/main/kotlin/ee/carlrobert/codegpt/events/CodeGPTEvent.kt
Normal 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
|
||||
)
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 = ""
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue