1
0
Fork 0
mirror of https://github.com/maybe-finance/maybe.git synced 2025-07-22 06:39:39 +02:00

Personal finance AI (v1) (#2022)

* AI sidebar

* Add chat and message models with associations

* Implement AI chat functionality with sidebar and messaging system

- Add chat and messages controllers
- Create chat and message views
- Implement chat-related routes
- Add message broadcasting and user interactions
- Update application layout to support chat sidebar
- Enhance user model with initials method

* Refactor AI sidebar with enhanced chat menu and interactions

- Update sidebar layout with dynamic width and improved responsiveness
- Add new chat menu Stimulus controller for toggling between chat and chat list views
- Improve chat list display with recent chats and empty state
- Extract AI avatar to a partial for reusability
- Enhance message display and interaction styling
- Add more contextual buttons and interaction hints

* Improve chat scroll behavior and message styling

- Refactor chat scroll functionality with Stimulus controller
- Optimize message scrolling in chat views
- Update message styling for better visual hierarchy
- Enhance chat container layout with flex and auto-scroll
- Simplify message rendering across different chat views

* Extract AI avatar to a shared partial for consistent styling

- Refactor AI avatar rendering across chat views
- Replace hardcoded avatar markup with a reusable partial
- Simplify avatar display in chats and messages views

* Update sidebar controller to handle right panel width dynamically

- Add conditional width class for right sidebar panel
- Ensure consistent sidebar toggle behavior for both left and right panels
- Use specific width class for right panel (w-[375px])

* Refactor chat form and AI greeting with flexible partials

- Extract message form to a reusable partial with dynamic context support
- Create flexible AI greeting partial for consistent welcome messages
- Simplify chat and sidebar views by leveraging new partials
- Add support for different form scenarios (chat, new chat, sidebar)
- Improve code modularity and reduce duplication

* Add chat clearing functionality with dynamic menu options

- Implement clear chat action in ChatsController
- Add clear chat route to support clearing messages
- Update AI sidebar with dropdown menu for chat actions
- Preserve system message when clearing chat
- Enhance chat interaction with new menu options

* Add frontmatter to project structure documentation

- Create initial frontmatter for structure.mdc file
- Include description and configuration options
- Prepare for potential dynamic documentation rendering

* Update general project rules with additional guidelines

- Add rule for using `Current.family` instead of `current_family`
- Include new guidelines for testing, API routes, and solution approach
- Expand project-specific rules for more consistent development practices

* Add OpenAI gem and AI-friendly data representations

- Add `ruby-openai` gem for AI integration
- Implement `to_ai_readable_hash` methods in BalanceSheet and IncomeStatement
- Include Promptable module in both models
- Add savings rate calculation method in IncomeStatement
- Prepare financial models for AI-powered insights and interactions

* Enhance AI Financial Assistant with Advanced Querying and Debugging Capabilities

- Implement comprehensive AI financial query system with function-based interactions
- Add detailed debug logging for AI responses and function calls
- Extend BalanceSheet and IncomeStatement models with AI-friendly methods
- Create robust error handling and fallback mechanisms for AI queries
- Update chat and message views to support debug mode and enhanced rendering
- Add AI query routes and initial test coverage for financial assistant

* Refactor AI sidebar and chat layout with improved structure and comments

- Remove inline AI chat from application layout
- Enhance AI sidebar with more semantic HTML structure
- Add descriptive comments to clarify different sections of chat view
- Improve flex layout and scrolling behavior in chat messages container
- Optimize message rendering with more explicit class names and structure

* Add Markdown rendering support for AI chat messages

- Implement `markdown` helper method in ApplicationHelper using Redcarpet
- Update message view to render AI messages with Markdown formatting
- Add comprehensive Markdown rendering options (tables, code blocks, links)
- Enhance AI Financial Assistant prompt to encourage Markdown usage
- Remove commented Markdown CSS in Tailwind application stylesheet

* Missing comma

* Enhance AI response processing with chat history context

* Improve AI debug logging with payload size limits and internal message flag

* Enhance AI chat interaction with improved thinking indicator and scrolling behavior

* Add AI consent and enable/disable functionality for AI chat

* Upgrade Biome and refactor JavaScript template literals

- Update @biomejs/biome to latest version with caret (^) notation
- Refactor AI query and chat controllers to use template literals
- Standardize npm scripts formatting in package.json

* Add beta testing usage note to AI consent modal

* Update test fixtures and configurations for AI chat functionality

- Add family association to chat fixtures and tests
- Set consistent password digest for test users
- Enable AI for test users
- Add OpenAI access token for test environment
- Update chat and user model tests to include family context

* Simplify data model and get tests passing

* Remove structure.mdc from version control

* Integrate AI chat styles into existing prose pattern

* Match Figma design spec, implement Turbo frames and actions for chats controller

* AI rules refresh

* Consolidate Stimulus controllers, thinking state, controllers, and views

* Naming, domain alignment

* Reset migrations

* Improve data model to support tool calls and message types

* Tool calling tests and fixtures

* Tool call implementation and test

* Get assistant test working again

* Test updates

* Process tool calls within provider

* Chat UI back to working state again

* Remove stale code

* Tests passing

* Update openai class naming to avoid conflicts

* Reconfigure test env

* Rebuild gemfile

* Fix naming conflicts for ChatResponse

* Message styles

* Use OpenAI conversation state management

* Assistant function base implementation

* Add back thinking messages, clean up error handling for chat

* Fix sync error when security price has bad data from provider

* Add balance sheet function to assistant

* Add better function calling error visibility

* Add income statement function

* Simplify and clean up "thinking" interactions with Turbo frames

* Remove stale data definitions from functions

* Ensure VCR fixtures working with latest code

* basic stream implementation

* Get streaming working

* Make AI sidebar wider when left sidebar is collapsed

* Get tests working with streaming responses

* Centralize provider error handling

* Provider data boundaries

---------

Co-authored-by: Josh Pigford <josh@joshpigford.com>
This commit is contained in:
Zach Gollwitzer 2025-03-28 13:08:22 -04:00 committed by GitHub
parent 8e6b81af77
commit 2f6b11c18f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
126 changed files with 3576 additions and 462 deletions

View file

@ -0,0 +1,60 @@
import { Controller } from "@hotwired/stimulus";
export default class extends Controller {
static targets = ["messages", "form", "input"];
connect() {
this.#configureAutoScroll();
}
disconnect() {
if (this.messagesObserver) {
this.messagesObserver.disconnect();
}
}
autoResize() {
const input = this.inputTarget;
const lineHeight = 20; // text-sm line-height (14px * 1.429 ≈ 20px)
const maxLines = 3; // 3 lines = 60px total
input.style.height = "auto";
input.style.height = `${Math.min(input.scrollHeight, lineHeight * maxLines)}px`;
input.style.overflowY =
input.scrollHeight > lineHeight * maxLines ? "auto" : "hidden";
}
submitSampleQuestion(e) {
this.inputTarget.value = e.target.dataset.chatQuestionParam;
setTimeout(() => {
this.formTarget.requestSubmit();
}, 200);
}
// Newlines require shift+enter, otherwise submit the form (same functionality as ChatGPT and others)
handleInputKeyDown(e) {
if (e.key === "Enter" && !e.shiftKey) {
e.preventDefault();
this.formTarget.requestSubmit();
}
}
#configureAutoScroll() {
this.messagesObserver = new MutationObserver((_mutations) => {
if (this.hasMessagesTarget) {
this.#scrollToBottom();
}
});
// Listen to entire sidebar for changes, always try to scroll to the bottom
this.messagesObserver.observe(this.element, {
childList: true,
subtree: true,
});
}
#scrollToBottom = () => {
this.messagesTarget.scrollTop = this.messagesTarget.scrollHeight;
};
}

View file

@ -2,17 +2,69 @@ import { Controller } from "@hotwired/stimulus";
// Connects to data-controller="sidebar"
export default class extends Controller {
static values = { userId: String };
static targets = ["panel", "content"];
static values = {
userId: String,
config: Object,
};
toggle() {
this.panelTarget.classList.toggle("w-0");
this.panelTarget.classList.toggle("opacity-0");
this.panelTarget.classList.toggle("w-80");
this.panelTarget.classList.toggle("opacity-100");
this.contentTarget.classList.toggle("max-w-4xl");
this.contentTarget.classList.toggle("max-w-5xl");
static targets = ["leftPanel", "rightPanel", "content"];
initialize() {
this.leftPanelOpen = this.configValue.left_panel.is_open;
this.rightPanelOpen = this.configValue.right_panel.is_open;
}
toggleLeftPanel() {
this.leftPanelOpen = !this.leftPanelOpen;
this.#updatePanelWidths();
this.#persistPreference("show_sidebar", this.leftPanelOpen);
}
toggleRightPanel() {
this.rightPanelOpen = !this.rightPanelOpen;
this.#updatePanelWidths();
this.#persistPreference("show_ai_sidebar", this.rightPanelOpen);
}
#updatePanelWidths() {
this.contentTarget.style.maxWidth = `${this.#contentMaxWidth()}px`;
this.leftPanelTarget.style.width = `${this.#leftPanelWidth()}px`;
this.rightPanelTarget.style.width = `${this.#rightPanelWidth()}px`;
}
#leftPanelWidth() {
if (this.leftPanelOpen) {
return this.configValue.left_panel.min_width;
}
return 0;
}
#rightPanelWidth() {
if (this.rightPanelOpen) {
if (this.leftPanelOpen) {
return this.configValue.right_panel.min_width;
}
return this.configValue.right_panel.max_width;
}
return 0;
}
#contentMaxWidth() {
if (!this.leftPanelOpen && !this.rightPanelOpen) {
return 1024;
}
if (this.leftPanelOpen && !this.rightPanelOpen) {
return 896;
}
return 768;
}
#persistPreference(field, value) {
fetch(`/users/${this.userIdValue}`, {
method: "PATCH",
headers: {
@ -21,7 +73,7 @@ export default class extends Controller {
Accept: "application/json",
},
body: new URLSearchParams({
"user[show_sidebar]": !this.panelTarget.classList.contains("w-0"),
[`user[${field}]`]: value,
}).toString(),
});
}