Skip to content

Commit

Permalink
add studylist import option to attempt word deinflection
Browse files Browse the repository at this point in the history
  • Loading branch information
hlorenzi committed Aug 18, 2024
1 parent b6af56d commit ad0830b
Show file tree
Hide file tree
Showing 9 changed files with 93 additions and 21 deletions.
1 change: 1 addition & 0 deletions backend/src/db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ export interface Interface
studylistWordImport: (
authUser: Api.MaybeUser,
studylistId: string,
attemptDeinflection: boolean,
words: Api.StudylistWordImport.ImportWord[])
=> Promise<number[]>

Expand Down
4 changes: 2 additions & 2 deletions backend/src/db/mongodb/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -291,8 +291,8 @@ export async function connect(): Promise<Interface>
MongoDbStudyLists.studylistWordAdd(state, authUser, studylistId, wordId),
studylistWordRemoveMany: (authUser, studylistId, wordIds) =>
MongoDbStudyLists.studylistWordRemoveMany(state, authUser, studylistId, wordIds),
studylistWordImport: (authUser, studylistId, words) =>
MongoDbStudyLists.studylistWordImport(state, authUser, studylistId, words),
studylistWordImport: (authUser, studylistId, attemptDeinflection, words) =>
MongoDbStudyLists.studylistWordImport(state, authUser, studylistId, attemptDeinflection, words),
studylistWordsGet: (authUser, studylistId) =>
MongoDbStudyLists.studylistWordsGet(state, authUser, studylistId),
studylistCommunityGetRecent: (authUser, limit) =>
Expand Down
47 changes: 43 additions & 4 deletions backend/src/db/mongodb/studylist.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import * as MongoDriver from "mongodb"
import * as MongoDb from "./index.ts"
import * as Db from "../index.ts"
import * as Auth from "../../auth/index.ts"
import * as DbMongo from "./index.ts"
Expand Down Expand Up @@ -566,6 +567,7 @@ export async function studylistWordImport(
state: DbMongo.State,
authUser: Api.MaybeUser,
studylistId: string,
attemptDeinflection: boolean,
words: Api.StudylistWordImport.ImportWord[])
: Promise<number[]>
{
Expand Down Expand Up @@ -595,12 +597,20 @@ export async function studylistWordImport(
const failedWordIndices: number[] = []
for (let w = 0; w < words.length; w++)
{
if (studylist.words.length + importedWords.length + 1 > Api.StudyList.maxWordCount)
{
failedWordIndices.push(w)
continue
}

const word = words[w]

const dbFind = word.reading ?
{ $all: [word.base, word.reading] } :
word.base

let wordId: string | undefined = undefined

const dbWords = await state.collWords
.find({
[DbMongo.fieldWordLookUpHeadingsText]: dbFind,
Expand All @@ -610,15 +620,44 @@ export async function studylistWordImport(
.limit(1)
.toArray()

if (dbWords.length < 1 ||
studylist.words.length + importedWords.length + 1 > Api.StudyList.maxWordCount)
if (dbWords.length >= 1)
wordId = dbWords[0]._id

if (wordId === undefined &&
attemptDeinflection)
{
// Attempt to de-inflect word
const inflectionBreakdown = Inflection.breakdown(word.base)
const inflectionOf = Inflection.flattenBreakdown(inflectionBreakdown)

const dbFindQueries: any[] = []
for (const step of inflectionOf)
{
dbFindQueries.push({
[MongoDb.fieldWordLookUpHeadingsText]: step.term,
[MongoDb.fieldWordLookUpTags]: step.category,
})
}

const fieldLookUp = "lookUp" satisfies keyof MongoDb.DbWordEntry
const fieldLen = "len" satisfies keyof MongoDb.DbWordEntry["lookUp"]

const dbDeinflectedWord = await state.collWords
.find({ $or: dbFindQueries })
.sort({ [`${fieldLookUp}.${fieldLen}`]: -1, score: -1, _id: 1 })
.limit(1)
.toArray()

if (dbDeinflectedWord.length >= 1)
wordId = dbDeinflectedWord[0]._id
}

if (wordId === undefined)
{
failedWordIndices.push(w)
continue
}

const wordId = dbWords[0]._id

if (studylist.words.find(w => w.id === wordId))
continue

Expand Down
15 changes: 1 addition & 14 deletions backend/src/server/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -392,20 +392,7 @@ function normalizeQuery(queryRaw: string): Api.Search.Query
.filter(w => w.length !== 0)

const inflectionBreakdown = Inflection.breakdown(queryJapanese)
const inflectionOf: Inflection.Inflected[] = []
const seenInflections = new Set<string>()
for (const step of inflectionBreakdown.flat())
{
const key = `${ step.sourceTerm };${ step.sourceCategory }`
if (seenInflections.has(key))
continue

seenInflections.add(key)
inflectionOf.push({
term: step.sourceTerm,
category: step.sourceCategory,
})
}
const inflectionOf = Inflection.flattenBreakdown(inflectionBreakdown)

const queryCanBeDefinition =
!Kana.hasJapanese(queryWithoutTags) ||
Expand Down
4 changes: 4 additions & 0 deletions backend/src/server/studylist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,9 @@ export function init(
if (typeof body.studylistId !== "string")
throw Api.Error.malformed

if (typeof body.attemptDeinflection !== "boolean")
throw Api.Error.malformed

if (!Array.isArray(body.words) ||
!body.words.every(w =>
typeof w.base === "string" &&
Expand All @@ -215,6 +218,7 @@ export function init(
const failedWordIndices = await db.studylistWordImport(
await ServerAuth.authenticateRequest(auth, req),
body.studylistId,
body.attemptDeinflection,
body.words)

res.send({ failedWordIndices } satisfies Api.StudylistWordImport.Response)
Expand Down
1 change: 1 addition & 0 deletions common/src/api/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,6 +495,7 @@ export namespace StudylistWordImport

export type Request = {
studylistId: string
attemptDeinflection: boolean
words: ImportWord[]
}

Expand Down
23 changes: 23 additions & 0 deletions common/src/inflection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,29 @@ export function breakdown(
}


export function flattenBreakdown(
inflectionBreakdown: Breakdown)
: Inflected[]
{
const inflectionOf: Inflected[] = []
const seenInflections = new Set<string>()
for (const step of inflectionBreakdown.flat())
{
const key = `${ step.sourceTerm };${ step.sourceCategory }`
if (seenInflections.has(key))
continue

seenInflections.add(key)
inflectionOf.push({
term: step.sourceTerm,
category: step.sourceCategory,
})
}

return inflectionOf
}


export function getRuleGroup(
id: string)
: Group | undefined
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ export type Prefs = {
studylistOrdering: "activity" | "name"
studylistWordOrdering: "date-added" | "kana"

studylistImportAttemptDeinflection: boolean

studylistExportHtmlCss: boolean
studylistExportKanjiLevel: "common" | "jouyou" | "uncommon" | "rare" | "all"
studylistExportSkipKatakana: boolean
Expand All @@ -47,6 +49,8 @@ export const prefsDefault: Prefs = {
studylistOrdering: "activity",
studylistWordOrdering: "date-added",

studylistImportAttemptDeinflection: false,

studylistExportHtmlCss: true,
studylistExportKanjiLevel: "common",
studylistExportSkipKatakana: false,
Expand Down
15 changes: 14 additions & 1 deletion frontend/src/pages/PageStudylist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,8 @@ export function PageStudylist(props: Framework.RouteProps)
}>
<HelpInfo>
Use the <Framework.IconBookmark color={ Framework.themeVar("iconGreenColor") }/> button
displayed alongside the results of a search to add words to this study list!
displayed alongside the results of a search to add words to this study list!<br/>
Alternatively, use the Import button above to add words listed in a file.
</HelpInfo>
</Solid.Show>

Expand Down Expand Up @@ -795,6 +796,7 @@ function ImportPopup(props: {
{
const res = await App.Api.studylistWordImport({
studylistId: props.studylist.id,
attemptDeinflection: App.usePrefs().studylistImportAttemptDeinflection,
words: words.slice(i, i + packLen),
})

Expand All @@ -820,6 +822,7 @@ function ImportPopup(props: {
"\n"
}

console.error(text)
alert(text)
}
else
Expand Down Expand Up @@ -894,6 +897,16 @@ function ImportPopup(props: {
</ImportWordGrid>

<br/>

<Framework.Checkbox
label="Attempt de-inflection"
value={ () => App.usePrefs().studylistImportAttemptDeinflection }
onChange={ (value) => App.mergePrefs({ studylistImportAttemptDeinflection: value }) }
/>

<br/>
<br/>

<Framework.Button
icon={ <Framework.IconUpload/> }
label="Import"
Expand Down

0 comments on commit ad0830b

Please sign in to comment.