Skip to content

Commit 05f146c

Browse files
authored
feat: web search support (#641)
* feat: web search support * fix: enable web search only for codegpt provider * fix: checkstyle * feat: improve list cell design
1 parent 1f28bc6 commit 05f146c

File tree

17 files changed

+290
-77
lines changed

17 files changed

+290
-77
lines changed

gradle/libs.versions.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jsoup = "1.17.2"
1212
jtokkit = "1.0.0"
1313
junit = "5.10.2"
1414
kotlin = "2.0.0"
15-
llm-client = "0.8.10"
15+
llm-client = "0.8.11"
1616
okio = "3.9.0"
1717
tree-sitter = "0.22.6a"
1818

src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestHandler.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package ee.carlrobert.codegpt.completions;
22

3+
import com.fasterxml.jackson.core.JsonProcessingException;
4+
import com.fasterxml.jackson.databind.ObjectMapper;
5+
import ee.carlrobert.codegpt.events.CodeGPTEvent;
36
import ee.carlrobert.codegpt.settings.GeneralSettings;
47
import ee.carlrobert.codegpt.settings.GeneralSettingsState;
58
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
69
import ee.carlrobert.llm.client.openai.completion.ErrorDetails;
7-
import ee.carlrobert.llm.client.you.completion.YouCompletionEventListener;
8-
import ee.carlrobert.llm.client.you.completion.YouSerpResult;
910
import ee.carlrobert.llm.completion.CompletionEventListener;
1011
import java.util.List;
1112
import javax.swing.SwingWorker;
@@ -67,7 +68,7 @@ public CompletionRequestWorker(CallParameters callParameters) {
6768
protected Void doInBackground() {
6869
var settings = GeneralSettings.getCurrentState();
6970
try {
70-
eventSource = startCall(callParameters, new YouRequestCompletionEventListener());
71+
eventSource = startCall(callParameters, new RequestCompletionEventListener());
7172
} catch (TotalUsageExceededException e) {
7273
completionResponseEventListener.handleTokensExceeded(
7374
callParameters.getConversation(),
@@ -86,11 +87,16 @@ protected void process(List<String> chunks) {
8687
}
8788
}
8889

89-
class YouRequestCompletionEventListener implements YouCompletionEventListener {
90+
class RequestCompletionEventListener implements CompletionEventListener<String> {
9091

9192
@Override
92-
public void onSerpResults(List<YouSerpResult> results) {
93-
completionResponseEventListener.handleSerpResults(results, callParameters.getMessage());
93+
public void onEvent(String data) {
94+
try {
95+
var event = new ObjectMapper().readValue(data, CodeGPTEvent.class);
96+
completionResponseEventListener.handleCodeGPTEvent(event);
97+
} catch (JsonProcessingException e) {
98+
// ignore
99+
}
94100
}
95101

96102
@Override

src/main/java/ee/carlrobert/codegpt/completions/CompletionRequestProvider.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,11 +217,17 @@ public OpenAIChatCompletionRequest buildOpenAIChatCompletionRequest(
217217
@Nullable String model,
218218
CallParameters callParameters) {
219219
var configuration = ConfigurationSettings.getCurrentState();
220-
return new OpenAIChatCompletionRequest.Builder(buildOpenAIMessages(model, callParameters))
220+
var requestBuilder = new OpenAIChatCompletionRequest.Builder(
221+
buildOpenAIMessages(model, callParameters))
221222
.setModel(model)
222223
.setMaxTokens(configuration.getMaxTokens())
223224
.setStream(true)
224-
.setTemperature(configuration.getTemperature()).build();
225+
.setTemperature(configuration.getTemperature());
226+
if (callParameters.getMessage().isWebSearchIncluded()) {
227+
// tri-state boolean
228+
requestBuilder.setWebSearchIncluded(true);
229+
}
230+
return requestBuilder.build();
225231
}
226232

227233
public GoogleCompletionRequest buildGoogleChatCompletionRequest(

src/main/java/ee/carlrobert/codegpt/completions/CompletionResponseEventListener.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@
22

33
import ee.carlrobert.codegpt.conversations.Conversation;
44
import ee.carlrobert.codegpt.conversations.message.Message;
5+
import ee.carlrobert.codegpt.events.CodeGPTEvent;
56
import ee.carlrobert.llm.client.openai.completion.ErrorDetails;
6-
import ee.carlrobert.llm.client.you.completion.YouSerpResult;
7-
import java.util.List;
87

98
public interface CompletionResponseEventListener {
109

@@ -20,6 +19,6 @@ default void handleTokensExceeded(Conversation conversation, Message message) {
2019
default void handleCompleted(String fullMessage, CallParameters callParameters) {
2120
}
2221

23-
default void handleSerpResults(List<YouSerpResult> results, Message message) {
22+
default void handleCodeGPTEvent(CodeGPTEvent event) {
2423
}
2524
}

src/main/java/ee/carlrobert/codegpt/conversations/message/Message.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ public class Message {
1717
private List<YouSerpResult> serpResults;
1818
private List<String> referencedFilePaths;
1919
private @Nullable String imageFilePath;
20+
private boolean webSearchIncluded;
2021

2122
public Message(String prompt, String response) {
2223
this(prompt);
@@ -81,6 +82,14 @@ public void setImageFilePath(@Nullable String imageFilePath) {
8182
this.imageFilePath = imageFilePath;
8283
}
8384

85+
public boolean isWebSearchIncluded() {
86+
return webSearchIncluded;
87+
}
88+
89+
public void setWebSearchIncluded(boolean webSearchIncluded) {
90+
this.webSearchIncluded = webSearchIncluded;
91+
}
92+
8493
@Override
8594
public boolean equals(Object obj) {
8695
if (obj == this) {

src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ChatToolWindowTabPanel.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,8 @@ private ResponsePanel createResponsePanel(Message message, ConversationType conv
173173
return new ResponsePanel()
174174
.withReloadAction(() -> reloadMessage(message, conversation, conversationType))
175175
.withDeleteAction(() -> removeMessage(message.getId(), conversation))
176-
.addContent(new ChatMessageResponseBody(project, true, this));
176+
.addContent(
177+
new ChatMessageResponseBody(project, true, false, message.isWebSearchIncluded(), this));
177178
}
178179

179180
private void reloadMessage(
@@ -244,7 +245,7 @@ public void handleTokensExceededPolicyAccepted() {
244245
requestHandler.call(callParameters);
245246
}
246247

247-
private Unit handleSubmit(String text) {
248+
private Unit handleSubmit(String text, boolean webSearchIncluded) {
248249
var message = new Message(text);
249250
var editor = EditorUtil.getSelectedEditor(project);
250251
if (editor != null) {
@@ -257,6 +258,7 @@ private Unit handleSubmit(String text) {
257258
}
258259
}
259260
message.setUserMessage(text);
261+
message.setWebSearchIncluded(webSearchIncluded);
260262
sendMessage(message, ConversationType.DEFAULT);
261263
return Unit.INSTANCE;
262264
}

src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ToolWindowCompletionResponseEventListener.java

Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,14 @@
1010
import ee.carlrobert.codegpt.conversations.Conversation;
1111
import ee.carlrobert.codegpt.conversations.ConversationService;
1212
import ee.carlrobert.codegpt.conversations.message.Message;
13+
import ee.carlrobert.codegpt.events.CodeGPTEvent;
1314
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
1415
import ee.carlrobert.codegpt.toolwindow.chat.ui.ChatMessageResponseBody;
1516
import ee.carlrobert.codegpt.toolwindow.chat.ui.ResponsePanel;
1617
import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.TotalTokensPanel;
1718
import ee.carlrobert.codegpt.ui.OverlayUtil;
1819
import ee.carlrobert.codegpt.ui.textarea.UserInputPanel;
1920
import ee.carlrobert.llm.client.openai.completion.ErrorDetails;
20-
import ee.carlrobert.llm.client.you.completion.YouSerpResult;
21-
import java.util.HashMap;
22-
import java.util.List;
23-
import java.util.Map;
24-
import java.util.UUID;
2521
import javax.swing.SwingUtilities;
2622

2723
abstract class ToolWindowCompletionResponseEventListener implements
@@ -31,7 +27,6 @@ abstract class ToolWindowCompletionResponseEventListener implements
3127
ToolWindowCompletionResponseEventListener.class);
3228

3329
private final StringBuilder messageBuilder = new StringBuilder();
34-
private final Map<UUID, List<YouSerpResult>> serpResultsMapping = new HashMap<>();
3530
private final EncodingManager encodingManager;
3631
private final ConversationService conversationService;
3732
private final ResponsePanel responsePanel;
@@ -113,20 +108,11 @@ public void handleTokensExceeded(Conversation conversation, Message message) {
113108

114109
@Override
115110
public void handleCompleted(String fullMessage, CallParameters callParameters) {
116-
var message = callParameters.getMessage();
117111
conversationService.saveMessage(fullMessage, callParameters);
118112

119-
var serpResults = serpResultsMapping.get(message.getId());
120-
var containsResults = serpResults != null && !serpResults.isEmpty();
121-
if (containsResults) {
122-
message.setSerpResults(serpResults);
123-
}
124113
SwingUtilities.invokeLater(() -> {
125114
try {
126115
responsePanel.enableActions();
127-
if (containsResults) {
128-
responseContainer.displaySerpResults(serpResults);
129-
}
130116
totalTokensPanel.updateUserPromptTokens(textArea.getText());
131117
totalTokensPanel.updateConversationTokens(callParameters.getConversation());
132118
} finally {
@@ -136,8 +122,8 @@ public void handleCompleted(String fullMessage, CallParameters callParameters) {
136122
}
137123

138124
@Override
139-
public void handleSerpResults(List<YouSerpResult> results, Message message) {
140-
serpResultsMapping.put(message.getId(), results);
125+
public void handleCodeGPTEvent(CodeGPTEvent event) {
126+
responseContainer.displayWebSearchItem(event.getEvent().getDetails());
141127
}
142128

143129
private void stopStreaming(ChatMessageResponseBody responseContainer) {

src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/ChatMessageResponseBody.java

Lines changed: 24 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,23 +12,25 @@
1212
import com.intellij.openapi.util.io.FileUtil;
1313
import com.intellij.openapi.vfs.LocalFileSystem;
1414
import com.intellij.openapi.vfs.VirtualFile;
15+
import com.intellij.ui.components.JBLabel;
1516
import com.intellij.util.ui.JBUI;
1617
import com.vladsch.flexmark.ast.FencedCodeBlock;
1718
import com.vladsch.flexmark.parser.Parser;
19+
import ee.carlrobert.codegpt.CodeGPTBundle;
1820
import ee.carlrobert.codegpt.actions.ActionType;
21+
import ee.carlrobert.codegpt.events.Details;
1922
import ee.carlrobert.codegpt.settings.GeneralSettingsConfigurable;
2023
import ee.carlrobert.codegpt.telemetry.TelemetryAction;
2124
import ee.carlrobert.codegpt.toolwindow.chat.StreamParser;
2225
import ee.carlrobert.codegpt.toolwindow.chat.editor.ResponseEditorPanel;
26+
import ee.carlrobert.codegpt.toolwindow.ui.WebpageList;
2327
import ee.carlrobert.codegpt.ui.UIUtil;
2428
import ee.carlrobert.codegpt.util.EditorUtil;
2529
import ee.carlrobert.codegpt.util.MarkdownUtil;
26-
import ee.carlrobert.llm.client.you.completion.YouSerpResult;
2730
import java.awt.BorderLayout;
28-
import java.util.List;
2931
import java.util.Objects;
30-
import java.util.stream.Collectors;
3132
import javax.swing.BoxLayout;
33+
import javax.swing.DefaultListModel;
3234
import javax.swing.JPanel;
3335
import javax.swing.JTextPane;
3436

@@ -38,6 +40,7 @@ public class ChatMessageResponseBody extends JPanel {
3840
private final Disposable parentDisposable;
3941
private final StreamParser streamParser;
4042
private final boolean readOnly;
43+
private final DefaultListModel<Details> webpageListModel = new DefaultListModel<>();
4144
private ResponseEditorPanel currentlyProcessedEditorPanel;
4245
private JTextPane currentlyProcessedTextPane;
4346
private boolean responseReceived;
@@ -50,13 +53,14 @@ public ChatMessageResponseBody(
5053
Project project,
5154
boolean withGhostText,
5255
Disposable parentDisposable) {
53-
this(project, withGhostText, false, parentDisposable);
56+
this(project, withGhostText, false, false, parentDisposable);
5457
}
5558

5659
public ChatMessageResponseBody(
5760
Project project,
5861
boolean withGhostText,
5962
boolean readOnly,
63+
boolean webSearchIncluded,
6064
Disposable parentDisposable) {
6165
super(new BorderLayout());
6266
this.project = project;
@@ -66,6 +70,18 @@ public ChatMessageResponseBody(
6670
setLayout(new BoxLayout(this, BoxLayout.PAGE_AXIS));
6771
setOpaque(false);
6872

73+
if (webSearchIncluded) {
74+
var title = new JPanel(new BorderLayout());
75+
title.setOpaque(false);
76+
title.setBorder(JBUI.Borders.empty(8, 0));
77+
title.add(new JBLabel(CodeGPTBundle.get("chatMessageResponseBody.webPagesTitle"))
78+
.withFont(JBUI.Fonts.miniFont()), BorderLayout.LINE_START);
79+
add(title);
80+
81+
var listPanel = new JPanel(new BorderLayout());
82+
listPanel.add(new WebpageList(webpageListModel), BorderLayout.LINE_START);
83+
add(listPanel);
84+
}
6985
if (withGhostText) {
7086
prepareProcessingText(!readOnly);
7187
currentlyProcessedTextPane.setText(
@@ -136,18 +152,6 @@ public void displayError(String message) {
136152
}
137153
}
138154

139-
public void displaySerpResults(List<YouSerpResult> serpResults) {
140-
var html = getSearchResultsHtml(serpResults);
141-
if (responseReceived) {
142-
add(createTextPane(html, false));
143-
} else {
144-
if (currentlyProcessedTextPane == null) {
145-
prepareProcessingText(false);
146-
}
147-
currentlyProcessedTextPane.setText(html);
148-
}
149-
}
150-
151155
public void clear() {
152156
removeAll();
153157

@@ -161,21 +165,6 @@ public void clear() {
161165
revalidate();
162166
}
163167

164-
private String getSearchResultsHtml(List<YouSerpResult> serpResults) {
165-
var titles = serpResults.stream()
166-
.map(result -> format(
167-
"<li style=\"margin-bottom: 4px;\"><a href=\"%s\">%s</a></li>",
168-
result.getUrl(),
169-
result.getName()))
170-
.collect(Collectors.joining());
171-
return format(
172-
"<html>"
173-
+ "<p><strong>Search results:</strong></p>"
174-
+ "<ol>%s</ol>"
175-
+ "</html>",
176-
titles);
177-
}
178-
179168
private void processResponse(String markdownInput, boolean codeResponse, boolean caretVisible) {
180169
responseReceived = true;
181170

@@ -239,4 +228,8 @@ private JTextPane createTextPane(String text, boolean caretVisible) {
239228
textPane.setBorder(JBUI.Borders.empty());
240229
return textPane;
241230
}
231+
232+
public void displayWebSearchItem(Details details) {
233+
webpageListModel.addElement(details);
234+
}
242235
}

src/main/java/ee/carlrobert/codegpt/toolwindow/chat/ui/UserMessagePanel.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,13 @@ private ChatMessageResponseBody createResponseBody(
5959
Project project,
6060
String prompt,
6161
Disposable parentDisposable) {
62-
return new ChatMessageResponseBody(project, false, true, parentDisposable).withResponse(prompt);
62+
return new ChatMessageResponseBody(
63+
project,
64+
false,
65+
true,
66+
false,
67+
parentDisposable)
68+
.withResponse(prompt);
6369
}
6470

6571
private JBLabel createDisplayNameLabel() {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package ee.carlrobert.codegpt.events
2+
3+
import com.fasterxml.jackson.annotation.JsonCreator
4+
import com.fasterxml.jackson.annotation.JsonProperty
5+
6+
data class CodeGPTEvent @JsonCreator constructor(
7+
@JsonProperty("event") val event: Event
8+
)
9+
10+
data class Event @JsonCreator constructor(
11+
@JsonProperty("details") val details: Details,
12+
@JsonProperty("type") val type: String
13+
)
14+
15+
data class Details @JsonCreator constructor(
16+
@JsonProperty("id") val id: String,
17+
@JsonProperty("name") val name: String,
18+
@JsonProperty("url") val url: String,
19+
@JsonProperty("displayUrl") val displayUrl: String
20+
)

0 commit comments

Comments
 (0)