Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,299 changes: 1,299 additions & 0 deletions .ai/openrouter-service-implementation-plan.md

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ gem "image_processing", "~> 1.2"
gem "tailwindcss-rails"
gem "tailwindcss-ruby"
gem "devise"
gem "httparty", "~> 0.21"
gem "will_paginate", "~> 4.0"

group :development, :test do
gem "rubocop", require: false
Expand All @@ -31,6 +33,7 @@ group :development, :test do
gem "factory_bot_rails"
gem "faker"
gem "rails-controller-testing"
gem "dotenv-rails"
end

group :development do
Expand All @@ -43,4 +46,5 @@ group :test do
gem "selenium-webdriver"
gem "simplecov", require: false
gem "simplecov-json", require: false
gem "webmock"
end
23 changes: 23 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,11 @@ GEM
coderay (1.1.3)
concurrent-ruby (1.3.6)
connection_pool (3.0.2)
crack (1.0.1)
bigdecimal
rexml
crass (1.0.6)
csv (3.3.5)
date (3.5.1)
debug (1.11.1)
irb (~> 1.10)
Expand All @@ -124,6 +128,9 @@ GEM
diff-lcs (1.6.2)
docile (1.4.1)
dotenv (3.2.0)
dotenv-rails (3.2.0)
dotenv (= 3.2.0)
railties (>= 6.1)
drb (2.2.3)
ed25519 (1.4.0)
erb (6.0.1)
Expand Down Expand Up @@ -156,6 +163,11 @@ GEM
raabro (~> 1.4)
globalid (1.3.0)
activesupport (>= 6.1)
hashdiff (1.2.1)
httparty (0.24.2)
csv
mini_mime (>= 1.0.0)
multi_xml (>= 0.5.2)
i18n (1.14.8)
concurrent-ruby (~> 1.0)
image_processing (1.14.0)
Expand Down Expand Up @@ -206,6 +218,8 @@ GEM
minitest (6.0.1)
prism (~> 1.5)
msgpack (1.8.0)
multi_xml (0.8.1)
bigdecimal (>= 3.1, < 5)
net-imap (0.6.2)
date
net-protocol
Expand Down Expand Up @@ -449,11 +463,16 @@ GEM
activemodel (>= 6.0.0)
bindex (>= 0.4.0)
railties (>= 6.0.0)
webmock (3.26.1)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
websocket (1.2.11)
websocket-driver (0.8.0)
base64
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.5)
will_paginate (4.0.1)
xpath (3.2.0)
nokogiri (~> 1.8)
zeitwerk (2.7.4)
Expand All @@ -476,9 +495,11 @@ DEPENDENCIES
capybara
debug
devise
dotenv-rails
erb_lint
factory_bot_rails
faker
httparty (~> 0.21)
image_processing (~> 1.2)
importmap-rails
jbuilder
Expand All @@ -505,6 +526,8 @@ DEPENDENCIES
turbo-rails
tzinfo-data
web-console
webmock
will_paginate (~> 4.0)

BUNDLED WITH
2.6.9
46 changes: 37 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,27 +50,36 @@ CI/CD and Hosting:

Clone the repository:

```
```bash
git clone https://github.com/your-org/Flashcards.git
cd Flashcards
```

Install dependencies:

```
```bash
bundle install
```

Set up the database:
Configure OpenRouter API (required for AI flashcard generation):

```bash
# Set environment variable for development
export OPENROUTER_API_KEY='sk-or-v1-your-key-here'

# Get your API key from: https://openrouter.ai
```
bin/rails db:create db:migrate

Set up the database:

```bash
bin/rails db:create db:migrate db:seed
```

Start the development server:

```
bin/rails server
```bash
bin/dev
```

The application will be available at http://localhost:3000.
Expand All @@ -79,11 +88,12 @@ The application will be available at http://localhost:3000.

Common Rails commands used in this project:

```
bin/rails server # Start the development server
```bash
bin/dev # Start development server (recommended)
bin/rails server # Start Rails server only
bin/rails console # Open Rails console
bin/rails db:migrate # Run database migrations
bin/rails test # Run test suite
bundle exec rspec # Run test suite
```

Linting and security tools (development/test):
Expand Down Expand Up @@ -122,6 +132,24 @@ Out of scope for MVP:

The project is currently in the MVP stage and under active development.

## Services Architecture

### OpenRouterService

Low-level API client for OpenRouter.ai communication:
- Handles HTTP requests and authentication
- Manages structured JSON responses
- Comprehensive error handling
- SSL configuration for development

### FlashcardGenerationService

High-level service for AI flashcard generation:
- Intelligent prompt engineering
- Response validation and formatting
- Quality control (length, content)
- Multiple model support

## License

This project is licensed under the MIT License.
126 changes: 126 additions & 0 deletions app/assets/stylesheets/application.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,129 @@
*
* Consider organizing styles into separate files for maintainability.
*/

/* will_paginate Tailwind Styling */

.pagination {
display: inline-flex;
align-items: center;
gap: 0.375rem;
padding: 0;
margin: 0;
list-style: none;
background-color: #ffffff;
border-radius: 0.75rem;
padding: 0.375rem;
box-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
}

.pagination a,
.pagination .current,
.pagination .disabled,
.pagination span {
display: inline-flex;
align-items: center;
justify-content: center;
min-width: 2.75rem;
height: 2.75rem;
padding: 0 0.875rem;
font-size: 0.875rem;
font-weight: 500;
border-radius: 0.5rem;
transition: all 0.15s ease-in-out;
line-height: 1;
}

.pagination a {
color: #4b5563;
background-color: transparent;
text-decoration: none;
}

.pagination a:hover {
background-color: #f3f4f6;
color: #111827;
transform: translateY(-1px);
}

.pagination a:active {
transform: translateY(0);
}

.pagination .current {
color: #ffffff;
background-color: #4f46e5;
font-weight: 600;
box-shadow: 0 4px 6px -1px rgb(79 70 229 / 0.3), 0 2px 4px -2px rgb(79 70 229 / 0.3);
}

.pagination .disabled {
color: #d1d5db;
background-color: transparent;
cursor: not-allowed;
opacity: 0.5;
}

.pagination .previous_page,
.pagination .next_page {
padding: 0 1.25rem;
font-weight: 600;
color: #4f46e5;
}

.pagination .previous_page:hover,
.pagination .next_page:hover {
background-color: #eef2ff;
color: #4338ca;
}

.pagination .previous_page.disabled,
.pagination .next_page.disabled {
color: #d1d5db;
}

.pagination .previous_page.disabled:hover,
.pagination .next_page.disabled:hover {
background-color: transparent;
transform: none;
}

.pagination em {
font-style: normal;
}

.pagination .gap {
color: #9ca3af;
padding: 0 0.5rem;
font-weight: 600;
}

/* Loading Spinner Animations */

@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

@keyframes pulse {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.5;
transform: scale(0.8);
}
}

.animate-spin {
animation: spin 1s linear infinite;
}

.animate-pulse {
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
30 changes: 28 additions & 2 deletions app/controllers/flashcards_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,22 @@ class FlashcardsController < ApplicationController
before_action :authenticate_user!

def index
@flashcards = current_user.flashcards
@flashcards = current_user.flashcards.order(created_at: :desc).paginate(page: params[:page], per_page: 12)

# User-specific stats
@user_stats = {
ai_acceptance_rate: current_user.ai_acceptance_rate,
ai_flashcards_percentage: current_user.ai_flashcards_percentage,
total_flashcards: current_user.flashcards.count
}

# System-wide stats
@system_stats = {
ai_acceptance_rate: User.system_ai_acceptance_rate,
ai_flashcards_percentage: User.system_ai_flashcards_percentage,
total_flashcards: User.system_total_flashcards,
total_users: User.system_total_users
}
end

def show
Expand Down Expand Up @@ -32,7 +47,18 @@ def edit
def update
@flashcard = current_user.flashcards.find(params[:id])

if @flashcard.update(flashcard_params)
# Check if flashcard content changed and it was AI-generated
content_changed = (@flashcard.front != flashcard_params[:front] ||
@flashcard.back != flashcard_params[:back])

# Change source to ai_edited if it was ai_full and content changed
if content_changed && @flashcard.ai_full?
updated_params = flashcard_params.merge(source: :ai_edited)
else
updated_params = flashcard_params
end

if @flashcard.update(updated_params)
redirect_to flashcards_path, notice: "Flashcard updated successfully."
else
flash.now[:alert] = "Failed to update flashcard. Please check the errors below."
Expand Down
Loading