Ship native iOS app, Wiki/TTS backend, and skeleton loading UX.
Replace the WebView shell with SwiftUI screens, add account-scoped Wiki and TTS APIs with adaptive review and photo scan support, and keep web/iOS pages usable while data loads asynchronously. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+92
-1
@@ -1,4 +1,4 @@
|
||||
from sqlalchemy import Column, ForeignKey, Integer, String, Text, UniqueConstraint
|
||||
from sqlalchemy import Column, Float, ForeignKey, Integer, String, Text, UniqueConstraint
|
||||
|
||||
from database import Base
|
||||
|
||||
@@ -73,7 +73,13 @@ class Word(Base):
|
||||
consecutive_correct_count = Column(Integer, nullable=False, default=0)
|
||||
mastery_score = Column(Integer, nullable=False, default=0)
|
||||
train_count = Column(Integer, nullable=False, default=0)
|
||||
error_bank_member = Column(Integer, nullable=False, default=0)
|
||||
error_reinforce_streak = Column(Integer, nullable=False, default=0)
|
||||
error_bank_entered_at = Column(String(32), nullable=True)
|
||||
total_train_seconds = Column(Integer, nullable=False, default=0)
|
||||
difficulty = Column(Float, nullable=False, default=5.0)
|
||||
stability_hours = Column(Float, nullable=False, default=24.0)
|
||||
next_review_at = Column(String(32), nullable=True)
|
||||
review_due_date = Column(String(32), nullable=True)
|
||||
last_reviewed_at = Column(String(32), nullable=True)
|
||||
created_at = Column(String(32), nullable=False)
|
||||
@@ -101,7 +107,10 @@ class UserSettings(Base):
|
||||
daily_target = Column(Integer, nullable=False, default=20)
|
||||
master_required_count = Column(Integer, nullable=False, default=3)
|
||||
weak_wrong_threshold = Column(Integer, nullable=False, default=3)
|
||||
error_clear_correct_count = Column(Integer, nullable=False, default=2)
|
||||
active_book_id = Column(Integer, ForeignKey("word_books.id"), nullable=True)
|
||||
ai_wiki_enabled = Column(Integer, nullable=False, default=0)
|
||||
ai_wiki_auto_organize = Column(Integer, nullable=False, default=0)
|
||||
|
||||
|
||||
class UserBookSettings(Base):
|
||||
@@ -116,6 +125,7 @@ class UserBookSettings(Base):
|
||||
daily_target = Column(Integer, nullable=False, default=12)
|
||||
master_required_count = Column(Integer, nullable=False, default=3)
|
||||
weak_wrong_threshold = Column(Integer, nullable=False, default=2)
|
||||
error_clear_correct_count = Column(Integer, nullable=False, default=2)
|
||||
|
||||
|
||||
class DictionaryEntry(Base):
|
||||
@@ -130,3 +140,84 @@ class DictionaryEntry(Base):
|
||||
example_en = Column(Text, nullable=True)
|
||||
example_cn = Column(Text, nullable=True)
|
||||
source = Column(String(64), nullable=True)
|
||||
|
||||
|
||||
class WikiSource(Base):
|
||||
"""Immutable learning event ingested into a user's Wiki."""
|
||||
|
||||
__tablename__ = "wiki_sources"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("user_id", "source_key", name="uq_wiki_source_user_key"),
|
||||
)
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
||||
word_id = Column(Integer, ForeignKey("words.id"), nullable=True, index=True)
|
||||
source_key = Column(String(96), nullable=False)
|
||||
source_type = Column(String(32), nullable=False)
|
||||
payload = Column(Text, nullable=False)
|
||||
created_at = Column(String(32), nullable=False)
|
||||
|
||||
|
||||
class WikiPage(Base):
|
||||
"""Compiled Markdown page owned by one user."""
|
||||
|
||||
__tablename__ = "wiki_pages"
|
||||
__table_args__ = (UniqueConstraint("user_id", "slug", name="uq_wiki_page_user_slug"),)
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
||||
word_id = Column(Integer, ForeignKey("words.id"), nullable=True, index=True)
|
||||
slug = Column(String(160), nullable=False)
|
||||
title = Column(String(500), nullable=False)
|
||||
category = Column(String(32), nullable=False)
|
||||
summary = Column(Text, nullable=False)
|
||||
content = Column(Text, nullable=False)
|
||||
source_count = Column(Integer, nullable=False, default=0)
|
||||
created_at = Column(String(32), nullable=False)
|
||||
updated_at = Column(String(32), nullable=False)
|
||||
|
||||
|
||||
class WikiLink(Base):
|
||||
__tablename__ = "wiki_links"
|
||||
__table_args__ = (
|
||||
UniqueConstraint(
|
||||
"user_id",
|
||||
"source_page_id",
|
||||
"target_page_id",
|
||||
"relation",
|
||||
name="uq_wiki_link_edge",
|
||||
),
|
||||
)
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
||||
source_page_id = Column(Integer, ForeignKey("wiki_pages.id"), nullable=False, index=True)
|
||||
target_page_id = Column(Integer, ForeignKey("wiki_pages.id"), nullable=False, index=True)
|
||||
relation = Column(String(32), nullable=False)
|
||||
created_at = Column(String(32), nullable=False)
|
||||
|
||||
|
||||
class WikiLog(Base):
|
||||
__tablename__ = "wiki_logs"
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
||||
action = Column(String(32), nullable=False)
|
||||
page_slug = Column(String(160), nullable=True)
|
||||
detail = Column(Text, nullable=False)
|
||||
created_at = Column(String(32), nullable=False)
|
||||
|
||||
|
||||
class WikiLlmUsage(Base):
|
||||
"""Per-account daily DeepSeek call counter for Wiki compilation."""
|
||||
|
||||
__tablename__ = "wiki_llm_usage"
|
||||
__table_args__ = (
|
||||
UniqueConstraint("user_id", "usage_date", name="uq_wiki_llm_usage_day"),
|
||||
)
|
||||
|
||||
id = Column(Integer, primary_key=True, autoincrement=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
|
||||
usage_date = Column(String(10), nullable=False)
|
||||
call_count = Column(Integer, nullable=False, default=0)
|
||||
|
||||
Reference in New Issue
Block a user