Feature: Support chatting with multiple files (#306)

* Initial implementation

* Refactor UI related classes and organize imports

* Display selected files notification, include the files in the prompt

* feat: store referenced file paths in the messate state

* feat: add selected files accordion

* feat: update UI

* feat: improve file selection

* feat: support prompt template configuration

* fix: token calculation for virtualfile checkbox tree

* refactor: clean up

* refactor: move labels/descriptions to bundle
This commit is contained in:
Carl-Robert 2023-12-12 22:30:39 +02:00 committed by GitHub
parent 4354000ddb
commit f4be25bdac
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
62 changed files with 1125 additions and 148 deletions

View file

@ -9,12 +9,15 @@ import static org.apache.http.HttpHeaders.AUTHORIZATION;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;
import ee.carlrobert.codegpt.CodeGPTKeys;
import ee.carlrobert.codegpt.EncodingManager;
import ee.carlrobert.codegpt.conversations.ConversationService;
import ee.carlrobert.codegpt.conversations.message.Message;
import ee.carlrobert.codegpt.settings.configuration.ConfigurationState;
import ee.carlrobert.codegpt.toolwindow.chat.standard.StandardChatToolWindowTabPanel;
import ee.carlrobert.embedding.CheckedFile;
import ee.carlrobert.llm.client.http.exchange.StreamHttpExchange;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import testsupport.IntegrationTest;
@ -79,4 +82,88 @@ public class StandardChatToolWindowTabPanelTest extends IntegrationTest {
.extracting("id", "prompt", "response")
.containsExactly(message.getId(), message.getPrompt(), message.getResponse());
}
public void testSendingOpenAIMessageWithReferencedContext() {
getProject().putUserData(CodeGPTKeys.SELECTED_FILES, List.of(
new CheckedFile("TEST_FILE_NAME_1", "TEST_FILE_PATH_1", "TEST_FILE_CONTENT_1"),
new CheckedFile("TEST_FILE_NAME_2", "TEST_FILE_PATH_2", "TEST_FILE_CONTENT_2"),
new CheckedFile("TEST_FILE_NAME_3", "TEST_FILE_PATH_3", "TEST_FILE_CONTENT_3")));
useOpenAIService();
ConfigurationState.getInstance().setSystemPrompt(COMPLETION_SYSTEM_PROMPT);
var message = new Message("Hello!");
var conversation = ConversationService.getInstance().startConversation();
var panel = new StandardChatToolWindowTabPanel(getProject(), conversation);
expectOpenAI((StreamHttpExchange) request -> {
assertThat(request.getUri().getPath()).isEqualTo("/v1/chat/completions");
assertThat(request.getMethod()).isEqualTo("POST");
assertThat(request.getHeaders().get(AUTHORIZATION).get(0)).isEqualTo("Bearer TEST_API_KEY");
assertThat(request.getBody())
.extracting(
"model",
"messages")
.containsExactly(
"gpt-4",
List.of(
Map.of("role", "system", "content", COMPLETION_SYSTEM_PROMPT),
Map.of("role", "user", "content",
"Use the following context to answer question at the end:\n\n"
+ "File Path: TEST_FILE_PATH_1\n"
+ "File Content:\n"
+ "```TEST_FILE_NAME_1\n"
+ "TEST_FILE_CONTENT_1\n"
+ "```\n\n"
+ "File Path: TEST_FILE_PATH_2\n"
+ "File Content:\n"
+ "```TEST_FILE_NAME_2\n"
+ "TEST_FILE_CONTENT_2\n"
+ "```\n\n"
+ "File Path: TEST_FILE_PATH_3\n"
+ "File Content:\n"
+ "```TEST_FILE_NAME_3\n"
+ "TEST_FILE_CONTENT_3\n"
+ "```\n\n"
+ "Question: Hello!")));
return List.of(
jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("role", "assistant")))),
jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "Hel")))),
jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "lo")))),
jsonMapResponse("choices", jsonArray(jsonMap("delta", jsonMap("content", "!")))));
});
panel.sendMessage(message);
await().atMost(5, SECONDS)
.until(() -> {
var messages = conversation.getMessages();
return !messages.isEmpty() && "Hello!".equals(messages.get(0).getResponse());
});
var encodingManager = EncodingManager.getInstance();
assertThat(panel.getTokenDetails()).extracting(
"systemPromptTokens",
"conversationTokens",
"userPromptTokens",
"highlightedTokens")
.containsExactly(
encodingManager.countTokens(COMPLETION_SYSTEM_PROMPT),
encodingManager.countTokens(message.getPrompt()),
0,
0);
assertThat(panel.getConversation())
.isNotNull()
.extracting("id", "model", "clientCode", "discardTokenLimit")
.containsExactly(
conversation.getId(),
conversation.getModel(),
conversation.getClientCode(),
false);
var messages = panel.getConversation().getMessages();
assertThat(messages.size()).isOne();
assertThat(messages.get(0))
.extracting("id", "prompt", "response", "referencedFilePaths")
.containsExactly(
message.getId(),
message.getPrompt(),
message.getResponse(),
List.of("TEST_FILE_PATH_1", "TEST_FILE_PATH_2", "TEST_FILE_PATH_3"));
}
}