mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-11 13:10:50 +00:00
feat: improved popup suggestions and personas support (#638)
* feat: support personas * fix: replace previous system prompts with personas * feat: add persona toolbar label * refactor: rename properties * refactor: clean up * fix: personas settings configurable state * refactor: code cleanup * feat: list item auto highlightning * feat: replace personas toolbar label with action link * refactor: code cleanup * fix: manual items not being able to delete * fix: personas settings configurable state * refactor: clean up code * fix: folder selection
This commit is contained in:
parent
40fa27a8bd
commit
307c12e15d
23 changed files with 4266 additions and 369 deletions
|
|
@ -21,6 +21,7 @@ import ee.carlrobert.codegpt.conversations.message.Message;
|
|||
import ee.carlrobert.codegpt.credentials.CredentialsStore;
|
||||
import ee.carlrobert.codegpt.settings.IncludedFilesSettings;
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings;
|
||||
import ee.carlrobert.codegpt.settings.persona.PersonaSettings;
|
||||
import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings;
|
||||
import ee.carlrobert.codegpt.settings.service.custom.CustomServiceChatCompletionSettingsState;
|
||||
import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings;
|
||||
|
|
@ -71,9 +72,6 @@ import org.jetbrains.annotations.Nullable;
|
|||
|
||||
public class CompletionRequestProvider {
|
||||
|
||||
public static final String COMPLETION_SYSTEM_PROMPT =
|
||||
getResourceContent("/prompts/default-completion.txt");
|
||||
|
||||
public static final String GENERATE_COMMIT_MESSAGE_SYSTEM_PROMPT =
|
||||
getResourceContent("/prompts/generate-commit-message.txt");
|
||||
|
||||
|
|
@ -197,7 +195,7 @@ public class CompletionRequestProvider {
|
|||
}
|
||||
|
||||
var systemPrompt = conversationType == FIX_COMPILE_ERRORS
|
||||
? FIX_COMPILE_ERRORS_SYSTEM_PROMPT : ConfigurationSettings.getSystemPrompt();
|
||||
? FIX_COMPILE_ERRORS_SYSTEM_PROMPT : PersonaSettings.getSystemPrompt();
|
||||
|
||||
var prompt = promptTemplate.buildPrompt(
|
||||
systemPrompt,
|
||||
|
|
@ -302,7 +300,7 @@ public class CompletionRequestProvider {
|
|||
request.setModel(settings.getModel());
|
||||
request.setMaxTokens(configuration.getMaxTokens());
|
||||
request.setStream(true);
|
||||
request.setSystem(ConfigurationSettings.getSystemPrompt());
|
||||
request.setSystem(PersonaSettings.getSystemPrompt());
|
||||
List<ClaudeCompletionMessage> messages = conversation.getMessages().stream()
|
||||
.filter(prevMessage -> prevMessage.getResponse() != null
|
||||
&& !prevMessage.getResponse().isEmpty())
|
||||
|
|
@ -345,8 +343,8 @@ public class CompletionRequestProvider {
|
|||
var message = callParameters.getMessage();
|
||||
var messages = new ArrayList<OllamaChatCompletionMessage>();
|
||||
if (callParameters.getConversationType() == ConversationType.DEFAULT) {
|
||||
String systemPrompt = ConfigurationSettings.getCurrentState().getSystemPrompt();
|
||||
messages.add(new OllamaChatCompletionMessage("system", systemPrompt, null));
|
||||
messages.add(
|
||||
new OllamaChatCompletionMessage("system", PersonaSettings.getSystemPrompt(), null));
|
||||
}
|
||||
if (callParameters.getConversationType() == ConversationType.FIX_COMPILE_ERRORS) {
|
||||
messages.add(
|
||||
|
|
@ -397,8 +395,8 @@ public class CompletionRequestProvider {
|
|||
var message = callParameters.getMessage();
|
||||
var messages = new ArrayList<OpenAIChatCompletionMessage>();
|
||||
if (callParameters.getConversationType() == ConversationType.DEFAULT) {
|
||||
String systemPrompt = ConfigurationSettings.getCurrentState().getSystemPrompt();
|
||||
messages.add(new OpenAIChatCompletionStandardMessage("system", systemPrompt));
|
||||
messages.add(
|
||||
new OpenAIChatCompletionStandardMessage("system", PersonaSettings.getSystemPrompt()));
|
||||
}
|
||||
if (callParameters.getConversationType() == ConversationType.FIX_COMPILE_ERRORS) {
|
||||
messages.add(
|
||||
|
|
@ -474,8 +472,7 @@ public class CompletionRequestProvider {
|
|||
// Gemini API does not support direct 'system' prompts:
|
||||
// see https://www.reddit.com/r/Bard/comments/1b90i8o/does_gemini_have_a_system_prompt_option_while/
|
||||
if (callParameters.getConversationType() == ConversationType.DEFAULT) {
|
||||
String systemPrompt = ConfigurationSettings.getCurrentState().getSystemPrompt();
|
||||
messages.add(new GoogleCompletionContent("user", List.of(systemPrompt)));
|
||||
messages.add(new GoogleCompletionContent("user", List.of(PersonaSettings.getSystemPrompt())));
|
||||
messages.add(new GoogleCompletionContent("model", List.of("Understood.")));
|
||||
}
|
||||
if (callParameters.getConversationType() == ConversationType.FIX_COMPILE_ERRORS) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
package ee.carlrobert.codegpt.settings.configuration;
|
||||
|
||||
import static ee.carlrobert.codegpt.actions.editor.EditorActionsUtil.DEFAULT_ACTIONS_ARRAY;
|
||||
import static ee.carlrobert.codegpt.completions.CompletionRequestProvider.COMPLETION_SYSTEM_PROMPT;
|
||||
|
||||
import com.intellij.icons.AllIcons;
|
||||
import com.intellij.icons.AllIcons.Nodes;
|
||||
|
|
@ -49,7 +48,6 @@ public class ConfigurationComponent {
|
|||
private final JBCheckBox autoFormattingCheckBox;
|
||||
private final JBCheckBox autocompletionPostProcessingCheckBox;
|
||||
private final JBCheckBox autocompletionContextAwareCheckBox;
|
||||
private final JTextArea systemPromptTextArea;
|
||||
private final JTextArea commitMessagePromptTextArea;
|
||||
private final IntegerField maxTokensField;
|
||||
private final JBTextField temperatureField;
|
||||
|
|
@ -94,17 +92,6 @@ public class ConfigurationComponent {
|
|||
maxTokensField.setColumns(12);
|
||||
maxTokensField.setValue(configuration.getMaxTokens());
|
||||
|
||||
systemPromptTextArea = new JTextArea(3, 60);
|
||||
if (configuration.getSystemPrompt().isBlank()) {
|
||||
// for backward compatibility
|
||||
systemPromptTextArea.setText(COMPLETION_SYSTEM_PROMPT);
|
||||
} else {
|
||||
systemPromptTextArea.setText(configuration.getSystemPrompt());
|
||||
}
|
||||
systemPromptTextArea.setLineWrap(true);
|
||||
systemPromptTextArea.setWrapStyleWord(true);
|
||||
systemPromptTextArea.setBorder(JBUI.Borders.empty(8, 4));
|
||||
|
||||
commitMessagePromptTextArea = new JTextArea(configuration.getCommitMessagePrompt(), 3, 60);
|
||||
commitMessagePromptTextArea.setLineWrap(true);
|
||||
commitMessagePromptTextArea.setWrapStyleWord(true);
|
||||
|
|
@ -164,7 +151,6 @@ public class ConfigurationComponent {
|
|||
state.setTableData(getTableData());
|
||||
state.setMaxTokens(maxTokensField.getValue());
|
||||
state.setTemperature(Double.parseDouble(temperatureField.getText()));
|
||||
state.setSystemPrompt(systemPromptTextArea.getText());
|
||||
state.setCommitMessagePrompt(commitMessagePromptTextArea.getText());
|
||||
state.setCheckForPluginUpdates(checkForPluginUpdatesCheckBox.isSelected());
|
||||
state.setCheckForNewScreenshots(checkForNewScreenshotsCheckBox.isSelected());
|
||||
|
|
@ -181,7 +167,6 @@ public class ConfigurationComponent {
|
|||
setTableData(configuration.getTableData());
|
||||
maxTokensField.setValue(configuration.getMaxTokens());
|
||||
temperatureField.setText(String.valueOf(configuration.getTemperature()));
|
||||
systemPromptTextArea.setText(configuration.getSystemPrompt());
|
||||
commitMessagePromptTextArea.setText(configuration.getCommitMessagePrompt());
|
||||
checkForPluginUpdatesCheckBox.setSelected(configuration.isCheckForPluginUpdates());
|
||||
checkForNewScreenshotsCheckBox.setSelected(configuration.isCheckForNewScreenshots());
|
||||
|
|
@ -242,15 +227,6 @@ public class ConfigurationComponent {
|
|||
|
||||
private JPanel createAssistantConfigurationForm() {
|
||||
var formBuilder = FormBuilder.createFormBuilder();
|
||||
addAssistantFormLabeledComponent(
|
||||
formBuilder,
|
||||
"configurationConfigurable.section.assistant.systemPromptField.label",
|
||||
"configurationConfigurable.section.assistant.systemPromptField.comment",
|
||||
JBUI.Panels
|
||||
.simplePanel(systemPromptTextArea)
|
||||
.withBorder(JBUI.Borders.customLine(
|
||||
JBUI.CurrentTheme.CustomFrameDecorations.separatorForeground())));
|
||||
formBuilder.addVerticalGap(8);
|
||||
addAssistantFormLabeledComponent(
|
||||
formBuilder,
|
||||
"configurationConfigurable.section.assistant.temperatureField.label",
|
||||
|
|
|
|||
|
|
@ -31,8 +31,4 @@ public class ConfigurationSettings implements PersistentStateComponent<Configura
|
|||
public static ConfigurationSettings getInstance() {
|
||||
return ApplicationManager.getApplication().getService(ConfigurationSettings.class);
|
||||
}
|
||||
|
||||
public static String getSystemPrompt() {
|
||||
return getCurrentState().getSystemPrompt();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package ee.carlrobert.codegpt.settings.configuration;
|
||||
|
||||
import static ee.carlrobert.codegpt.completions.CompletionRequestProvider.COMPLETION_SYSTEM_PROMPT;
|
||||
import static ee.carlrobert.codegpt.completions.CompletionRequestProvider.GENERATE_COMMIT_MESSAGE_SYSTEM_PROMPT;
|
||||
|
||||
import ee.carlrobert.codegpt.actions.editor.EditorActionsUtil;
|
||||
|
|
@ -9,7 +8,6 @@ import java.util.Objects;
|
|||
|
||||
public class ConfigurationState {
|
||||
|
||||
private String systemPrompt = COMPLETION_SYSTEM_PROMPT;
|
||||
private String commitMessagePrompt = GENERATE_COMMIT_MESSAGE_SYSTEM_PROMPT;
|
||||
private int maxTokens = 2048;
|
||||
private double temperature = 0.1;
|
||||
|
|
@ -24,18 +22,10 @@ public class ConfigurationState {
|
|||
private boolean autocompletionContextAwareEnabled = false;
|
||||
private Map<String, String> tableData = EditorActionsUtil.DEFAULT_ACTIONS;
|
||||
|
||||
public String getSystemPrompt() {
|
||||
return systemPrompt;
|
||||
}
|
||||
|
||||
public String getCommitMessagePrompt() {
|
||||
return commitMessagePrompt;
|
||||
}
|
||||
|
||||
public void setSystemPrompt(String systemPrompt) {
|
||||
this.systemPrompt = systemPrompt;
|
||||
}
|
||||
|
||||
public void setCommitMessagePrompt(String commitMessagePrompt) {
|
||||
this.commitMessagePrompt = commitMessagePrompt;
|
||||
}
|
||||
|
|
@ -155,14 +145,13 @@ public class ConfigurationState {
|
|||
&& autoFormattingEnabled == that.autoFormattingEnabled
|
||||
&& autocompletionPostProcessingEnabled == that.autocompletionPostProcessingEnabled
|
||||
&& autocompletionContextAwareEnabled == that.autocompletionContextAwareEnabled
|
||||
&& Objects.equals(systemPrompt, that.systemPrompt)
|
||||
&& Objects.equals(commitMessagePrompt, that.commitMessagePrompt)
|
||||
&& Objects.equals(tableData, that.tableData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(systemPrompt, commitMessagePrompt, maxTokens, temperature,
|
||||
return Objects.hash(commitMessagePrompt, maxTokens, temperature,
|
||||
checkForPluginUpdates, checkForNewScreenshots, createNewChatOnEachAction,
|
||||
ignoreGitCommitTokenLimit, methodNameGenerationEnabled, captureCompileErrors,
|
||||
autoFormattingEnabled, autocompletionPostProcessingEnabled,
|
||||
|
|
|
|||
|
|
@ -7,8 +7,13 @@ import com.intellij.ide.BrowserUtil;
|
|||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.actionSystem.ActionManager;
|
||||
import com.intellij.openapi.actionSystem.ActionToolbar;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.DefaultCompactActionGroup;
|
||||
import com.intellij.openapi.actionSystem.Presentation;
|
||||
import com.intellij.openapi.actionSystem.ex.CustomComponentAction;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.options.ShowSettingsUtil;
|
||||
import com.intellij.openapi.project.DumbAwareAction;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.SimpleToolWindowPanel;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
|
|
@ -23,6 +28,8 @@ import ee.carlrobert.codegpt.actions.toolwindow.OpenInEditorAction;
|
|||
import ee.carlrobert.codegpt.conversations.ConversationService;
|
||||
import ee.carlrobert.codegpt.conversations.ConversationsState;
|
||||
import ee.carlrobert.codegpt.settings.GeneralSettings;
|
||||
import ee.carlrobert.codegpt.settings.persona.PersonaSettings;
|
||||
import ee.carlrobert.codegpt.settings.persona.PersonasConfigurable;
|
||||
import ee.carlrobert.codegpt.settings.service.ProviderChangeNotifier;
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType;
|
||||
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTUserDetailsNotifier;
|
||||
|
|
@ -35,6 +42,7 @@ import java.nio.file.Paths;
|
|||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.swing.BoxLayout;
|
||||
import javax.swing.JComponent;
|
||||
import javax.swing.JPanel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
|
|
@ -165,6 +173,8 @@ public class ChatToolWindowPanel extends SimpleToolWindowPanel {
|
|||
new ClearChatWindowAction(() -> tabbedPane.resetCurrentlyActiveTabPanel(project)));
|
||||
actionGroup.addSeparator();
|
||||
actionGroup.add(new OpenInEditorAction());
|
||||
actionGroup.addSeparator();
|
||||
actionGroup.add(new SelectedPersonaActionLink(project));
|
||||
|
||||
var toolbar = ActionManager.getInstance()
|
||||
.createActionToolbar("NAVIGATION_BAR_TOOLBAR", actionGroup, true);
|
||||
|
|
@ -186,4 +196,47 @@ public class ChatToolWindowPanel extends SimpleToolWindowPanel {
|
|||
.syncPublisher(IncludeFilesInContextNotifier.FILES_INCLUDED_IN_CONTEXT_TOPIC)
|
||||
.filesIncluded(emptyList());
|
||||
}
|
||||
|
||||
private static class SelectedPersonaActionLink extends DumbAwareAction implements
|
||||
CustomComponentAction {
|
||||
|
||||
private final Project project;
|
||||
|
||||
SelectedPersonaActionLink(Project project) {
|
||||
this.project = project;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public JComponent createCustomComponent(
|
||||
@NotNull Presentation presentation,
|
||||
@NotNull String place) {
|
||||
var link = new ActionLink(getSelectedPersonaName(), (e) -> {
|
||||
ShowSettingsUtil.getInstance()
|
||||
.showSettingsDialog(project, PersonasConfigurable.class);
|
||||
});
|
||||
link.setExternalLinkIcon();
|
||||
link.setFont(JBUI.Fonts.smallFont());
|
||||
link.setBorder(JBUI.Borders.empty(0, 4));
|
||||
return link;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateCustomComponent(
|
||||
@NotNull JComponent component,
|
||||
@NotNull Presentation presentation) {
|
||||
((ActionLink) component).setText(getSelectedPersonaName());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
}
|
||||
|
||||
private String getSelectedPersonaName() {
|
||||
return ApplicationManager.getApplication().getService(PersonaSettings.class)
|
||||
.getState()
|
||||
.getSelectedPersona()
|
||||
.getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat.ui.textarea;
|
||||
|
||||
import ee.carlrobert.codegpt.EncodingManager;
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings;
|
||||
import ee.carlrobert.codegpt.settings.persona.PersonaSettings;
|
||||
|
||||
public class TotalTokensDetails {
|
||||
|
||||
|
|
@ -12,7 +12,7 @@ public class TotalTokensDetails {
|
|||
private int referencedFilesTokens;
|
||||
|
||||
public TotalTokensDetails(EncodingManager encodingManager) {
|
||||
systemPromptTokens = encodingManager.countTokens(ConfigurationSettings.getSystemPrompt());
|
||||
systemPromptTokens = encodingManager.countTokens(PersonaSettings.getSystemPrompt());
|
||||
}
|
||||
|
||||
public int getSystemPromptTokens() {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,44 @@
|
|||
package ee.carlrobert.codegpt.settings.persona
|
||||
|
||||
import com.intellij.openapi.components.*
|
||||
|
||||
const val DEFAULT_PROMPT =
|
||||
"You are an AI programming assistant.\nFollow the user's requirements carefully & to the letter.\nYour responses should be informative and logical.\nYou should always adhere to technical information.\nIf the user asks for code or technical questions, you must provide code suggestions and adhere to technical information.\nIf the question is related to a developer, you must respond with content related to a developer.\nFirst think step-by-step - describe your plan for what to build in pseudocode, written out in great detail.\nThen output the code in a single code block.\nMinimize any other prose.\nKeep your answers short and impersonal.\nUse Markdown formatting in your answers.\nMake sure to include the programming language name at the start of the Markdown code blocks.\nAvoid wrapping the whole response in triple backticks.\nThe user works in an IDE built by JetBrains which has a concept for editors with open files, integrated unit test support, and output pane that shows the output of running the code as well as an integrated terminal.\nYou can only give one reply for each conversation turn."
|
||||
|
||||
@Service
|
||||
@State(
|
||||
name = "CodeGPT_PersonaSettings",
|
||||
storages = [Storage("CodeGPT_PersonaSettings.xml")]
|
||||
)
|
||||
class PersonaSettings :
|
||||
SimplePersistentStateComponent<PersonaSettingsState>(PersonaSettingsState()) {
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getSystemPrompt(): String {
|
||||
return service<PersonaSettings>().state.selectedPersona.instructions ?: ""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PersonaSettingsState : BaseState() {
|
||||
var selectedPersona by property(PersonaDetailsState())
|
||||
var userCreatedPersonas by list<PersonaDetailsState>()
|
||||
}
|
||||
|
||||
class PersonaDetailsState : BaseState() {
|
||||
var id by property(1L)
|
||||
var name by string("CodeGPT Default")
|
||||
var instructions by string(DEFAULT_PROMPT)
|
||||
}
|
||||
|
||||
@JvmRecord
|
||||
data class PersonaDetails(val id: Long, val name: String, val instructions: String)
|
||||
|
||||
fun PersonaDetails.toPersonaDetailsState(): PersonaDetailsState {
|
||||
val newState = PersonaDetailsState()
|
||||
newState.id = id
|
||||
newState.name = name
|
||||
newState.instructions = instructions
|
||||
return newState
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package ee.carlrobert.codegpt.settings.persona
|
||||
|
||||
import com.intellij.openapi.options.Configurable
|
||||
import javax.swing.JComponent
|
||||
|
||||
class PersonasConfigurable : Configurable {
|
||||
|
||||
private lateinit var component: PersonasSettingsForm
|
||||
|
||||
override fun getDisplayName(): String {
|
||||
return "CodeGPT: Personas"
|
||||
}
|
||||
|
||||
override fun createComponent(): JComponent {
|
||||
component = PersonasSettingsForm()
|
||||
return component.createPanel()
|
||||
}
|
||||
|
||||
override fun isModified(): Boolean = component.isModified()
|
||||
|
||||
override fun apply() {
|
||||
component.applyChanges()
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
component.resetChanges()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,288 @@
|
|||
package ee.carlrobert.codegpt.settings.persona
|
||||
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.actionSystem.AnAction
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.ui.DialogPanel
|
||||
import com.intellij.ui.DocumentAdapter
|
||||
import com.intellij.ui.ToolbarDecorator
|
||||
import com.intellij.ui.components.JBScrollPane
|
||||
import com.intellij.ui.components.JBTextArea
|
||||
import com.intellij.ui.components.JBTextField
|
||||
import com.intellij.ui.dsl.builder.Align
|
||||
import com.intellij.ui.dsl.builder.LabelPosition
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import com.intellij.ui.table.JBTable
|
||||
import com.intellij.util.ui.JBUI
|
||||
import ee.carlrobert.codegpt.util.ResourceUtil
|
||||
import java.awt.Dimension
|
||||
import javax.swing.UIManager
|
||||
import javax.swing.event.DocumentEvent
|
||||
import javax.swing.table.DefaultTableModel
|
||||
import javax.swing.text.JTextComponent
|
||||
|
||||
class NonEditableTableModel(columnNames: Array<String>, rowCount: Int) :
|
||||
DefaultTableModel(columnNames, rowCount) {
|
||||
override fun isCellEditable(row: Int, column: Int) = false
|
||||
}
|
||||
|
||||
class PersonasSettingsForm {
|
||||
|
||||
private val tableModel =
|
||||
NonEditableTableModel(arrayOf("Id", "Name", "Instructions", "FromResource"), 0)
|
||||
private val table = JBTable(tableModel).apply {
|
||||
setupTableColumns()
|
||||
selectionModel.addListSelectionListener { populateEditArea() }
|
||||
}
|
||||
private val nameField = JBTextField().apply {
|
||||
addTextChangeListener { newText ->
|
||||
updateTableModelIfRowSelected(1, newText)
|
||||
}
|
||||
}
|
||||
private val instructionsTextArea = JBTextArea().apply {
|
||||
lineWrap = true
|
||||
wrapStyleWord = true
|
||||
font = UIManager.getFont("TextField.font")
|
||||
border = JBUI.Borders.empty(3, 6)
|
||||
addTextChangeListener { newText ->
|
||||
updateTableModelIfRowSelected(2, newText)
|
||||
}
|
||||
}
|
||||
private val addedItems = mutableListOf<PersonaDetails>()
|
||||
private val removedItemIds = mutableListOf<Long>()
|
||||
|
||||
init {
|
||||
setupForm()
|
||||
}
|
||||
|
||||
fun createPanel(): DialogPanel {
|
||||
return panel {
|
||||
row {
|
||||
val toolbarDecorator = ToolbarDecorator.createDecorator(table)
|
||||
.setAddAction { handleAddItem() }
|
||||
.setRemoveAction { handleRemoveItem() }
|
||||
.addExtraAction(object :
|
||||
AnAction("Duplicate", "Duplicate persona", AllIcons.Actions.Copy) {
|
||||
override fun actionPerformed(e: AnActionEvent) {
|
||||
handleDuplicateItem()
|
||||
}
|
||||
})
|
||||
.setRemoveActionUpdater {
|
||||
val selectedRow = table.selectedRow
|
||||
selectedRow != -1 && !(tableModel.getValueAt(selectedRow, 3) as Boolean)
|
||||
}
|
||||
.disableUpDownActions()
|
||||
|
||||
cell(toolbarDecorator.createPanel())
|
||||
.align(Align.FILL)
|
||||
.resizableColumn()
|
||||
.applyToComponent {
|
||||
preferredSize = Dimension(650, 250)
|
||||
}
|
||||
}
|
||||
row {
|
||||
cell(nameField)
|
||||
.label("Name:", LabelPosition.TOP)
|
||||
.align(Align.FILL)
|
||||
}
|
||||
row {
|
||||
cell(JBScrollPane(instructionsTextArea).apply {
|
||||
preferredSize = Dimension(650, 225)
|
||||
})
|
||||
.label("Instructions:", LabelPosition.TOP)
|
||||
.align(Align.FILL)
|
||||
.resizableColumn()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun applyChanges() {
|
||||
val persona = getSelectedPersona()
|
||||
service<PersonaSettings>().state.run {
|
||||
if (persona != null) {
|
||||
selectedPersona = persona.toPersonaDetailsState()
|
||||
}
|
||||
|
||||
userCreatedPersonas.removeIf { removedItemIds.contains(it.id) }
|
||||
userCreatedPersonas.addAll(addedItems.map { it.toPersonaDetailsState() })
|
||||
}
|
||||
clear()
|
||||
}
|
||||
|
||||
fun isModified(): Boolean {
|
||||
service<PersonaSettings>().state.let {
|
||||
val (id, name, description) = getSelectedPersona() ?: return false
|
||||
return it.selectedPersona.id != id
|
||||
|| it.selectedPersona.name != name
|
||||
|| it.selectedPersona.instructions != description
|
||||
|| removedItemIds.size > 0
|
||||
|| addedItems.size > 0
|
||||
}
|
||||
}
|
||||
|
||||
fun resetChanges() {
|
||||
clear()
|
||||
tableModel.rowCount = 0
|
||||
setupForm()
|
||||
}
|
||||
|
||||
private fun populateEditArea() {
|
||||
val selectedRow = table.selectedRow
|
||||
if (selectedRow != -1) {
|
||||
val userCreatedResource = !(tableModel.getValueAt(selectedRow, 3) as Boolean)
|
||||
|
||||
nameField.text = tableModel.getValueAt(selectedRow, 1) as String
|
||||
nameField.isEnabled = userCreatedResource
|
||||
instructionsTextArea.text = tableModel.getValueAt(selectedRow, 2) as String
|
||||
instructionsTextArea.isEnabled = userCreatedResource
|
||||
} else {
|
||||
nameField.text = ""
|
||||
instructionsTextArea.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAddItem() {
|
||||
addPersonaToTable(createNewPersona("New Persona", "New Prompt"))
|
||||
}
|
||||
|
||||
private fun handleDuplicateItem() {
|
||||
val selectedRow = table.selectedRow
|
||||
val originalName = tableModel.getValueAt(selectedRow, 1) as String
|
||||
val originalPrompt = tableModel.getValueAt(selectedRow, 2) as String
|
||||
addPersonaToTable(createNewPersona("$originalName Copy", originalPrompt))
|
||||
}
|
||||
|
||||
private fun createNewPersona(name: String, prompt: String): PersonaDetails {
|
||||
val newId = findMaxId() + 1
|
||||
return PersonaDetails(newId, name, prompt)
|
||||
}
|
||||
|
||||
private fun addPersonaToTable(persona: PersonaDetails) {
|
||||
addedItems.add(persona)
|
||||
tableModel.addRow(arrayOf(persona.id, persona.name, persona.instructions, false))
|
||||
selectLastRowAndUpdateUI()
|
||||
}
|
||||
|
||||
private fun findMaxId(): Long {
|
||||
return (0 until table.rowCount).maxOf {
|
||||
tableModel.getValueAt(it, 0) as Long
|
||||
}
|
||||
}
|
||||
|
||||
private fun selectLastRowAndUpdateUI() {
|
||||
val lastRow = table.rowCount - 1
|
||||
table.setRowSelectionInterval(lastRow, lastRow)
|
||||
populateEditArea()
|
||||
scrollToLastRow()
|
||||
nameField.requestFocus()
|
||||
}
|
||||
|
||||
private fun handleRemoveItem() {
|
||||
val selectedRow = table.selectedRow
|
||||
if (selectedRow != -1 && !(tableModel.getValueAt(selectedRow, 3) as Boolean)) {
|
||||
val id = tableModel.getValueAt(selectedRow, 0) as Long
|
||||
if (addedItems.none { it.id == id }) {
|
||||
removedItemIds.add(id)
|
||||
}
|
||||
addedItems.filter { it.id != id }
|
||||
tableModel.removeRow(selectedRow)
|
||||
populateEditArea()
|
||||
|
||||
val newSelectedRow = if (selectedRow > 0) selectedRow - 1 else 0
|
||||
if (table.rowCount > 0) {
|
||||
table.setRowSelectionInterval(newSelectedRow, newSelectedRow)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupForm() {
|
||||
service<PersonaSettings>().state.run {
|
||||
userCreatedPersonas.forEachIndexed { index, persona ->
|
||||
tableModel.addPersonaRow(
|
||||
PersonaDetails(
|
||||
persona.id,
|
||||
persona.name!!,
|
||||
persona.instructions!!
|
||||
),
|
||||
selectedPersona.id,
|
||||
index
|
||||
)
|
||||
}
|
||||
ResourceUtil.getFilteredPersonaSuggestions().forEachIndexed { index, persona ->
|
||||
tableModel.addPersonaRow(persona, selectedPersona.id, index, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun DefaultTableModel.addPersonaRow(
|
||||
persona: PersonaDetails,
|
||||
selectedPersonaId: Long,
|
||||
rowIndex: Int,
|
||||
fromResource: Boolean = false
|
||||
) {
|
||||
val (id, name, instructions) = persona
|
||||
addRow(arrayOf(id, name, instructions, fromResource))
|
||||
if (selectedPersonaId == id) {
|
||||
table.setRowSelectionInterval(rowIndex, rowIndex)
|
||||
}
|
||||
}
|
||||
|
||||
private fun scrollToLastRow() {
|
||||
table.scrollRectToVisible(table.getCellRect(table.rowCount - 1, 0, true))
|
||||
}
|
||||
|
||||
private fun JBTable.setupTableColumns() {
|
||||
columnModel.apply {
|
||||
getColumn(0).apply {
|
||||
minWidth = 0
|
||||
maxWidth = 0
|
||||
preferredWidth = 0
|
||||
resizable = false
|
||||
}
|
||||
getColumn(1).preferredWidth = 60
|
||||
getColumn(2).preferredWidth = 240
|
||||
getColumn(3).apply {
|
||||
minWidth = 0
|
||||
maxWidth = 0
|
||||
preferredWidth = 0
|
||||
resizable = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun JBTable.getSelectedPersona(): PersonaDetails? {
|
||||
if (selectedRow == -1) {
|
||||
return null
|
||||
}
|
||||
|
||||
return PersonaDetails(
|
||||
tableModel.getValueAt(selectedRow, 0) as Long,
|
||||
tableModel.getValueAt(selectedRow, 1) as String,
|
||||
tableModel.getValueAt(selectedRow, 2) as String
|
||||
)
|
||||
}
|
||||
|
||||
private fun JTextComponent.addTextChangeListener(listener: (String) -> Unit) {
|
||||
document.addDocumentListener(object : DocumentAdapter() {
|
||||
override fun textChanged(e: DocumentEvent) {
|
||||
listener(e.document.getText(0, e.document.length))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateTableModelIfRowSelected(column: Int, newValue: Any) {
|
||||
if (table.selectedRow != -1) {
|
||||
tableModel.setValueAt(newValue, table.selectedRow, column)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getSelectedPersona(): PersonaDetails? {
|
||||
return table.getSelectedPersona()
|
||||
}
|
||||
|
||||
private fun clear() {
|
||||
addedItems.clear()
|
||||
removedItemIds.clear()
|
||||
}
|
||||
}
|
||||
|
|
@ -4,6 +4,7 @@ import com.intellij.openapi.components.service
|
|||
import com.intellij.openapi.editor.colors.EditorColorsManager
|
||||
import com.intellij.openapi.editor.colors.EditorFontType
|
||||
import com.intellij.openapi.editor.ex.util.EditorUtil
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.ui.JBColor
|
||||
import com.intellij.util.ui.JBFont
|
||||
|
|
@ -43,8 +44,12 @@ class CustomTextPane(private val onSubmit: (String) -> Unit) : JTextPane() {
|
|||
})
|
||||
}
|
||||
|
||||
fun highlightText(text: String) {
|
||||
val lastIndex = this.text.lastIndexOf('@')
|
||||
fun appendHighlightedText(
|
||||
text: String,
|
||||
searchChar: Char = '@',
|
||||
withWhitespace: Boolean = true
|
||||
): TextRange? {
|
||||
val lastIndex = this.text.lastIndexOf(searchChar)
|
||||
if (lastIndex != -1) {
|
||||
val styleContext = StyleContext.getDefaultStyleContext()
|
||||
val fileNameStyle = styleContext.addStyle("smart-highlighter", null)
|
||||
|
|
@ -71,12 +76,16 @@ class CustomTextPane(private val onSubmit: (String) -> Unit) : JTextPane() {
|
|||
fileNameStyle,
|
||||
true
|
||||
)
|
||||
document.insertString(
|
||||
document.length,
|
||||
" ",
|
||||
styleContext.getStyle(StyleContext.DEFAULT_STYLE)
|
||||
)
|
||||
if (withWhitespace) {
|
||||
document.insertString(
|
||||
document.length,
|
||||
" ",
|
||||
styleContext.getStyle(StyleContext.DEFAULT_STYLE)
|
||||
)
|
||||
}
|
||||
return TextRange(lastIndex, lastIndex + text.length)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun paintComponent(g: Graphics) {
|
||||
|
|
@ -90,7 +99,6 @@ class CustomTextPane(private val onSubmit: (String) -> Unit) : JTextPane() {
|
|||
} else {
|
||||
UIManager.getFont("TextField.font")
|
||||
}
|
||||
// Draw placeholder
|
||||
g2d.drawString(
|
||||
CodeGPTBundle.get("toolwindow.chat.textArea.emptyText"),
|
||||
insets.left,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,109 @@
|
|||
package ee.carlrobert.codegpt.ui.textarea
|
||||
|
||||
import com.intellij.openapi.application.runInEdt
|
||||
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 kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.awt.event.KeyAdapter
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.text.StyleContext
|
||||
import javax.swing.text.StyledDocument
|
||||
|
||||
class CustomTextPaneKeyAdapter(
|
||||
private val project: Project,
|
||||
private val textPane: CustomTextPane
|
||||
) : KeyAdapter() {
|
||||
|
||||
private val suggestionsPopupManager = SuggestionsPopupManager(project, textPane)
|
||||
private val popupOpenedAtRange: AtomicReference<TextRange?> = AtomicReference(null)
|
||||
|
||||
override fun keyReleased(e: KeyEvent) {
|
||||
if (textPane.text.isEmpty()) {
|
||||
// TODO: Remove only the files that were added via shortcuts
|
||||
project.service<FileSearchService>().removeFilesFromSession()
|
||||
suggestionsPopupManager.hidePopup()
|
||||
return
|
||||
}
|
||||
if (e.keyCode == KeyEvent.VK_BACK_SPACE) {
|
||||
if (popupOpenedAtRange.get() == TextRange(
|
||||
textPane.caretPosition,
|
||||
textPane.caretPosition + 1
|
||||
)
|
||||
) {
|
||||
suggestionsPopupManager.hidePopup()
|
||||
return
|
||||
}
|
||||
|
||||
if (textPane.text.isNotEmpty() && textPane.text.last() == '@') {
|
||||
suggestionsPopupManager.reset()
|
||||
}
|
||||
}
|
||||
|
||||
when (e.keyCode) {
|
||||
KeyEvent.VK_UP, KeyEvent.VK_DOWN -> {
|
||||
suggestionsPopupManager.requestFocus()
|
||||
suggestionsPopupManager.selectNext()
|
||||
e.consume()
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (suggestionsPopupManager.isPopupVisible()) {
|
||||
updateSuggestions()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun keyTyped(e: KeyEvent) {
|
||||
val popupVisible = suggestionsPopupManager.isPopupVisible()
|
||||
if (e.keyChar == '@' && !popupVisible) {
|
||||
suggestionsPopupManager.showPopup(textPane)
|
||||
popupOpenedAtRange.getAndSet(
|
||||
TextRange(
|
||||
textPane.caretPosition,
|
||||
textPane.caretPosition + 1
|
||||
)
|
||||
)
|
||||
return
|
||||
} else if (e.keyChar == '\t') {
|
||||
suggestionsPopupManager.requestFocus()
|
||||
suggestionsPopupManager.selectNext()
|
||||
return
|
||||
} else if (popupVisible) {
|
||||
updateSuggestions()
|
||||
}
|
||||
|
||||
val doc = textPane.document as StyledDocument
|
||||
if (textPane.caretPosition >= 0) {
|
||||
doc.setCharacterAttributes(
|
||||
textPane.caretPosition,
|
||||
1,
|
||||
StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE),
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSuggestions() {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
runInEdt {
|
||||
val lastAtIndex = textPane.text.lastIndexOf('@')
|
||||
if (lastAtIndex != -1) {
|
||||
val lastAtSearchIndex = textPane.text.lastIndexOf(':')
|
||||
if (lastAtSearchIndex != -1) {
|
||||
val searchText = textPane.text.substring(lastAtSearchIndex + 1)
|
||||
if (searchText.isNotEmpty()) {
|
||||
suggestionsPopupManager.updateSuggestions(searchText)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
suggestionsPopupManager.hidePopup()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +1,38 @@
|
|||
package ee.carlrobert.codegpt.ui.textarea
|
||||
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.fileTypes.FileTypeManager
|
||||
import com.intellij.ui.JBColor
|
||||
import com.intellij.ui.components.JBList
|
||||
import com.intellij.ui.dsl.builder.AlignX
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import com.intellij.util.ui.JBUI
|
||||
import java.awt.Component
|
||||
import java.awt.Dimension
|
||||
import java.awt.KeyboardFocusManager
|
||||
import java.awt.event.KeyAdapter
|
||||
import java.awt.event.KeyEvent
|
||||
import java.awt.event.MouseAdapter
|
||||
import java.awt.event.MouseEvent
|
||||
import javax.swing.*
|
||||
import javax.swing.DefaultListModel
|
||||
import javax.swing.ListSelectionModel
|
||||
|
||||
class SuggestionList(
|
||||
listModel: DefaultListModel<SuggestionItem>,
|
||||
private val textPane: CustomTextPane,
|
||||
private val onSelected: (SuggestionItem) -> Unit
|
||||
) : JBList<SuggestionItem>(listModel) {
|
||||
|
||||
init {
|
||||
setupUI()
|
||||
setupKeyboardFocusManager()
|
||||
setupKeyListener()
|
||||
setupMouseListener()
|
||||
setupMouseMotionListener()
|
||||
}
|
||||
|
||||
private fun setupUI() {
|
||||
border = JBUI.Borders.empty()
|
||||
preferredSize = Dimension(480, (30 * 6))
|
||||
selectionMode = ListSelectionModel.SINGLE_SELECTION
|
||||
cellRenderer = SuggestionsListCellRenderer()
|
||||
cellRenderer = SuggestionListCellRenderer(textPane)
|
||||
}
|
||||
|
||||
private fun setupKeyboardFocusManager() {
|
||||
KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher { e ->
|
||||
if (e.keyCode == KeyEvent.VK_TAB && e.id == KeyEvent.KEY_PRESSED && isFocusOwner) {
|
||||
if (isTabKeyPressed(e) && isFocusOwner) {
|
||||
selectNext()
|
||||
e.consume()
|
||||
true
|
||||
|
|
@ -35,22 +40,33 @@ class SuggestionList(
|
|||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isTabKeyPressed(e: KeyEvent) =
|
||||
e.keyCode == KeyEvent.VK_TAB && e.id == KeyEvent.KEY_PRESSED
|
||||
|
||||
private fun setupKeyListener() {
|
||||
addKeyListener(object : KeyAdapter() {
|
||||
override fun keyReleased(e: KeyEvent) {
|
||||
when (e.keyCode) {
|
||||
KeyEvent.VK_ENTER -> {
|
||||
onSelected(listModel.get(selectedIndex))
|
||||
e.consume()
|
||||
}
|
||||
if (e.keyCode == KeyEvent.VK_ENTER) {
|
||||
handleEnterKey()
|
||||
e.consume()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun handleEnterKey() {
|
||||
val item = model.getElementAt(selectedIndex)
|
||||
if (item is SuggestionItem.ActionItem && item.action.enabled || item !is SuggestionItem.ActionItem) {
|
||||
onSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupMouseListener() {
|
||||
addMouseListener(object : MouseAdapter() {
|
||||
override fun mouseClicked(e: MouseEvent) {
|
||||
val index = locationToIndex(e.point)
|
||||
if (index >= 0) {
|
||||
onSelected(listModel.getElementAt(index))
|
||||
}
|
||||
handleMouseClick(e)
|
||||
}
|
||||
|
||||
override fun mouseExited(e: MouseEvent) {
|
||||
|
|
@ -58,6 +74,20 @@ class SuggestionList(
|
|||
repaint()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun handleMouseClick(e: MouseEvent) {
|
||||
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) {
|
||||
onSelected(item)
|
||||
}
|
||||
e.consume()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupMouseMotionListener() {
|
||||
addMouseMotionListener(object : MouseAdapter() {
|
||||
override fun mouseMoved(e: MouseEvent) {
|
||||
val index = locationToIndex(e.point)
|
||||
|
|
@ -74,105 +104,4 @@ class SuggestionList(
|
|||
selectedIndex = newIndex
|
||||
ensureIndexIsVisible(newIndex)
|
||||
}
|
||||
}
|
||||
|
||||
private class SuggestionsListCellRenderer : 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 SuggestionItem) {
|
||||
renderSuggestionItem(component, value, list, index, isSelected, cellHasFocus)
|
||||
} else {
|
||||
component
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderSuggestionItem(
|
||||
component: JLabel,
|
||||
value: SuggestionItem,
|
||||
list: JList<*>?,
|
||||
index: Int,
|
||||
isSelected: Boolean,
|
||||
cellHasFocus: Boolean
|
||||
): JPanel = when (value) {
|
||||
is SuggestionItem.FileItem -> renderFileItem(component, value)
|
||||
is SuggestionItem.ActionItem -> renderActionItem(component, value)
|
||||
}.apply {
|
||||
setupPanelProperties(list, index, isSelected, cellHasFocus)
|
||||
}
|
||||
|
||||
private fun renderFileItem(component: JLabel, value: SuggestionItem.FileItem): JPanel {
|
||||
val file = value.file
|
||||
component.apply {
|
||||
text = file.name
|
||||
icon = when {
|
||||
file.isDirectory -> AllIcons.Nodes.Folder
|
||||
else -> FileTypeManager.getInstance().getFileTypeByFileName(file.name).icon
|
||||
}
|
||||
iconTextGap = 4
|
||||
}
|
||||
|
||||
return panel {
|
||||
row {
|
||||
cell(component)
|
||||
text(truncatePath(480 - component.width - 28, file.path))
|
||||
.align(AlignX.RIGHT)
|
||||
.applyToComponent {
|
||||
font = JBUI.Fonts.smallFont()
|
||||
foreground = JBColor.gray
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderActionItem(component: JLabel, value: SuggestionItem.ActionItem): JPanel {
|
||||
component.apply {
|
||||
text = value.action.displayName
|
||||
icon = value.action.icon
|
||||
iconTextGap = 4
|
||||
}
|
||||
return panel {
|
||||
row {
|
||||
cell(component)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun JPanel.setupPanelProperties(
|
||||
list: JList<*>?,
|
||||
index: Int,
|
||||
isSelected: Boolean,
|
||||
cellHasFocus: Boolean
|
||||
) {
|
||||
preferredSize = Dimension(preferredSize.width, 30)
|
||||
border = JBUI.Borders.empty(0, 4, 0, 4)
|
||||
|
||||
val isHovered = list?.getClientProperty("hoveredIndex") == index
|
||||
if (isHovered || isSelected || cellHasFocus) {
|
||||
background = UIManager.getColor("List.selectionBackground")
|
||||
foreground = UIManager.getColor("List.selectionForeground")
|
||||
}
|
||||
}
|
||||
|
||||
private fun truncatePath(maxWidth: Int, fullPath: String): String {
|
||||
val fontMetrics = getFontMetrics(JBUI.Fonts.smallFont())
|
||||
|
||||
if (fontMetrics.stringWidth(fullPath) <= maxWidth) {
|
||||
return fullPath
|
||||
}
|
||||
|
||||
val ellipsis = "..."
|
||||
var truncatedPath = fullPath
|
||||
while (truncatedPath.isNotEmpty() && fontMetrics.stringWidth(ellipsis + truncatedPath) > maxWidth) {
|
||||
truncatedPath = truncatedPath.substring(1)
|
||||
}
|
||||
return ellipsis + truncatedPath
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,188 @@
|
|||
package ee.carlrobert.codegpt.ui.textarea
|
||||
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.fileTypes.FileTypeManager
|
||||
import com.intellij.ui.ColorUtil
|
||||
import com.intellij.ui.JBColor
|
||||
import com.intellij.ui.dsl.builder.AlignX
|
||||
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.persona.PersonaSettings
|
||||
import java.awt.Component
|
||||
import java.awt.Dimension
|
||||
import javax.swing.*
|
||||
|
||||
class SuggestionListCellRenderer(
|
||||
private val textPane: CustomTextPane
|
||||
) : 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 SuggestionItem) {
|
||||
renderSuggestionItem(component, value, list, index, isSelected, cellHasFocus)
|
||||
} else {
|
||||
component
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderSuggestionItem(
|
||||
component: JLabel,
|
||||
value: SuggestionItem,
|
||||
list: JList<*>?,
|
||||
index: Int,
|
||||
isSelected: Boolean,
|
||||
cellHasFocus: Boolean
|
||||
): JPanel = when (value) {
|
||||
is SuggestionItem.FileItem -> renderFileItem(component, value)
|
||||
is SuggestionItem.FolderItem -> renderFolderItem(component, value)
|
||||
is SuggestionItem.ActionItem -> renderActionItem(component, value)
|
||||
is SuggestionItem.PersonaItem -> renderPersonaItem(component, value)
|
||||
}.apply {
|
||||
setupPanelProperties(list, index, isSelected, cellHasFocus)
|
||||
}
|
||||
|
||||
private fun renderFileItem(component: JLabel, item: SuggestionItem.FileItem): JPanel {
|
||||
val icon = when {
|
||||
item.file.isDirectory -> AllIcons.Nodes.Folder
|
||||
else -> service<FileTypeManager>().getFileTypeByFileName(item.file.name).icon
|
||||
}
|
||||
return createDefaultPanel(component, icon, item.file.name, item.file.path)
|
||||
}
|
||||
|
||||
private fun renderFolderItem(component: JLabel, item: SuggestionItem.FolderItem): JPanel {
|
||||
return createDefaultPanel(
|
||||
component,
|
||||
AllIcons.Nodes.Folder,
|
||||
item.folder.name,
|
||||
item.folder.path
|
||||
)
|
||||
}
|
||||
|
||||
private fun renderActionItem(component: JLabel, item: SuggestionItem.ActionItem): JPanel {
|
||||
val description = if (item.action == DefaultAction.PERSONAS)
|
||||
service<PersonaSettings>().state.selectedPersona.name
|
||||
else null
|
||||
|
||||
return createDefaultPanel(
|
||||
component.apply {
|
||||
disabledIcon = item.action.icon
|
||||
isEnabled = item.action.enabled
|
||||
},
|
||||
item.action.icon,
|
||||
item.action.displayName,
|
||||
description
|
||||
)
|
||||
}
|
||||
|
||||
private fun renderPersonaItem(component: JLabel, item: SuggestionItem.PersonaItem): JPanel {
|
||||
return createDefaultPanel(
|
||||
component,
|
||||
AllIcons.General.User,
|
||||
item.personaDetails.name,
|
||||
item.personaDetails.instructions,
|
||||
)
|
||||
}
|
||||
|
||||
private fun getSearchText(text: String): String? {
|
||||
val lastAtIndex = text.lastIndexOf('@')
|
||||
if (lastAtIndex == -1) return null
|
||||
|
||||
val lastColonIndex = text.lastIndexOf(':')
|
||||
if (lastColonIndex == -1) return null
|
||||
|
||||
return text.substring(lastColonIndex + 1).takeIf { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
private fun generateHighlightedHtml(title: String, searchText: String): String {
|
||||
val searchIndex = title.indexOf(searchText, ignoreCase = true)
|
||||
if (searchIndex == -1) return title
|
||||
|
||||
val prefix = title.substring(0, searchIndex)
|
||||
val highlight = title.substring(
|
||||
searchIndex,
|
||||
(searchIndex + searchText.length).coerceAtMost(title.length)
|
||||
)
|
||||
val suffix = title.substring((searchIndex + searchText.length).coerceAtMost(title.length))
|
||||
|
||||
val foregroundHex = ColorUtil.toHex(GotItTooltip.codeForeground(true))
|
||||
val backgroundHex = ColorUtil.toHex(GotItTooltip.codeBackground(true))
|
||||
|
||||
return "<html>$prefix<span style=\"color: $foregroundHex;background-color: $backgroundHex;\">$highlight</span>$suffix</html>"
|
||||
}
|
||||
|
||||
private fun createDefaultPanel(
|
||||
label: JLabel,
|
||||
labelIcon: Icon,
|
||||
title: String,
|
||||
description: String? = null
|
||||
): JPanel {
|
||||
val searchText = getSearchText(textPane.text)
|
||||
label.apply {
|
||||
icon = labelIcon
|
||||
iconTextGap = 4
|
||||
text = if (searchText != null) {
|
||||
generateHighlightedHtml(title, searchText)
|
||||
} else {
|
||||
title
|
||||
}
|
||||
}
|
||||
|
||||
return panel {
|
||||
row {
|
||||
cell(label)
|
||||
if (description != null) {
|
||||
text(description.truncate(480 - label.width - 28, false))
|
||||
.customize(UnscaledGaps(left = 8))
|
||||
.align(AlignX.RIGHT)
|
||||
.applyToComponent {
|
||||
font = JBUI.Fonts.smallFont()
|
||||
foreground = JBColor.gray
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun JPanel.setupPanelProperties(
|
||||
list: JList<*>?,
|
||||
index: Int,
|
||||
isSelected: Boolean,
|
||||
cellHasFocus: Boolean
|
||||
) {
|
||||
preferredSize = Dimension(480, 30)
|
||||
border = JBUI.Borders.empty(0, 4, 0, 4)
|
||||
|
||||
val isHovered = list?.getClientProperty("hoveredIndex") == index
|
||||
if (isHovered || isSelected || cellHasFocus) {
|
||||
background = UIManager.getColor("List.selectionBackground")
|
||||
foreground = UIManager.getColor("List.selectionForeground")
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.truncate(maxWidth: Int, fromEnd: Boolean = true): String {
|
||||
val fontMetrics = getFontMetrics(JBUI.Fonts.smallFont())
|
||||
if (fontMetrics.stringWidth(this) <= maxWidth) return this
|
||||
|
||||
val ellipsis = "..."
|
||||
var truncated = this
|
||||
while (fontMetrics.stringWidth(ellipsis + truncated) > maxWidth && truncated.isNotEmpty()) {
|
||||
truncated = if (fromEnd) {
|
||||
truncated.drop(1)
|
||||
} else {
|
||||
truncated.dropLast(1)
|
||||
}
|
||||
}
|
||||
return ellipsis + truncated
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
package ee.carlrobert.codegpt.ui.textarea
|
||||
|
||||
import com.intellij.openapi.application.readAction
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.ProjectFileIndex
|
||||
import ee.carlrobert.codegpt.util.ResourceUtil
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.File
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import javax.swing.DefaultListModel
|
||||
import kotlin.io.path.absolutePathString
|
||||
import kotlin.io.path.isDirectory
|
||||
import kotlin.io.path.name
|
||||
|
||||
interface SuggestionUpdateStrategy {
|
||||
fun populateSuggestions(
|
||||
project: Project,
|
||||
listModel: DefaultListModel<SuggestionItem>,
|
||||
)
|
||||
|
||||
fun updateSuggestions(
|
||||
project: Project,
|
||||
listModel: DefaultListModel<SuggestionItem>,
|
||||
searchText: String,
|
||||
)
|
||||
}
|
||||
|
||||
class FileSuggestionActionStrategy : SuggestionUpdateStrategy {
|
||||
override fun populateSuggestions(
|
||||
project: Project,
|
||||
listModel: DefaultListModel<SuggestionItem>,
|
||||
) {
|
||||
val projectFileIndex = project.service<ProjectFileIndex>()
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val openFilePaths = project.service<FileEditorManager>().openFiles
|
||||
.filter { readAction { projectFileIndex.isInContent(it) } }
|
||||
.take(10)
|
||||
.map { file -> file.path }
|
||||
listModel.clear()
|
||||
listModel.addAll(openFilePaths.map { SuggestionItem.FileItem(File(it)) })
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateSuggestions(
|
||||
project: Project,
|
||||
listModel: DefaultListModel<SuggestionItem>,
|
||||
searchText: String,
|
||||
) {
|
||||
val filePaths = project.service<FileSearchService>().searchFiles(searchText).take(10)
|
||||
listModel.clear()
|
||||
listModel.addAll(filePaths.map { SuggestionItem.FileItem(File(it)) })
|
||||
}
|
||||
}
|
||||
|
||||
class FolderSuggestionActionStrategy : SuggestionUpdateStrategy {
|
||||
private val projectFoldersCache = mutableMapOf<Project, List<String>>()
|
||||
|
||||
override fun populateSuggestions(
|
||||
project: Project,
|
||||
listModel: DefaultListModel<SuggestionItem>
|
||||
) {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val folderPaths = getProjectFolders(project)
|
||||
.take(10)
|
||||
.map { SuggestionItem.FolderItem(Path.of(it).toFile()) }
|
||||
listModel.clear()
|
||||
listModel.addAll(folderPaths)
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateSuggestions(
|
||||
project: Project,
|
||||
listModel: DefaultListModel<SuggestionItem>,
|
||||
searchText: String
|
||||
) {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val filteredFolders = getProjectFolders(project)
|
||||
.filter { it.contains(searchText, ignoreCase = true) }
|
||||
.take(10)
|
||||
.map { SuggestionItem.FolderItem(Path.of(it).toFile()) }
|
||||
listModel.clear()
|
||||
listModel.addAll(filteredFolders)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun getProjectFolders(project: Project): List<String> {
|
||||
return projectFoldersCache.getOrPut(project) {
|
||||
findProjectFolders(project)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun findProjectFolders(project: Project): List<String> {
|
||||
val projectRoot = project.basePath?.let { Path.of(it) } ?: return emptyList()
|
||||
return withContext(Dispatchers.IO) {
|
||||
val uniqueFolders = mutableSetOf<String>()
|
||||
Files.walk(projectRoot)
|
||||
.filter { it.isDirectory() && !it.name.startsWith(".") }
|
||||
.forEach { folder ->
|
||||
val folderPath = folder.absolutePathString()
|
||||
if (uniqueFolders.none { it.startsWith(folderPath) }) {
|
||||
uniqueFolders.removeAll { it.startsWith(folderPath) }
|
||||
uniqueFolders.add(folderPath)
|
||||
}
|
||||
}
|
||||
uniqueFolders.toList()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class PersonaSuggestionActionStrategy : SuggestionUpdateStrategy {
|
||||
|
||||
override fun populateSuggestions(
|
||||
project: Project,
|
||||
listModel: DefaultListModel<SuggestionItem>,
|
||||
) {
|
||||
listModel.clear()
|
||||
listModel.addAll(ResourceUtil.getFilteredPersonaSuggestions(null))
|
||||
}
|
||||
|
||||
override fun updateSuggestions(
|
||||
project: Project,
|
||||
listModel: DefaultListModel<SuggestionItem>,
|
||||
searchText: String,
|
||||
) {
|
||||
listModel.clear()
|
||||
listModel.addAll(ResourceUtil.getFilteredPersonaSuggestions { it.name.contains(searchText, true) })
|
||||
}
|
||||
}
|
||||
|
||||
class CreatePersonaActionStrategy : SuggestionUpdateStrategy {
|
||||
override fun populateSuggestions(
|
||||
project: Project,
|
||||
listModel: DefaultListModel<SuggestionItem>,
|
||||
) {
|
||||
}
|
||||
|
||||
override fun updateSuggestions(
|
||||
project: Project,
|
||||
listModel: DefaultListModel<SuggestionItem>,
|
||||
searchText: String,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
||||
class DefaultSuggestionActionStrategy : SuggestionUpdateStrategy {
|
||||
override fun populateSuggestions(
|
||||
project: Project,
|
||||
listModel: DefaultListModel<SuggestionItem>,
|
||||
) {
|
||||
}
|
||||
|
||||
override fun updateSuggestions(
|
||||
project: Project,
|
||||
listModel: DefaultListModel<SuggestionItem>,
|
||||
searchText: String,
|
||||
) {
|
||||
}
|
||||
}
|
||||
|
|
@ -1,62 +1,96 @@
|
|||
package ee.carlrobert.codegpt.ui.textarea
|
||||
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.application.readAction
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.options.ShowSettingsUtil
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.roots.ProjectFileIndex
|
||||
import com.intellij.openapi.ui.popup.JBPopup
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||
import com.intellij.openapi.vfs.VfsUtilCore
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.openapi.vfs.VirtualFileVisitor
|
||||
import com.intellij.ui.components.JBScrollPane
|
||||
import com.intellij.util.ui.JBUI
|
||||
import com.intellij.vcsUtil.showAbove
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import ee.carlrobert.codegpt.settings.persona.PersonaDetails
|
||||
import ee.carlrobert.codegpt.settings.persona.PersonaSettings
|
||||
import ee.carlrobert.codegpt.settings.persona.PersonasConfigurable
|
||||
import java.awt.Dimension
|
||||
import java.awt.Point
|
||||
import java.io.File
|
||||
import java.nio.file.Paths
|
||||
import javax.swing.DefaultListModel
|
||||
import javax.swing.Icon
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.ScrollPaneConstants
|
||||
import javax.swing.event.ListDataEvent
|
||||
import javax.swing.event.ListDataListener
|
||||
|
||||
enum class DefaultAction(val displayName: String, val icon: Icon) {
|
||||
ATTACH_IMAGE("Attach image", AllIcons.FileTypes.Image),
|
||||
SEARCH_WEB("Search web", AllIcons.General.Web),
|
||||
enum class DefaultAction(
|
||||
val displayName: String,
|
||||
val code: String,
|
||||
val icon: Icon,
|
||||
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),
|
||||
CREATE_NEW_PERSONA("Create new persona", "", AllIcons.General.Add),
|
||||
}
|
||||
|
||||
sealed class SuggestionItem {
|
||||
data class FileItem(val file: File) : SuggestionItem()
|
||||
data class FolderItem(val folder: File) : SuggestionItem()
|
||||
data class ActionItem(val action: DefaultAction) : SuggestionItem()
|
||||
data class PersonaItem(val personaDetails: PersonaDetails) : SuggestionItem()
|
||||
}
|
||||
|
||||
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),
|
||||
)
|
||||
|
||||
class SuggestionsPopupManager(
|
||||
private val project: Project,
|
||||
private val onSelected: (filePath: String) -> Unit
|
||||
private val textPane: CustomTextPane,
|
||||
) {
|
||||
|
||||
private var currentActionStrategy: SuggestionUpdateStrategy = DefaultSuggestionActionStrategy()
|
||||
private val appliedActions: MutableList<SuggestionItem.ActionItem> = mutableListOf()
|
||||
private var popup: JBPopup? = null
|
||||
private val listModel = DefaultListModel<SuggestionItem>()
|
||||
private val list = SuggestionList(listModel) {
|
||||
if (it is SuggestionItem.FileItem) {
|
||||
onSelected(it.file.path)
|
||||
} else if (it is SuggestionItem.ActionItem) {
|
||||
when (it.action) {
|
||||
DefaultAction.ATTACH_IMAGE -> {} // todo
|
||||
DefaultAction.SEARCH_WEB -> {} // todo
|
||||
}
|
||||
private var originalLocation: Point? = null
|
||||
private val listModel = DefaultListModel<SuggestionItem>().apply {
|
||||
addListDataListener(object : ListDataListener {
|
||||
override fun intervalAdded(e: ListDataEvent) = adjustPopupSize()
|
||||
override fun intervalRemoved(e: ListDataEvent) {}
|
||||
override fun contentsChanged(e: ListDataEvent) {}
|
||||
})
|
||||
}
|
||||
private val list = SuggestionList(listModel, textPane) {
|
||||
when (it) {
|
||||
is SuggestionItem.ActionItem -> handleActionSelection(it)
|
||||
is SuggestionItem.FileItem -> handleFileSelection(it.file.path)
|
||||
is SuggestionItem.FolderItem -> handleFolderSelection(it.folder.path)
|
||||
is SuggestionItem.PersonaItem -> handlePersonaSelection(it.personaDetails)
|
||||
}
|
||||
}
|
||||
private val scrollPane: JBScrollPane = JBScrollPane(list).apply {
|
||||
border = JBUI.Borders.empty()
|
||||
verticalScrollBarPolicy = ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED
|
||||
horizontalScrollBarPolicy = ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER
|
||||
}
|
||||
|
||||
fun showPopup(component: JComponent) {
|
||||
popup = createPopup(component)
|
||||
popup?.showAbove(component)
|
||||
|
||||
val projectFileIndex = project.service<ProjectFileIndex>()
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val openFilePaths = project.service<FileEditorManager>().openFiles
|
||||
.filter { readAction { projectFileIndex.isInContent(it) } }
|
||||
.take(6)
|
||||
.map { it.path }
|
||||
updateSuggestions(openFilePaths)
|
||||
}
|
||||
originalLocation = component.locationOnScreen
|
||||
reset(true)
|
||||
}
|
||||
|
||||
fun hidePopup() {
|
||||
|
|
@ -67,11 +101,6 @@ class SuggestionsPopupManager(
|
|||
return popup?.isVisible ?: false
|
||||
}
|
||||
|
||||
fun updateSuggestions(filePaths: List<String>) {
|
||||
listModel.clear()
|
||||
listModel.addAll(filePaths.map { SuggestionItem.FileItem(File(it)) })
|
||||
}
|
||||
|
||||
fun requestFocus() {
|
||||
list.requestFocus()
|
||||
}
|
||||
|
|
@ -80,16 +109,119 @@ class SuggestionsPopupManager(
|
|||
list.selectNext()
|
||||
}
|
||||
|
||||
private fun createPopup(preferableFocusComponent: JComponent? = null): JBPopup =
|
||||
fun updateSuggestions(searchText: String) {
|
||||
currentActionStrategy.updateSuggestions(project, listModel, searchText)
|
||||
}
|
||||
|
||||
fun reset(clearPrevious: Boolean = true) {
|
||||
if (clearPrevious) {
|
||||
listModel.clear()
|
||||
}
|
||||
listModel.addAll(DEFAULT_ACTIONS)
|
||||
popup?.content?.revalidate()
|
||||
popup?.content?.repaint()
|
||||
}
|
||||
|
||||
private fun handleActionSelection(item: SuggestionItem.ActionItem) {
|
||||
if (item.action == DefaultAction.CREATE_NEW_PERSONA) {
|
||||
hidePopup()
|
||||
service<ShowSettingsUtil>().showSettingsDialog(
|
||||
project,
|
||||
PersonasConfigurable::class.java
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
appliedActions.add(item)
|
||||
currentActionStrategy = when (item.action) {
|
||||
DefaultAction.FILES -> {
|
||||
FileSuggestionActionStrategy()
|
||||
}
|
||||
|
||||
DefaultAction.FOLDERS -> {
|
||||
FolderSuggestionActionStrategy()
|
||||
}
|
||||
|
||||
DefaultAction.PERSONAS -> {
|
||||
PersonaSuggestionActionStrategy()
|
||||
}
|
||||
|
||||
else -> {
|
||||
DefaultSuggestionActionStrategy()
|
||||
}
|
||||
}
|
||||
currentActionStrategy.populateSuggestions(project, listModel)
|
||||
textPane.appendHighlightedText(item.action.code, withWhitespace = false)
|
||||
textPane.requestFocus()
|
||||
}
|
||||
|
||||
private fun handleFileSelection(filePath: String) {
|
||||
val selectedFile = service<VirtualFileManager>().findFileByNioPath(Paths.get(filePath))
|
||||
selectedFile?.let { file ->
|
||||
textPane.appendHighlightedText(file.name, ':')
|
||||
project.service<FileSearchService>().addFileToSession(file)
|
||||
}
|
||||
hidePopup()
|
||||
}
|
||||
|
||||
private fun handleFolderSelection(folderPath: String) {
|
||||
textPane.appendHighlightedText(folderPath, ':')
|
||||
|
||||
val folder = service<VirtualFileManager>().findFileByNioPath(Paths.get(folderPath))
|
||||
if (folder != null) {
|
||||
VfsUtilCore.visitChildrenRecursively(folder, object : VirtualFileVisitor<Any>() {
|
||||
override fun visitFile(file: VirtualFile): Boolean {
|
||||
if (!file.isDirectory) {
|
||||
project.service<FileSearchService>().addFileToSession(file)
|
||||
}
|
||||
return true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
hidePopup()
|
||||
}
|
||||
|
||||
private fun handlePersonaSelection(personaDetails: PersonaDetails) {
|
||||
service<PersonaSettings>().state.selectedPersona.apply {
|
||||
id = personaDetails.id
|
||||
name = personaDetails.name
|
||||
instructions = personaDetails.instructions
|
||||
}
|
||||
textPane.appendHighlightedText(personaDetails.name, ':')
|
||||
hidePopup()
|
||||
}
|
||||
|
||||
private fun adjustPopupSize() {
|
||||
val maxVisibleRows = 15
|
||||
val newRowCount = minOf(listModel.size(), maxVisibleRows)
|
||||
list.setVisibleRowCount(newRowCount)
|
||||
list.revalidate()
|
||||
list.repaint()
|
||||
|
||||
popup?.size = list.preferredSize
|
||||
|
||||
originalLocation?.let { original ->
|
||||
val newY = original.y - list.preferredSize.height
|
||||
popup?.setLocation(Point(original.x, maxOf(newY, 0)))
|
||||
}
|
||||
}
|
||||
|
||||
private fun createPopup(
|
||||
preferableFocusComponent: JComponent? = null,
|
||||
): JBPopup =
|
||||
service<JBPopupFactory>()
|
||||
.createComponentPopupBuilder(list, preferableFocusComponent)
|
||||
.createComponentPopupBuilder(scrollPane, preferableFocusComponent)
|
||||
.setMovable(true)
|
||||
.setCancelOnClickOutside(true)
|
||||
.setCancelOnClickOutside(false)
|
||||
.setCancelOnWindowDeactivation(false)
|
||||
.setRequestFocus(true)
|
||||
.setMinSize(Dimension(480, 30))
|
||||
.setCancelCallback {
|
||||
listModel.removeAllElements()
|
||||
originalLocation = null
|
||||
currentActionStrategy = DefaultSuggestionActionStrategy()
|
||||
true
|
||||
}
|
||||
.setResizable(true)
|
||||
.createPopup()
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import com.intellij.openapi.actionSystem.AnActionEvent
|
|||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.observable.properties.AtomicBooleanProperty
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.vfs.VirtualFileManager
|
||||
import com.intellij.ui.components.AnActionLink
|
||||
import com.intellij.ui.dsl.builder.AlignX
|
||||
import com.intellij.ui.dsl.builder.RightGap
|
||||
|
|
@ -22,16 +21,8 @@ import ee.carlrobert.codegpt.settings.GeneralSettings
|
|||
import ee.carlrobert.codegpt.toolwindow.chat.ChatToolWindowContentManager
|
||||
import ee.carlrobert.codegpt.toolwindow.chat.ui.textarea.ModelComboBoxAction
|
||||
import ee.carlrobert.codegpt.ui.IconActionButton
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import java.awt.*
|
||||
import java.awt.event.KeyAdapter
|
||||
import java.awt.event.KeyEvent
|
||||
import java.nio.file.Paths
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.text.StyleContext
|
||||
import javax.swing.text.StyledDocument
|
||||
|
||||
class UserInputPanel(
|
||||
private val project: Project,
|
||||
|
|
@ -39,12 +30,9 @@ class UserInputPanel(
|
|||
private val onStop: () -> Unit
|
||||
) : JPanel(BorderLayout()) {
|
||||
|
||||
private val suggestionsPopupManager = SuggestionsPopupManager(project) {
|
||||
handleFileSelection(it)
|
||||
}
|
||||
private val textPane = CustomTextPane { handleSubmit() }.apply {
|
||||
addKeyListener(CustomTextPaneKeyAdapter())
|
||||
}
|
||||
private val textPane = CustomTextPane { handleSubmit() }
|
||||
.apply { addKeyListener(CustomTextPaneKeyAdapter(project, this)) }
|
||||
|
||||
private val submitButton = IconActionButton(
|
||||
object : AnAction(
|
||||
CodeGPTBundle.get("smartTextPane.submitButton.title"),
|
||||
|
|
@ -78,6 +66,46 @@ class UserInputPanel(
|
|||
add(getFooter(), BorderLayout.SOUTH)
|
||||
}
|
||||
|
||||
fun setSubmitEnabled(enabled: Boolean) {
|
||||
submitButton.isEnabled = enabled
|
||||
stopButton.isEnabled = !enabled
|
||||
}
|
||||
|
||||
override fun requestFocus() {
|
||||
textPane.requestFocus()
|
||||
textPane.requestFocusInWindow()
|
||||
}
|
||||
|
||||
override fun paintComponent(g: Graphics) {
|
||||
val g2 = g.create() as Graphics2D
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
|
||||
g2.color = background
|
||||
g2.fillRoundRect(0, 0, width - 1, height - 1, 16, 16)
|
||||
super.paintComponent(g)
|
||||
g2.dispose()
|
||||
}
|
||||
|
||||
override fun paintBorder(g: Graphics) {
|
||||
val g2 = g.create() as Graphics2D
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
|
||||
g2.color = JBUI.CurrentTheme.ActionButton.focusedBorder()
|
||||
if (textPane.isFocusOwner) {
|
||||
g2.stroke = BasicStroke(1.5F)
|
||||
}
|
||||
g2.drawRoundRect(0, 0, width - 1, height - 1, 16, 16)
|
||||
g2.dispose()
|
||||
}
|
||||
|
||||
override fun getInsets(): Insets = JBUI.insets(4)
|
||||
|
||||
private fun handleSubmit() {
|
||||
val text = textPane.text.trim()
|
||||
if (text.isNotEmpty()) {
|
||||
onSubmit(text)
|
||||
textPane.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFooter(): JPanel {
|
||||
val attachImageLink = AnActionLink(CodeGPTBundle.get("shared.image"), AttachImageAction())
|
||||
.apply {
|
||||
|
|
@ -115,118 +143,4 @@ class UserInputPanel(
|
|||
private fun isImageActionSupported(): Boolean {
|
||||
return service<GeneralSettings>().state.selectedService.isImageActionSupported
|
||||
}
|
||||
|
||||
fun setSubmitEnabled(enabled: Boolean) {
|
||||
submitButton.isEnabled = enabled
|
||||
stopButton.isEnabled = !enabled
|
||||
}
|
||||
|
||||
override fun requestFocus() {
|
||||
textPane.requestFocus()
|
||||
textPane.requestFocusInWindow()
|
||||
}
|
||||
|
||||
override fun paintComponent(g: Graphics) {
|
||||
val g2 = g.create() as Graphics2D
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
|
||||
g2.color = background
|
||||
g2.fillRoundRect(0, 0, width - 1, height - 1, 16, 16)
|
||||
super.paintComponent(g)
|
||||
g2.dispose()
|
||||
}
|
||||
|
||||
override fun paintBorder(g: Graphics) {
|
||||
val g2 = g.create() as Graphics2D
|
||||
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON)
|
||||
g2.color = JBUI.CurrentTheme.ActionButton.focusedBorder()
|
||||
if (textPane.isFocusOwner) {
|
||||
g2.stroke = BasicStroke(1.5F)
|
||||
}
|
||||
g2.drawRoundRect(0, 0, width - 1, height - 1, 16, 16)
|
||||
g2.dispose()
|
||||
}
|
||||
|
||||
override fun getInsets(): Insets = JBUI.insets(4)
|
||||
|
||||
private fun updateSuggestions() {
|
||||
CoroutineScope(Dispatchers.Default).launch {
|
||||
val lastAtIndex = textPane.text.lastIndexOf('@')
|
||||
if (lastAtIndex != -1) {
|
||||
val searchText = textPane.text.substring(lastAtIndex + 1)
|
||||
if (searchText.isNotEmpty()) {
|
||||
val filePaths = project.service<FileSearchService>().searchFiles(searchText)
|
||||
suggestionsPopupManager.updateSuggestions(filePaths)
|
||||
}
|
||||
} else {
|
||||
suggestionsPopupManager.hidePopup()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleSubmit() {
|
||||
val text = textPane.text.trim()
|
||||
if (text.isNotEmpty()) {
|
||||
onSubmit(text)
|
||||
textPane.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleFileSelection(filePath: String) {
|
||||
val selectedFile = service<VirtualFileManager>().findFileByNioPath(Paths.get(filePath))
|
||||
selectedFile?.let { file ->
|
||||
textPane.highlightText(file.name)
|
||||
project.service<FileSearchService>().addFileToSession(file)
|
||||
}
|
||||
suggestionsPopupManager.hidePopup()
|
||||
}
|
||||
|
||||
inner class CustomTextPaneKeyAdapter : KeyAdapter() {
|
||||
private val defaultStyle =
|
||||
StyleContext.getDefaultStyleContext().getStyle(StyleContext.DEFAULT_STYLE)
|
||||
|
||||
override fun keyReleased(e: KeyEvent) {
|
||||
if (text.isEmpty()) {
|
||||
project.service<FileSearchService>().removeFilesFromSession()
|
||||
}
|
||||
|
||||
// todo
|
||||
if (!text.contains('@')) {
|
||||
suggestionsPopupManager.hidePopup()
|
||||
return
|
||||
}
|
||||
|
||||
when (e.keyCode) {
|
||||
KeyEvent.VK_UP, KeyEvent.VK_DOWN -> {
|
||||
suggestionsPopupManager.requestFocus()
|
||||
suggestionsPopupManager.selectNext()
|
||||
e.consume()
|
||||
}
|
||||
|
||||
else -> {
|
||||
if (suggestionsPopupManager.isPopupVisible()) {
|
||||
updateSuggestions()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun keyTyped(e: KeyEvent) {
|
||||
val popupVisible = suggestionsPopupManager.isPopupVisible()
|
||||
if (e.keyChar == '@' && !popupVisible) {
|
||||
suggestionsPopupManager.showPopup(textPane)
|
||||
return
|
||||
} else if (e.keyChar == '\t') {
|
||||
suggestionsPopupManager.requestFocus()
|
||||
suggestionsPopupManager.selectNext()
|
||||
return
|
||||
} else if (popupVisible) {
|
||||
updateSuggestions()
|
||||
}
|
||||
|
||||
val doc = textPane.document as StyledDocument
|
||||
if (textPane.caretPosition >= 0) {
|
||||
doc.setCharacterAttributes(textPane.caretPosition, 1, defaultStyle, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
29
src/main/kotlin/ee/carlrobert/codegpt/util/ResourceUtil.kt
Normal file
29
src/main/kotlin/ee/carlrobert/codegpt/util/ResourceUtil.kt
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package ee.carlrobert.codegpt.util
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import ee.carlrobert.codegpt.settings.persona.PersonaDetails
|
||||
import ee.carlrobert.codegpt.ui.textarea.DefaultAction
|
||||
import ee.carlrobert.codegpt.ui.textarea.SuggestionItem
|
||||
import ee.carlrobert.codegpt.util.file.FileUtil.getResourceContent
|
||||
|
||||
object ResourceUtil {
|
||||
|
||||
fun getFilteredPersonaSuggestions(
|
||||
filterPredicate: ((PersonaDetails) -> Boolean)? = null
|
||||
): List<SuggestionItem> {
|
||||
var personaDetails = getFilteredPersonaSuggestions()
|
||||
if (filterPredicate != null) {
|
||||
personaDetails = personaDetails.filter(filterPredicate).toMutableList()
|
||||
}
|
||||
return personaDetails
|
||||
.map { SuggestionItem.PersonaItem(it) }
|
||||
.take(10) + listOf(SuggestionItem.ActionItem(DefaultAction.CREATE_NEW_PERSONA))
|
||||
}
|
||||
|
||||
fun getFilteredPersonaSuggestions(): MutableList<PersonaDetails> {
|
||||
return ObjectMapper().readValue(
|
||||
getResourceContent("/prompts.json"),
|
||||
object : TypeReference<MutableList<PersonaDetails>>() {})
|
||||
}
|
||||
}
|
||||
|
|
@ -48,6 +48,8 @@
|
|||
instance="ee.carlrobert.codegpt.settings.configuration.ConfigurationConfigurable"/>
|
||||
<applicationConfigurable id="settings.codegpt.advanced" parentId="settings.codegpt" displayName="Advanced Settings"
|
||||
instance="ee.carlrobert.codegpt.settings.advanced.AdvancedSettingsConfigurable"/>
|
||||
<applicationConfigurable id="settings.codegpt.personas" parentId="settings.codegpt" displayName="Personas"
|
||||
instance="ee.carlrobert.codegpt.settings.persona.PersonasConfigurable"/>
|
||||
<applicationConfigurable
|
||||
parentId="settings.codegpt"
|
||||
instance="ee.carlrobert.codegpt.telemetry.ui.preferences.TelemetryConfigurable"
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ toolwindow.chat.youProCheckBox.text=Use GPT-4 model
|
|||
toolwindow.chat.youProCheckBox.enable=Turn on for complex queries
|
||||
toolwindow.chat.youProCheckBox.disable=Turn off for faster responses
|
||||
toolwindow.chat.youProCheckBox.notAllowed=Enable by subscribing to YouPro plan
|
||||
toolwindow.chat.textArea.emptyText=Ask anything... Use '@' to include files in the message
|
||||
toolwindow.chat.textArea.emptyText=Ask anything... Use '@' to include additional context
|
||||
service.codegpt.title=CodeGPT
|
||||
service.openai.title=OpenAI
|
||||
service.custom.openai.title=Custom OpenAI
|
||||
|
|
|
|||
3047
src/main/resources/prompts.json
Normal file
3047
src/main/resources/prompts.json
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,9 +1,10 @@
|
|||
package ee.carlrobert.codegpt.completions
|
||||
|
||||
import ee.carlrobert.codegpt.completions.CompletionRequestProvider.COMPLETION_SYSTEM_PROMPT
|
||||
import com.intellij.openapi.components.service
|
||||
import ee.carlrobert.codegpt.conversations.ConversationService
|
||||
import ee.carlrobert.codegpt.conversations.message.Message
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
|
||||
import ee.carlrobert.codegpt.settings.persona.DEFAULT_PROMPT
|
||||
import ee.carlrobert.codegpt.settings.persona.PersonaSettings
|
||||
import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionModel
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.assertj.core.groups.Tuple
|
||||
|
|
@ -13,7 +14,7 @@ class CompletionRequestProviderTest : IntegrationTest() {
|
|||
|
||||
fun testChatCompletionRequestWithSystemPromptOverride() {
|
||||
useOpenAIService()
|
||||
ConfigurationSettings.getCurrentState().systemPrompt = "TEST_SYSTEM_PROMPT"
|
||||
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
|
||||
val conversation = ConversationService.getInstance().startConversation()
|
||||
val firstMessage = createDummyMessage(500)
|
||||
val secondMessage = createDummyMessage(250)
|
||||
|
|
@ -42,7 +43,7 @@ class CompletionRequestProviderTest : IntegrationTest() {
|
|||
|
||||
fun testChatCompletionRequestWithoutSystemPromptOverride() {
|
||||
useOpenAIService()
|
||||
ConfigurationSettings.getCurrentState().systemPrompt = COMPLETION_SYSTEM_PROMPT
|
||||
service<PersonaSettings>().state.selectedPersona.instructions = DEFAULT_PROMPT
|
||||
val conversation = ConversationService.getInstance().startConversation()
|
||||
val firstMessage = createDummyMessage(500)
|
||||
val secondMessage = createDummyMessage(250)
|
||||
|
|
@ -61,7 +62,7 @@ class CompletionRequestProviderTest : IntegrationTest() {
|
|||
assertThat(request.messages)
|
||||
.extracting("role", "content")
|
||||
.containsExactly(
|
||||
Tuple.tuple("system", COMPLETION_SYSTEM_PROMPT),
|
||||
Tuple.tuple("system", DEFAULT_PROMPT),
|
||||
Tuple.tuple("user", "TEST_PROMPT"),
|
||||
Tuple.tuple("assistant", firstMessage.response),
|
||||
Tuple.tuple("user", "TEST_PROMPT"),
|
||||
|
|
@ -71,7 +72,7 @@ class CompletionRequestProviderTest : IntegrationTest() {
|
|||
|
||||
fun testChatCompletionRequestRetry() {
|
||||
useOpenAIService()
|
||||
ConfigurationSettings.getCurrentState().systemPrompt = "TEST_SYSTEM_PROMPT"
|
||||
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
|
||||
val conversation = ConversationService.getInstance().startConversation()
|
||||
val firstMessage = createDummyMessage("FIRST_TEST_PROMPT", 500)
|
||||
val secondMessage = createDummyMessage("SECOND_TEST_PROMPT", 250)
|
||||
|
|
@ -97,8 +98,7 @@ class CompletionRequestProviderTest : IntegrationTest() {
|
|||
}
|
||||
|
||||
fun testReducedChatCompletionRequest() {
|
||||
useOpenAIService()
|
||||
ConfigurationSettings.getCurrentState().systemPrompt = COMPLETION_SYSTEM_PROMPT
|
||||
service<PersonaSettings>().state.selectedPersona.instructions = DEFAULT_PROMPT
|
||||
val conversation = ConversationService.getInstance().startConversation()
|
||||
conversation.addMessage(createDummyMessage(50))
|
||||
conversation.addMessage(createDummyMessage(100))
|
||||
|
|
@ -120,7 +120,7 @@ class CompletionRequestProviderTest : IntegrationTest() {
|
|||
assertThat(request.messages)
|
||||
.extracting("role", "content")
|
||||
.containsExactly(
|
||||
Tuple.tuple("system", COMPLETION_SYSTEM_PROMPT),
|
||||
Tuple.tuple("system", DEFAULT_PROMPT),
|
||||
Tuple.tuple("user", "TEST_PROMPT"),
|
||||
Tuple.tuple("assistant", remainingMessage.response),
|
||||
Tuple.tuple("user", "TEST_CHAT_COMPLETION_PROMPT"))
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
package ee.carlrobert.codegpt.completions
|
||||
|
||||
import com.intellij.openapi.components.service
|
||||
import ee.carlrobert.codegpt.completions.llama.PromptTemplate.LLAMA
|
||||
import ee.carlrobert.codegpt.conversations.ConversationService
|
||||
import ee.carlrobert.codegpt.conversations.message.Message
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
|
||||
import ee.carlrobert.codegpt.settings.persona.PersonaSettings
|
||||
import ee.carlrobert.llm.client.http.RequestEntity
|
||||
import ee.carlrobert.llm.client.http.exchange.NdJsonStreamHttpExchange
|
||||
import ee.carlrobert.llm.client.http.exchange.StreamHttpExchange
|
||||
|
|
@ -16,7 +18,7 @@ class DefaultCompletionRequestHandlerTest : IntegrationTest() {
|
|||
|
||||
fun testOpenAIChatCompletionCall() {
|
||||
useOpenAIService()
|
||||
ConfigurationSettings.getCurrentState().systemPrompt = "TEST_SYSTEM_PROMPT"
|
||||
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
|
||||
val message = Message("TEST_PROMPT")
|
||||
val conversation = ConversationService.getInstance().startConversation()
|
||||
val requestHandler = CompletionRequestHandler(getRequestEventListener(message))
|
||||
|
|
@ -47,7 +49,7 @@ class DefaultCompletionRequestHandlerTest : IntegrationTest() {
|
|||
|
||||
fun testAzureChatCompletionCall() {
|
||||
useAzureService()
|
||||
ConfigurationSettings.getCurrentState().systemPrompt = "TEST_SYSTEM_PROMPT"
|
||||
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
|
||||
val conversationService = ConversationService.getInstance()
|
||||
val prevMessage = Message("TEST_PREV_PROMPT")
|
||||
prevMessage.response = "TEST_PREV_RESPONSE"
|
||||
|
|
@ -85,7 +87,7 @@ class DefaultCompletionRequestHandlerTest : IntegrationTest() {
|
|||
fun testLlamaChatCompletionCall() {
|
||||
useLlamaService()
|
||||
ConfigurationSettings.getCurrentState().maxTokens = 99
|
||||
ConfigurationSettings.getCurrentState().systemPrompt = "TEST_SYSTEM_PROMPT"
|
||||
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
|
||||
val message = Message("TEST_PROMPT")
|
||||
val conversation = ConversationService.getInstance().startConversation()
|
||||
conversation.addMessage(Message("Ping", "Pong"))
|
||||
|
|
@ -120,7 +122,7 @@ class DefaultCompletionRequestHandlerTest : IntegrationTest() {
|
|||
fun testOllamaChatCompletionCall() {
|
||||
useOllamaService()
|
||||
ConfigurationSettings.getCurrentState().maxTokens = 99
|
||||
ConfigurationSettings.getCurrentState().systemPrompt = "TEST_SYSTEM_PROMPT"
|
||||
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
|
||||
val message = Message("TEST_PROMPT")
|
||||
val conversation = ConversationService.getInstance().startConversation()
|
||||
val requestHandler = CompletionRequestHandler(getRequestEventListener(message))
|
||||
|
|
@ -156,7 +158,7 @@ class DefaultCompletionRequestHandlerTest : IntegrationTest() {
|
|||
|
||||
fun testGoogleChatCompletionCall() {
|
||||
useGoogleService()
|
||||
ConfigurationSettings.getCurrentState().systemPrompt = "TEST_SYSTEM_PROMPT"
|
||||
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
|
||||
val message = Message("TEST_PROMPT")
|
||||
val conversation = ConversationService.getInstance().startConversation()
|
||||
val requestHandler = CompletionRequestHandler(getRequestEventListener(message))
|
||||
|
|
@ -192,7 +194,7 @@ class DefaultCompletionRequestHandlerTest : IntegrationTest() {
|
|||
|
||||
fun testCodeGPTServiceChatCompletionCall() {
|
||||
useCodeGPTService()
|
||||
ConfigurationSettings.getCurrentState().systemPrompt = "TEST_SYSTEM_PROMPT"
|
||||
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
|
||||
val message = Message("TEST_PROMPT")
|
||||
val conversation = ConversationService.getInstance().startConversation()
|
||||
val requestHandler = CompletionRequestHandler(getRequestEventListener(message))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package ee.carlrobert.codegpt.toolwindow.chat
|
||||
|
||||
import com.intellij.openapi.components.service
|
||||
import ee.carlrobert.codegpt.CodeGPTKeys
|
||||
import ee.carlrobert.codegpt.EncodingManager
|
||||
import ee.carlrobert.codegpt.ReferencedFile
|
||||
|
|
@ -10,6 +11,7 @@ import ee.carlrobert.codegpt.completions.llama.PromptTemplate.LLAMA
|
|||
import ee.carlrobert.codegpt.conversations.ConversationService
|
||||
import ee.carlrobert.codegpt.conversations.message.Message
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
|
||||
import ee.carlrobert.codegpt.settings.persona.PersonaSettings
|
||||
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings
|
||||
import ee.carlrobert.llm.client.http.RequestEntity
|
||||
import ee.carlrobert.llm.client.http.exchange.StreamHttpExchange
|
||||
|
|
@ -30,7 +32,7 @@ class ChatToolWindowTabPanelTest : IntegrationTest() {
|
|||
|
||||
fun testSendingOpenAIMessage() {
|
||||
useOpenAIService()
|
||||
ConfigurationSettings.getCurrentState().systemPrompt = "TEST_SYSTEM_PROMPT"
|
||||
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
|
||||
val message = Message("Hello!")
|
||||
val conversation = ConversationService.getInstance().startConversation()
|
||||
val panel = ChatToolWindowTabPanel(project, conversation)
|
||||
|
|
@ -92,7 +94,7 @@ class ChatToolWindowTabPanelTest : IntegrationTest() {
|
|||
ReferencedFile("TEST_FILE_NAME_2", "TEST_FILE_PATH_2", "TEST_FILE_CONTENT_2"),
|
||||
ReferencedFile("TEST_FILE_NAME_3", "TEST_FILE_PATH_3", "TEST_FILE_CONTENT_3")))
|
||||
useOpenAIService()
|
||||
ConfigurationSettings.getCurrentState().systemPrompt = "TEST_SYSTEM_PROMPT"
|
||||
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
|
||||
val message = Message("TEST_MESSAGE")
|
||||
message.userMessage = "TEST_MESSAGE"
|
||||
message.referencedFilePaths = listOf("TEST_FILE_PATH_1", "TEST_FILE_PATH_2", "TEST_FILE_PATH_3")
|
||||
|
|
@ -179,7 +181,7 @@ class ChatToolWindowTabPanelTest : IntegrationTest() {
|
|||
val testImagePath = Objects.requireNonNull(javaClass.getResource("/images/test-image.png")).path
|
||||
project.putUserData(CodeGPTKeys.IMAGE_ATTACHMENT_FILE_PATH, testImagePath)
|
||||
useOpenAIService("gpt-4-vision-preview")
|
||||
ConfigurationSettings.getCurrentState().systemPrompt = "TEST_SYSTEM_PROMPT"
|
||||
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
|
||||
val message = Message("TEST_MESSAGE")
|
||||
val conversation = ConversationService.getInstance().startConversation()
|
||||
val panel = ChatToolWindowTabPanel(project, conversation)
|
||||
|
|
@ -255,7 +257,7 @@ class ChatToolWindowTabPanelTest : IntegrationTest() {
|
|||
ReferencedFile("TEST_FILE_NAME_2", "TEST_FILE_PATH_2", "TEST_FILE_CONTENT_2"),
|
||||
ReferencedFile("TEST_FILE_NAME_3", "TEST_FILE_PATH_3", "TEST_FILE_CONTENT_3")))
|
||||
useOpenAIService()
|
||||
ConfigurationSettings.getCurrentState().systemPrompt = "TEST_SYSTEM_PROMPT"
|
||||
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
|
||||
val message = Message("TEST_MESSAGE")
|
||||
message.userMessage = "TEST_MESSAGE"
|
||||
message.referencedFilePaths = listOf("TEST_FILE_PATH_1", "TEST_FILE_PATH_2", "TEST_FILE_PATH_3")
|
||||
|
|
@ -341,7 +343,7 @@ class ChatToolWindowTabPanelTest : IntegrationTest() {
|
|||
fun testSendingLlamaMessage() {
|
||||
useLlamaService()
|
||||
val configurationState = ConfigurationSettings.getCurrentState()
|
||||
configurationState.systemPrompt = "TEST_SYSTEM_PROMPT"
|
||||
service<PersonaSettings>().state.selectedPersona.instructions = "TEST_SYSTEM_PROMPT"
|
||||
configurationState.maxTokens = 1000
|
||||
configurationState.temperature = 0.1
|
||||
val llamaSettings = LlamaSettings.getCurrentState()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue