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:
@@ -21,6 +21,49 @@ def run_migrations() -> None:
|
||||
conn.execute(
|
||||
text("ALTER TABLE words ADD COLUMN train_count INTEGER NOT NULL DEFAULT 0")
|
||||
)
|
||||
if "difficulty" not in cols:
|
||||
conn.execute(
|
||||
text("ALTER TABLE words ADD COLUMN difficulty DOUBLE NOT NULL DEFAULT 5.0")
|
||||
)
|
||||
if "stability_hours" not in cols:
|
||||
conn.execute(
|
||||
text(
|
||||
"ALTER TABLE words ADD COLUMN stability_hours "
|
||||
"DOUBLE NOT NULL DEFAULT 24.0"
|
||||
)
|
||||
)
|
||||
if "next_review_at" not in cols:
|
||||
conn.execute(text("ALTER TABLE words ADD COLUMN next_review_at VARCHAR(32)"))
|
||||
if "error_bank_member" not in cols:
|
||||
conn.execute(
|
||||
text(
|
||||
"ALTER TABLE words ADD COLUMN error_bank_member "
|
||||
"INTEGER NOT NULL DEFAULT 0"
|
||||
)
|
||||
)
|
||||
if "error_reinforce_streak" not in cols:
|
||||
conn.execute(
|
||||
text(
|
||||
"ALTER TABLE words ADD COLUMN error_reinforce_streak "
|
||||
"INTEGER NOT NULL DEFAULT 0"
|
||||
)
|
||||
)
|
||||
if "error_bank_entered_at" not in cols:
|
||||
conn.execute(
|
||||
text("ALTER TABLE words ADD COLUMN error_bank_entered_at VARCHAR(32)")
|
||||
)
|
||||
conn.execute(
|
||||
text(
|
||||
"UPDATE words SET error_bank_member = 1 "
|
||||
"WHERE wrong_count > 0 AND error_bank_member = 0"
|
||||
)
|
||||
)
|
||||
conn.execute(
|
||||
text(
|
||||
"UPDATE words SET error_bank_member = 1 "
|
||||
"WHERE error_reinforce_streak >= 2 AND error_bank_member = 0"
|
||||
)
|
||||
)
|
||||
|
||||
if insp.has_table("quiz_records"):
|
||||
cols = {c["name"] for c in insp.get_columns("quiz_records")}
|
||||
@@ -36,6 +79,161 @@ def run_migrations() -> None:
|
||||
cols = {c["name"] for c in insp.get_columns("user_settings")}
|
||||
if "active_book_id" not in cols:
|
||||
conn.execute(text("ALTER TABLE user_settings ADD COLUMN active_book_id INTEGER"))
|
||||
if "ai_wiki_enabled" not in cols:
|
||||
conn.execute(
|
||||
text(
|
||||
"ALTER TABLE user_settings ADD COLUMN ai_wiki_enabled "
|
||||
"INTEGER NOT NULL DEFAULT 0"
|
||||
)
|
||||
)
|
||||
if "ai_wiki_auto_organize" not in cols:
|
||||
conn.execute(
|
||||
text(
|
||||
"ALTER TABLE user_settings ADD COLUMN ai_wiki_auto_organize "
|
||||
"INTEGER NOT NULL DEFAULT 0"
|
||||
)
|
||||
)
|
||||
if "error_clear_correct_count" not in cols:
|
||||
conn.execute(
|
||||
text(
|
||||
"ALTER TABLE user_settings ADD COLUMN error_clear_correct_count "
|
||||
"INTEGER NOT NULL DEFAULT 2"
|
||||
)
|
||||
)
|
||||
|
||||
if insp.has_table("user_book_settings"):
|
||||
ubs_cols = {c["name"] for c in insp.get_columns("user_book_settings")}
|
||||
if "error_clear_correct_count" not in ubs_cols:
|
||||
conn.execute(
|
||||
text(
|
||||
"ALTER TABLE user_book_settings ADD COLUMN error_clear_correct_count "
|
||||
"INTEGER NOT NULL DEFAULT 2"
|
||||
)
|
||||
)
|
||||
|
||||
if not insp.has_table("wiki_sources"):
|
||||
conn.execute(
|
||||
text(
|
||||
"""
|
||||
CREATE TABLE wiki_sources (
|
||||
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
word_id INTEGER NULL,
|
||||
source_key VARCHAR(96) NOT NULL,
|
||||
source_type VARCHAR(32) NOT NULL,
|
||||
payload TEXT NOT NULL,
|
||||
created_at VARCHAR(32) NOT NULL,
|
||||
CONSTRAINT uq_wiki_source_user_key UNIQUE (user_id, source_key),
|
||||
FOREIGN KEY(user_id) REFERENCES users (id),
|
||||
FOREIGN KEY(word_id) REFERENCES words (id)
|
||||
)
|
||||
"""
|
||||
)
|
||||
)
|
||||
conn.execute(text("CREATE INDEX ix_wiki_sources_user_id ON wiki_sources (user_id)"))
|
||||
conn.execute(text("CREATE INDEX ix_wiki_sources_word_id ON wiki_sources (word_id)"))
|
||||
|
||||
if not insp.has_table("wiki_pages"):
|
||||
conn.execute(
|
||||
text(
|
||||
"""
|
||||
CREATE TABLE wiki_pages (
|
||||
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
word_id INTEGER NULL,
|
||||
slug VARCHAR(160) NOT NULL,
|
||||
title VARCHAR(500) NOT NULL,
|
||||
category VARCHAR(32) NOT NULL,
|
||||
summary TEXT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
source_count INTEGER NOT NULL DEFAULT 0,
|
||||
created_at VARCHAR(32) NOT NULL,
|
||||
updated_at VARCHAR(32) NOT NULL,
|
||||
CONSTRAINT uq_wiki_page_user_slug UNIQUE (user_id, slug),
|
||||
FOREIGN KEY(user_id) REFERENCES users (id),
|
||||
FOREIGN KEY(word_id) REFERENCES words (id)
|
||||
)
|
||||
"""
|
||||
)
|
||||
)
|
||||
conn.execute(text("CREATE INDEX ix_wiki_pages_user_id ON wiki_pages (user_id)"))
|
||||
conn.execute(text("CREATE INDEX ix_wiki_pages_word_id ON wiki_pages (word_id)"))
|
||||
|
||||
if not insp.has_table("wiki_links"):
|
||||
conn.execute(
|
||||
text(
|
||||
"""
|
||||
CREATE TABLE wiki_links (
|
||||
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
source_page_id INTEGER NOT NULL,
|
||||
target_page_id INTEGER NOT NULL,
|
||||
relation VARCHAR(32) NOT NULL,
|
||||
created_at VARCHAR(32) NOT NULL,
|
||||
CONSTRAINT uq_wiki_link_edge UNIQUE (
|
||||
user_id, source_page_id, target_page_id, relation
|
||||
),
|
||||
FOREIGN KEY(user_id) REFERENCES users (id),
|
||||
FOREIGN KEY(source_page_id) REFERENCES wiki_pages (id),
|
||||
FOREIGN KEY(target_page_id) REFERENCES wiki_pages (id)
|
||||
)
|
||||
"""
|
||||
)
|
||||
)
|
||||
conn.execute(text("CREATE INDEX ix_wiki_links_user_id ON wiki_links (user_id)"))
|
||||
conn.execute(
|
||||
text("CREATE INDEX ix_wiki_links_source_page_id ON wiki_links (source_page_id)")
|
||||
)
|
||||
conn.execute(
|
||||
text("CREATE INDEX ix_wiki_links_target_page_id ON wiki_links (target_page_id)")
|
||||
)
|
||||
|
||||
if not insp.has_table("wiki_logs"):
|
||||
conn.execute(
|
||||
text(
|
||||
"""
|
||||
CREATE TABLE wiki_logs (
|
||||
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
action VARCHAR(32) NOT NULL,
|
||||
page_slug VARCHAR(160) NULL,
|
||||
detail TEXT NOT NULL,
|
||||
created_at VARCHAR(32) NOT NULL,
|
||||
FOREIGN KEY(user_id) REFERENCES users (id)
|
||||
)
|
||||
"""
|
||||
)
|
||||
)
|
||||
conn.execute(text("CREATE INDEX ix_wiki_logs_user_id ON wiki_logs (user_id)"))
|
||||
|
||||
if not insp.has_table("wiki_llm_usage"):
|
||||
conn.execute(
|
||||
text(
|
||||
"""
|
||||
CREATE TABLE wiki_llm_usage (
|
||||
id INTEGER PRIMARY KEY AUTO_INCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
usage_date VARCHAR(10) NOT NULL,
|
||||
call_count INTEGER NOT NULL DEFAULT 0,
|
||||
CONSTRAINT uq_wiki_llm_usage_day UNIQUE (user_id, usage_date),
|
||||
FOREIGN KEY(user_id) REFERENCES users (id)
|
||||
)
|
||||
"""
|
||||
)
|
||||
)
|
||||
conn.execute(text("CREATE INDEX ix_wiki_llm_usage_user_id ON wiki_llm_usage (user_id)"))
|
||||
|
||||
if insp.has_table("wiki_pages"):
|
||||
conn.execute(
|
||||
text(
|
||||
"""
|
||||
UPDATE wiki_pages SET
|
||||
title = REPLACE(title, 'WordLoop LLM Wiki', 'WordLoop Wiki'),
|
||||
content = REPLACE(content, 'WordLoop LLM Wiki', 'WordLoop Wiki')
|
||||
WHERE title LIKE '%LLM Wiki%' OR content LIKE '%LLM Wiki%'
|
||||
"""
|
||||
)
|
||||
)
|
||||
|
||||
if not insp.has_table("user_book_settings"):
|
||||
conn.execute(
|
||||
|
||||
Reference in New Issue
Block a user