Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Refactoring] Make ChatResponsePanel more modular #243

Open
stephanj opened this issue Aug 23, 2024 · 0 comments
Open

[Refactoring] Make ChatResponsePanel more modular #243

stephanj opened this issue Aug 23, 2024 · 0 comments
Labels
enhancement New feature or request

Comments

@stephanj
Copy link
Contributor

Suggested by Claude Sonnet 3.5:

  1. Create a NodeProcessor interface:
public interface NodeProcessor {
    JPanel processNode();
}
  1. Create concrete implementations for different node types:
public class TextNodeProcessor implements NodeProcessor {
    private final Node node;
    
    public TextNodeProcessor(Node node) {
        this.node = node;
    }
    
    @Override
    public JPanel processNode() {
        // Process text node
        // Return a JPanel with the processed content
    }
}

public class CodeBlockNodeProcessor implements NodeProcessor {
    private final FencedCodeBlock codeBlock;
    private final ChatMessageContext chatMessageContext;
    
    public CodeBlockNodeProcessor(FencedCodeBlock codeBlock, ChatMessageContext chatMessageContext) {
        this.codeBlock = codeBlock;
        this.chatMessageContext = chatMessageContext;
    }
    
    @Override
    public JPanel processNode() {
        // Process code block
        // Return a JPanel with the processed content, including syntax highlighting and buttons
    }
}
  1. Create a NodeProcessorFactory:
public class NodeProcessorFactory {
    public static NodeProcessor createProcessor(Node node, ChatMessageContext chatMessageContext) {
        if (node instanceof FencedCodeBlock) {
            return new CodeBlockNodeProcessor((FencedCodeBlock) node, chatMessageContext);
        } else {
            return new TextNodeProcessor(node);
        }
    }
}
  1. Refactor the ChatResponsePanel:
public class ChatResponsePanel extends BackgroundPanel {
    private final ChatMessageContext chatMessageContext;

    public ChatResponsePanel(ChatMessageContext chatMessageContext) {
        super(chatMessageContext.getName());
        this.chatMessageContext = chatMessageContext;
        
        setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
        add(new ResponseHeaderPanel(chatMessageContext));
        addResponsePane(chatMessageContext);
        
        if (chatMessageContext.hasFiles()) {
            addFileListPanel();
        }
        
        if (DevoxxGenieStateService.getInstance().getShowExecutionTime()) {
            addExecutionInfoPanel();
        }
    }

    private void addResponsePane(ChatMessageContext chatMessageContext) {
        String markDownResponse = chatMessageContext.getAiMessage().text();
        Node document = Parser.builder().build().parse(markDownResponse);
        
        Node node = document.getFirstChild();
        while (node != null) {
            NodeProcessor processor = NodeProcessorFactory.createProcessor(node, chatMessageContext);
            JPanel panel = processor.processNode();
            add(panel);
            node = node.getNext();
        }
    }

    private void addFileListPanel() {
        List<VirtualFile> files = FileListManager.getInstance().getFiles();
        ExpandablePanel fileListPanel = new ExpandablePanel(chatMessageContext, files);
        add(fileListPanel);
    }

    private void addExecutionInfoPanel() {
        // Add execution time, token usage and cost information
        JPanel infoPanel = new ExecutionInfoPanel(chatMessageContext);
        add(infoPanel);
    }
}
  1. Create an ExecutionInfoPanel to encapsulate the execution information:
public class ExecutionInfoPanel extends JPanel {
    public ExecutionInfoPanel(ChatMessageContext chatMessageContext) {
        setLayout(new FlowLayout(FlowLayout.LEFT));
        setOpaque(false);

        TokenUsage tokenUsage = chatMessageContext.getTokenUsage();
        if (tokenUsage != null) {
            String cost = "";
            if (DefaultLLMSettingsUtil.isApiKeyBasedProvider(chatMessageContext.getLanguageModel().getProvider())) {
                cost = String.format("- %.5f $", chatMessageContext.getCost());
            }

            NumberFormat numberFormat = NumberFormat.getNumberInstance(Locale.getDefault());
            String formattedInputTokens = numberFormat.format(tokenUsage.inputTokenCount());
            String formattedOutputTokens = numberFormat.format(tokenUsage.outputTokenCount());

            String infoText = String.format("ϟ %.2fs - Tokens ↑ %s ↓️ %s %s",
                    chatMessageContext.getExecutionTimeMs() / 1000.0,
                    formattedInputTokens,
                    formattedOutputTokens,
                    cost);

            JLabel infoLabel = new JLabel(infoText);
            infoLabel.setForeground(JBColor.GRAY);
            infoLabel.setFont(infoLabel.getFont().deriveFont(12f));
            add(infoLabel);
        }
    }
}

This refactoring brings several benefits:

  1. Separation of concerns: Each node type is processed by its own class, making the code more modular and easier to maintain.
  2. Extensibility: Adding support for new node types is as simple as creating a new processor and adding it to the factory.
  3. Cleaner main class: The ChatResponsePanel is now more focused on composition and layout, delegating the processing of content to specialized classes.
  4. Improved readability: The code is now more self-documenting, with clear responsibilities for each class.

To fully implement this refactoring, you'll need to:

  1. Update the TextNodeProcessor and CodeBlockNodeProcessor to handle the specific rendering logic for each type of content.
  2. Ensure that the ChatMessageContext contains all necessary information for rendering (like the project instance for opening files).
  3. Update any other parts of the DevoxxGenie plugin that interact with the ChatResponsePanel to work with this new structure.

This refactored provides a more modular and maintainable structure for handling different types of content in the response panel.

@stephanj stephanj added the enhancement New feature or request label Aug 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant