Skip to content

Commit d054cd0

Browse files
authored
Reorganize Settings sections + add LLM model/prompt configs (#116)
* Reshuffle/organize settings UI * Settings: AI prompt display/minor touch-ups * API key settings tests * Moved import/export together * Collapsible LLM prompt DIVs * Add export tests
1 parent fb6e094 commit d054cd0

38 files changed

+1033
-417
lines changed

app/controllers/family_exports_controller.rb

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ class FamilyExportsController < ApplicationController
22
include StreamExtensions
33

44
before_action :require_admin
5-
before_action :set_export, only: [ :download ]
5+
before_action :set_export, only: [ :download, :destroy ]
66

77
def new
88
# Modal view for initiating export
@@ -13,9 +13,9 @@ def create
1313
FamilyDataExportJob.perform_later(@export)
1414

1515
respond_to do |format|
16-
format.html { redirect_to settings_profile_path, notice: "Export started. You'll be able to download it shortly." }
16+
format.html { redirect_to imports_path, notice: "Export started. You'll be able to download it shortly." }
1717
format.turbo_stream {
18-
stream_redirect_to settings_profile_path, notice: "Export started. You'll be able to download it shortly."
18+
stream_redirect_to imports_path, notice: "Export started. You'll be able to download it shortly."
1919
}
2020
end
2121
end
@@ -29,10 +29,15 @@ def download
2929
if @export.downloadable?
3030
redirect_to @export.export_file, allow_other_host: true
3131
else
32-
redirect_to settings_profile_path, alert: "Export not ready for download"
32+
redirect_to imports_path, alert: "Export not ready for download"
3333
end
3434
end
3535

36+
def destroy
37+
@export.destroy
38+
redirect_to imports_path, notice: "Export deleted successfully"
39+
end
40+
3641
private
3742

3843
def set_export

app/controllers/imports_controller.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
class ImportsController < ApplicationController
2+
include SettingsHelper
3+
24
before_action :set_import, only: %i[show publish destroy revert apply_template]
35

46
def publish
@@ -11,7 +13,11 @@ def publish
1113

1214
def index
1315
@imports = Current.family.imports
14-
16+
@exports = Current.user.admin? ? Current.family.family_exports.ordered.limit(10) : nil
17+
@breadcrumbs = [
18+
[ "Home", root_path ],
19+
[ "Import/Export", imports_path ]
20+
]
1521
render layout: "settings"
1622
end
1723

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
class Settings::AiPromptsController < ApplicationController
2+
layout "settings"
3+
4+
def show
5+
@breadcrumbs = [
6+
[ "Home", root_path ],
7+
[ "AI Prompts", nil ]
8+
]
9+
@family = Current.family
10+
@assistant_config = Assistant.config_for(OpenStruct.new(user: Current.user))
11+
end
12+
end

app/controllers/settings/api_keys_controller.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ class Settings::ApiKeysController < ApplicationController
88
def show
99
@breadcrumbs = [
1010
[ "Home", root_path ],
11-
[ "API Keys", nil ]
11+
[ "API Key", nil ]
1212
]
1313
@current_api_key = @api_key
1414
end
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
class Settings::GuidesController < ApplicationController
2+
layout "settings"
3+
4+
def show
5+
@breadcrumbs = [
6+
[ "Home", root_path ],
7+
[ "Guides", nil ]
8+
]
9+
markdown = Redcarpet::Markdown.new(Redcarpet::Render::HTML,
10+
autolink: true,
11+
tables: true,
12+
fenced_code_blocks: true,
13+
strikethrough: true,
14+
superscript: true
15+
)
16+
@guide_content = markdown.render(File.read(Rails.root.join("docs/onboarding/guide.md")))
17+
end
18+
end

app/controllers/settings/profiles_controller.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ def show
55
@user = Current.user
66
@users = Current.family.users.order(:created_at)
77
@pending_invitations = Current.family.invitations.pending
8+
@breadcrumbs = [
9+
[ "Home", root_path ],
10+
[ "Profile Info", nil ]
11+
]
812
end
913

1014
def destroy

app/helpers/settings_helper.rb

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,25 @@
11
module SettingsHelper
22
SETTINGS_ORDER = [
3-
{ name: "Account", path: :settings_profile_path },
3+
# General section
4+
{ name: "Accounts", path: :accounts_path },
5+
{ name: "Bank Sync", path: :settings_bank_sync_path },
46
{ name: "Preferences", path: :settings_preferences_path },
7+
{ name: "Profile Info", path: :settings_profile_path },
58
{ name: "Security", path: :settings_security_path },
6-
{ name: "Self-Hosting", path: :settings_hosting_path, condition: :self_hosted? },
7-
{ name: "API Key", path: :settings_api_key_path },
89
{ name: "Billing", path: :settings_billing_path, condition: :not_self_hosted? },
9-
{ name: "Accounts", path: :accounts_path },
10-
{ name: "Imports", path: :imports_path },
11-
{ name: "Tags", path: :tags_path },
10+
# Transactions section
1211
{ name: "Categories", path: :categories_path },
12+
{ name: "Tags", path: :tags_path },
1313
{ name: "Rules", path: :rules_path },
1414
{ name: "Merchants", path: :family_merchants_path },
15+
# Advanced section
16+
{ name: "AI Prompts", path: :settings_ai_prompts_path },
17+
{ name: "API Key", path: :settings_api_key_path },
18+
{ name: "Self-Hosting", path: :settings_hosting_path, condition: :self_hosted? },
19+
{ name: "Imports", path: :imports_path },
20+
{ name: "SimpleFin", path: :simplefin_items_path },
21+
# More section
22+
{ name: "Guides", path: :settings_guides_path },
1523
{ name: "What's new", path: :changelog_path },
1624
{ name: "Feedback", path: :feedback_path }
1725
]

app/models/family_export.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
class FamilyExport < ApplicationRecord
22
belongs_to :family
33

4-
has_one_attached :export_file
4+
has_one_attached :export_file, dependent: :purge_later
55

66
enum :status, {
77
pending: "pending",

app/models/provider/openai.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ def supports_model?(model)
1414
MODELS.include?(model)
1515
end
1616

17-
def auto_categorize(transactions: [], user_categories: [], model: "gpt-4.1-mini")
17+
def auto_categorize(transactions: [], user_categories: [], model: "")
1818
with_provider_response do
1919
raise Error, "Too many transactions to auto-categorize. Max is 25 per request." if transactions.size > 25
2020

@@ -36,7 +36,7 @@ def auto_categorize(transactions: [], user_categories: [], model: "gpt-4.1-mini"
3636
end
3737
end
3838

39-
def auto_detect_merchants(transactions: [], user_merchants: [], model: "gpt-4.1-mini")
39+
def auto_detect_merchants(transactions: [], user_merchants: [], model: "")
4040
with_provider_response do
4141
raise Error, "Too many transactions to auto-detect merchants. Max is 25 per request." if transactions.size > 25
4242

app/models/provider/openai/auto_categorizer.rb

Lines changed: 24 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
class Provider::Openai::AutoCategorizer
2+
DEFAULT_MODEL = "gpt-4.1-mini"
3+
24
def initialize(client, model: "", transactions: [], user_categories: [])
35
@client = client
46
@model = model
@@ -8,7 +10,7 @@ def initialize(client, model: "", transactions: [], user_categories: [])
810

911
def auto_categorize
1012
response = client.responses.create(parameters: {
11-
model: model,
13+
model: model.presence || DEFAULT_MODEL,
1214
input: [ { role: "developer", content: developer_message } ],
1315
text: {
1416
format: {
@@ -26,6 +28,27 @@ def auto_categorize
2628
build_response(extract_categorizations(response))
2729
end
2830

31+
def instructions
32+
<<~INSTRUCTIONS.strip_heredoc
33+
You are an assistant to a consumer personal finance app. You will be provided a list
34+
of the user's transactions and a list of the user's categories. Your job is to auto-categorize
35+
each transaction.
36+
37+
Closely follow ALL the rules below while auto-categorizing:
38+
39+
- Return 1 result per transaction
40+
- Correlate each transaction by ID (transaction_id)
41+
- Attempt to match the most specific category possible (i.e. subcategory over parent category)
42+
- Category and transaction classifications should match (i.e. if transaction is an "expense", the category must have classification of "expense")
43+
- If you don't know the category, return "null"
44+
- You should always favor "null" over false positives
45+
- Be slightly pessimistic. Only match a category if you're 60%+ confident it is the correct one.
46+
- Each transaction has varying metadata that can be used to determine the category
47+
- Note: "hint" comes from 3rd party aggregators and typically represents a category name that
48+
may or may not match any of the user-supplied categories
49+
INSTRUCTIONS
50+
end
51+
2952
private
3053
attr_reader :client, :model, :transactions, :user_categories
3154

@@ -97,25 +120,4 @@ def developer_message
97120
```
98121
MESSAGE
99122
end
100-
101-
def instructions
102-
<<~INSTRUCTIONS.strip_heredoc
103-
You are an assistant to a consumer personal finance app. You will be provided a list
104-
of the user's transactions and a list of the user's categories. Your job is to auto-categorize
105-
each transaction.
106-
107-
Closely follow ALL the rules below while auto-categorizing:
108-
109-
- Return 1 result per transaction
110-
- Correlate each transaction by ID (transaction_id)
111-
- Attempt to match the most specific category possible (i.e. subcategory over parent category)
112-
- Category and transaction classifications should match (i.e. if transaction is an "expense", the category must have classification of "expense")
113-
- If you don't know the category, return "null"
114-
- You should always favor "null" over false positives
115-
- Be slightly pessimistic. Only match a category if you're 60%+ confident it is the correct one.
116-
- Each transaction has varying metadata that can be used to determine the category
117-
- Note: "hint" comes from 3rd party aggregators and typically represents a category name that
118-
may or may not match any of the user-supplied categories
119-
INSTRUCTIONS
120-
end
121123
end

0 commit comments

Comments
 (0)