Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor flashcard low-level implementation structure #10471

Open
HowcanoeWang opened this issue Feb 29, 2024 · 151 comments
Open

Refactor flashcard low-level implementation structure #10471

HowcanoeWang opened this issue Feb 29, 2024 · 151 comments
Assignees
Labels
Milestone

Comments

@HowcanoeWang
Copy link

HowcanoeWang commented Feb 29, 2024

提供闪卡交互API

In what scenarios do you need this feature?

目前的闪卡只提供了 正面-反面 制卡,对于大部分问答式场景来说足够使用,但有时候也有翻转的需求(一个内容提供 正-反,反-正两张卡片)。

更进一步来说,对于部分学习的场景下,可能有更复杂的模板制卡的需求(Anki的)。如英语单词,最基本的需要准备 英语、中文、发音 三块内容,对应的需要制成 英-中(知道意思),中-英(中文意思到英语单词),音-中(听力卡片) 三张卡片才能比较好的掌握该单词。

虽然Anki可以很方便的实现上述功能,但Anki的卡组没有笔记的逻辑性,在数据超过上千的时候,即使使用标签也非常混乱,不利于知识库的维护。

因此想增强一下思源的闪卡功能,目前本人愿意开发模板制卡的插件,希望能提供或更新一下闪卡的API,目前想出来的插件可能需要有如下的API:

  1. 闪卡创建API
    从指定的块ID中创建卡片,不确定这个是不是已经有实现了?
    ginServer.Handle("POST", "/api/setting/setFlashcard", model.CheckAuth, model.CheckReadonly, setFlashcard)
    • API/flashcard/addFlashcard
    • parameters
      {
          “frontIDs”:["20210912214605-uhi5gco", ... ] // 允许多个块生成正面
          "backIDs":  ["20210912214605-uhi5gco", ... ] // 允许多个块生成反面
      }
      
    • return
      {
          "code": 0,
          "msg": "",
          "ID": "flashcard-2021xxxx"   // 闪卡的ID值,方便后续追踪与管理?
      }
      
  2. 闪卡编辑API
    编辑闪卡对应的正面内容和反面内容
    • API/flashcard/setFlashcard
    • parameters
      {
          “ID”:“flashcard-2021xxxx”  // 闪卡ID
          “frontIDs”:["20210912214605-uhi5gco", ... ]  // 允许多个块生成正面
          "backIDs":  ["20210912214605-uhi5gco",  ...]  // 允许多个块生成反面
      }
      
    • return
      {
           "code": 0,
           "msg": "",
           "data": null
      }
      
  3. 闪卡标记API
    输入一个块ID,打上制卡的颜色提示(可选项,或后续再讨论)
    图片

上述API修改,将闪卡制卡不仅限于父子块,允许完全独立的块生成闪卡(方便闪卡模板插件生成对应的markdown模板进行管理)


使用示例(插件调用API实现,不需要官方下场,仅用来说明应用场景)

调用插件,制卡,会在文档中插入一个如下的 无序列表块 来提供闪卡模板 (仅作示例,用户可以在插件中自定义来生成更复杂的
模板):

图片

对于整体的无序列表块,自定义如下属性:

图片

来追踪和管理,这个生成了哪些闪卡。

对于上面的每一个字段列表项 (如 * 字段1 (field-1)),有如下自定义属性:

图片

用来引导下面的所有子块作为模板填充选项。

根据用户的闪卡生成设置(如这个例子,生成3张单词表闪卡):

  1. 英-中(知道意思)闪卡
    正面是字段1 (field-1) 下面的所有子块,反面是 field-2 和field-3的所有子块 (用户可自定义)
  2. 中-英(中文意思到英语单词)闪卡
    正面是 field-2 下面的所有子块,反面是 field-1 和field-3的所有子块 (用户可自定义)
  3. 音-中(听力卡片)
    正面是 field-3 下面的所有子块,反面是 field-1 和field-2的所有子块 (用户可自定义)

Describe the optimal solution

官方实现翻转闪卡和用户自定义模板?感觉官方应该没有精力来搞

Describe the candidate solution

No response

Other information

No response

@suka233
Copy link

suka233 commented Feb 29, 2024

双手支持,并且希望插件能提供闪卡复习界面的一些事件,来开发出更高级的功能。
基本需要有如下的事件 or Api:进入闪卡复习界面,离开闪卡复习界面,当前的闪卡,当前闪卡的状态(处于正面or反面),用户点击闪卡复习按钮的回调(beforeReview这种),获取昨日未复习的闪卡。

基于以上,可以实现例如闪卡自动发音(可能会有什么安全限制?这个待验证),闪卡支持正面手动输入答案,背面自动评分,自动推迟昨日到期闪卡等等高级功能。

以上的部分功能虽然可以通过前端的MutationObserver实现,但是需要考虑的东西太多,远不如官方提供准确的事件来的方便。

@zxhd863943427
Copy link
Contributor

我觉得根本来说这是闪卡数据结构,还有类的设计问题。
用API来实现的话,之后扩展就比较麻烦。

关于双向闪卡,我个人的想法是最好把它视为更广泛一般闪卡的特殊情况,也就是一个块生成的多种闪卡中的一种,使用一种指定的方法,从块中抽取dom。

闪卡系统 drawio

对于闪卡系统,我粗略的想法是这样的,前期插件可以通过注册闪卡类,扩产闪卡类型,后期可以由思源本体提供模板管理功能。

@88250
Copy link
Member

88250 commented Feb 29, 2024

@HowcanoeWang 一张闪卡只能对应一个块,否则编辑器会有问题,所以添加闪卡的时候也是一张闪卡对应一个块的;关于双向制卡和模板制卡我估计基于现有结构很难实现了……

@HowcanoeWang
Copy link
Author

HowcanoeWang commented Feb 29, 2024

@88250 闪卡和单块绑定感觉限制太大了(比如你说的,编辑器会出问题)

有没有可能把闪卡在数据库中独立出来一个数据结构+编辑UI?

从数据库来说,每张闪卡有一个独立的ID,然后记录正面卡面包含的所有块ID,和反面卡片包含的块ID。
闪卡编辑器成为一个独立的悬浮窗,展示的内容实际上是块嵌入的UI

@zxhd863943427 闪卡独立ID后,还能增加你上面提到的不同闪卡类型,然后思源闪卡UI就能根据类型来进行不同的UI渲染

一切的基础都是要把闪卡从单个块绑定剥离

@HowcanoeWang
Copy link
Author

HowcanoeWang commented Feb 29, 2024

@88250 @zxhd863943427 @luo-chuan 比如在数据库中,闪卡独立出来,拥有如下的数据结构:

ID frontIDs backIDs 挖空 startdate nextdate suspend 备注
flashcard-xxx ['块ID1',’块ID2‘, ...] ['块ID3',’块ID4‘, ...] False 2024xx 2024yy true 正向闪卡
flashcard-yyy ['块ID3',’块ID4‘, ...] ['块ID1',’块ID2‘, ...] False 2024xx 2024yy true 反向闪卡(通过交换正反面包含的块ID来实现)
flashcard-zzz ['块ID3',’块ID4‘, ...] ['块ID1',’块ID2‘, ...] True 2024xx 2024yy true 挖空闪卡(前端渲染挖空)
flashcard-www [’块ID4‘,'<input>', ...] ['块ID1',’块ID2‘, ...] False 2024xx 2024yy true 使用input块来生成填空闪卡

这种数据结构,无缝兼容目前思源笔记所使用的闪卡和单个块绑定(把frontIDs改成绑定的单个块列表即可)

@zxhd863943427
Copy link
Contributor

@HowcanoeWang 一张闪卡只能对应一个块,否则编辑器会有问题,所以添加闪卡的时候也是一张闪卡对应一个块的;关于双向制卡和模板制卡我估计基于现有结构很难实现了……

能详细说一下为什么一张闪卡只能对应一个块,否则编辑器会有问题?

我自己在捣鼓实现的时候,拿废弃的卡包当成分类依据,也实现了一块多卡的demo。

@zxhd863943427
Copy link
Contributor

zxhd863943427 commented Feb 29, 2024

@HowcanoeWang

目前闪卡的具体结构不是这个样子的,参见这个:https://pkg.go.dev/github.com/open-spaced-repetition/go-fsrs#Card

我把它复制过来在下面方便看:

type Card struct {
	Due           [time](https://pkg.go.dev/time).[Time](https://pkg.go.dev/time#Time) `json:"Due"`
	Stability     [float64](https://pkg.go.dev/builtin#float64)   `json:"Stability"`
	Difficulty    [float64](https://pkg.go.dev/builtin#float64)   `json:"Difficulty"`
	ElapsedDays   [uint64](https://pkg.go.dev/builtin#uint64)    `json:"ElapsedDays"`
	ScheduledDays [uint64](https://pkg.go.dev/builtin#uint64)    `json:"ScheduledDays"`
	Reps          [uint64](https://pkg.go.dev/builtin#uint64)    `json:"Reps"`
	Lapses        [uint64](https://pkg.go.dev/builtin#uint64)    `json:"Lapses"`
	State         [State](https://pkg.go.dev/github.com/open-spaced-repetition/go-fsrs#State)     `json:"State"`
	LastReview    [time](https://pkg.go.dev/time).[Time](https://pkg.go.dev/time#Time) `json:"LastReview"`
}

思源目前的实现差不多就是在这个包的基础上浅浅地套了一层皮,改成你这个数据结构其实改动并不小。

@luo-chuan
Copy link

期待大佬们讨论出完美方案,我先围观。

@HowcanoeWang
Copy link
Author

思源目前的实现差不多就是在这个包的基础上浅浅地套了一层皮,改成你这个数据结构其实改动并不小。

@zxhd863943427 直接把哪些属性添加到上面数据结构表格的列就无缝对接了,但代码层面确实要改动不少。不过个人感觉赶紧把底层重新设计一下对拓展性来说更重要一点

@HowcanoeWang
Copy link
Author

类似的,可以参考Anki的数据结构:

以下是 Anki 数据库的一些核心表及其简要描述:

cards 表
存储有关每张卡片的信息,如复习时间、间隔、难易度、队列状态等。
字段包括 id(卡片ID)、nid(笔记ID)、did(牌组ID)、ord(卡片在笔记中的序号)、type、queue(卡片状态,如新卡片、复习中等)、due(下次复习时间)、ivl(间隔)、factor(难度系数)、reps(复习次数)、lapses(遗忘次数)等。

notes 表
存储笔记(生成一张或多张卡片的源信息)。
字段包括 id、guid(全局唯一标识符)、mid(模型ID)、mod(修改时间)、usn(更新序列号)、tags(标签)、flds(字段数据,包含笔记的实际内容)等。

decks 表
存储关于牌组的信息。
通常以 JSON 格式存储牌组设置和元数据。

revlog 表
存储复习日志,即每次复习发生时的详细信息。
字段包括 id、cid(卡片ID)、usn、ease(用户给出的评分)、ivl(新间隔)、lastIvl(上次间隔)、factor、time(复习时间,毫秒)、type(复习类型)等。

models 表
存储卡片和笔记的模型(或称类型),控制如何从笔记生成卡片,以及卡片的样式和布局。
通常以 JSON 格式存储字段、模板和CSS。

collection 表
存储关于整个集合的信息,如版本、最近同步时间等。
通常只有一行记录,包含配置和统计数据。

conf 表
存储配置信息,包括同步设置、界面选项等。

Anki 数据库可能还包含其他辅助表或索引,以改进性能和数据组织。要查看最新的数据库结构,您可以在 Anki 安装目录中找到数据库文件(通常名为 collection.anki2),使用 SQLite 工具打开它,并检查表结构。

@Jiangshuon
Copy link

双向制卡的应用场景主要是背单词是吧,包括闪卡语音功能也是,我尚未想到其他应用场景。其实我觉得背单词这种需求为啥不用专门的背单词软件,欧路词典、墨墨背单词、百词斩一大堆功能丰富的。用anki记单词,自己复制释义、例句吗?

如果是英语翻译、改错、完形填空这种学习,也用不到双向吧

@Jiangshuon
Copy link

话说其他笔记软件如logseq,obsidian插件怎么实现双向闪卡的呢?可以研究下吧

@Jiangshuon
Copy link

@88250 闪卡和单块绑定感觉限制太大了(比如你说的,编辑器会出问题)

有没有可能把闪卡在数据库中独立出来一个数据结构+编辑UI?

从数据库来说,每张闪卡有一个独立的ID,然后记录正面卡面包含的所有块ID,和反面卡片包含的块ID。 闪卡编辑器成为一个独立的悬浮窗,展示的内容实际上是块嵌入的UI

@zxhd863943427 闪卡独立ID后,还能增加你上面提到的不同闪卡类型,然后思源闪卡UI就能根据类型来进行不同的UI渲染

一切的基础都是要把闪卡从单个块绑定剥离

你是说独立出一个类似anki的制卡和复习页面吗?其实思源的优势就是在做笔记、复习笔记的过程中非常方便地将重要内容制作闪卡,在复习闪卡时非常方便地修改闪卡以及查看闪卡上下文(退出聚焦),笔记为主、闪卡为辅。

思源闪卡其实没有正面、反面的概念,只有问题、答案的概念,一张闪卡绑定一个问题块,前端根据问题块类型(列表块、超级块、标题块…)的不同,以不同的展示方式展示答案。这个anki的逻辑有很大差别。

在制卡方面,思源可以参照下其他笔记软件,毕竟anki不是笔记软件

@luo-chuan
Copy link

话说其他笔记软件如logseq,obsidian插件怎么实现双向闪卡的呢?可以研究下吧

这两的闪卡功能不值一提。参考remnote才有意义。

@luo-chuan
Copy link

双向制卡的应用场景主要是背单词是吧,包括闪卡语音功能也是,我尚未想到其他应用场景。其实我觉得背单词这种需求为啥不用专门的背单词软件,欧路词典、墨墨背单词、百词斩一大堆功能丰富的。用anki记单词,自己复制释义、例句吗?

如果是英语翻译、改错、完形填空这种学习,也用不到双向吧

双向卡很多学科都需要,举个例子。
正面:阿尔伯特·爱因斯坦
背面:犹太裔物理学家、诺贝尔物理学奖得主;主要成就:提出光量子假说,解决了光电效应问题,创立了狭义相对论、广义相对论等。

@88250
Copy link
Member

88250 commented Feb 29, 2024

后端改动主要是 Riff card 基类,将 BID 改成 frontIDs 和 backIDs,考虑到需要向前兼容,所以估计得保留 BID,新增 frontIDs 和 backIDs,使用过程中自动将 BID 迁移到 frontIDs 中,以及已有容器块子块迁移到 backIDs 中。

前端改动主要是闪卡管理界面,右侧需要类似嵌入块分栏多个块 @Vanessa219

这个 Issue 有点大,我估计得拆分一下(先占位,等考虑好后创建 issue 关联):

#10471 (comment) 这里提到的结构,挖空 字段估计不适合,因为思源挖空是个开关,用户随时可以打开或者关闭,不是在卡片上记录挖空状态的。

@88250 88250 changed the title 提供闪卡交互API Refactor flashcard low-level implementation structure Feb 29, 2024
@88250 88250 self-assigned this Feb 29, 2024
@88250 88250 added the Refactor label Feb 29, 2024
@88250 88250 added this to the backlog milestone Feb 29, 2024
@mozhux
Copy link

mozhux commented Feb 29, 2024

建议挖空变成工具栏feature也行呢,和标记区分开。

@luo-chuan
Copy link

建议挖空变成工具栏feature也行呢,和标记区分开。

对。既然要重构,那就重新考虑一下这个。

@zxhd863943427
Copy link
Contributor

后端改动主要是 Riff card 基类,将 BID 改成 frontIDs 和 backIDs,考虑到需要向前兼容,所以估计得保留 BID,新增 frontIDs 和 backIDs,使用过程中自动将 BID 迁移到 frontIDs 中,以及已有容器块子块迁移到 backIDs 中。

前端改动主要是闪卡管理界面,右侧需要类似嵌入块分栏多个块 @Vanessa219

这个 Issue 有点大,我估计得拆分一下(先占位,等考虑好后创建 issue 关联):

* 重构闪卡数据结构(本 issue),包含支持 [Flashcard support tag #10453](https://github.com/siyuan-note/siyuan/issues/10453) [Flashcard support flag #10233](https://github.com/siyuan-note/siyuan/issues/10233) [Flashcard support suspend #8832](https://github.com/siyuan-note/siyuan/issues/8832) 的底层结构,标签、旗标和暂停分别加字段

* 改进闪卡管理界面以支持一张卡片加载多块

* 提供闪卡 CRUD API

#10471 (comment) 这里提到的结构,挖空 字段估计不适合,因为思源挖空是个开关,用户随时可以打开或者关闭,不是在卡片上记录挖空状态的。

关联block的部分与卡片最好分开来,如果重构的话还是考虑参考anki的设计,从note的field字段中生成卡片。

总之这个不急着开工,先确定一个足够拓展性的结构再说。

@88250
Copy link
Member

88250 commented Feb 29, 2024

嗯,不急着开工的,这个改动有点大,我们先完成闪卡其他能够改进的点,这个需要再考虑一段时间,估计今年内都不一定能改到这里。

@zxhd863943427
Copy link
Contributor

感觉这方面可能需要适当提高速度了,不然依赖于当前结构的接口太多了到时候就不好改了。

@88250
Copy link
Member

88250 commented Mar 20, 2024

@zxhd863943427 感谢示意,我明白了。

现在整个方案应该是清晰了,稍后我整理一下方案大家再评估讨论看看。

时间上争取在下个月能开工。

@Jiangshuon
Copy link

D大,方案还没整理好吗

@88250
Copy link
Member

88250 commented Mar 21, 2024

还没开始……这两天事情太多了,我尽量抓紧。

@5kyfkr
Copy link

5kyfkr commented Apr 5, 2024

路过,顶顶

1 similar comment
@397334469
Copy link

路过,顶顶

@Jiangshuon
Copy link

顶顶,月末了!!心痒难赖

@88250
Copy link
Member

88250 commented May 1, 2024

大致整理了下方案:

原则

现有的 快速制卡 保持不变,提供 高级制卡

数据结构

修改 Card,增加字段:

  • SourceID 卡源 ID
  • Group 分组
  • Tag: string 标签,英文逗号分隔
  • Flag: string 旗标
  • Suspend: bool 是否暂停
  • Context: map<string, string> 扩展数据

新增 CardSource:

  • ID 卡源 ID
  • Type: string 类型
  • Data: string 根据类型不太的关联数据(比如 Block IDs 或者任何能够唯一标识的实体)
  • Context: map<string, string> 扩展数据

前端

TODO

版本计划

  • v3.1.0 中完成暂停、旗标和标签

@zxhd863943427
Copy link
Contributor

这个方案感觉没有太大问题。

@88250
Copy link
Member

88250 commented May 1, 2024

前端没有时间,后面考虑吧。

@zxhd863943427
Copy link
Contributor

前端没有时间,后面考虑吧。

返回的数据只是额外增加字段即可,前端不需要这么快变动。

@ZSCN2024
Copy link

ZSCN2024 commented May 3, 2024

思源闪卡缺少类似anki的卡片管理功能,请问是否有考虑过将闪卡和数据库结合?
数据库可以作为很好的闪卡管理载体。

image

image

@88250 88250 unpinned this issue May 30, 2024
@88250 88250 mentioned this issue Jun 5, 2024
4 tasks
@zxhd863943427
Copy link
Contributor

目前进度为 siyuan-note/riff#8

另外我感觉可以考虑不用返回全部闪卡,每一次只返回当前的闪卡即可。这样前端就不用考虑剩余闪卡的问题。

@kx1356
Copy link

kx1356 commented Oct 25, 2024

又过去2个月了,最近有什么消息吗?

@ZSCN2024
Copy link

又过去2个月了,最近有什么消息吗?

感觉要凉。一直在关注论坛和github的信息,闪卡这块没什么动静。我现在已经用anki了,思源单纯用来记录。但是从单纯记录的角度,感觉notion更方便快速,思源唯一的优势就是本地化,还在纠结中。

@TCOTC
Copy link
Contributor

TCOTC commented Oct 25, 2024

闪卡重构这边还在等上游算法更新

@Jiangshuon
Copy link

闪卡重构这边还在等上游算法更新

@zxhd863943427 z佬, 算法更新还会影响闪卡重构吗?不是独立的嘛,不会吧。。闪卡优化真是一波三折呀o(╥﹏╥)o

@ZSCN2024
Copy link

闪卡功能优化凉了吗?

@TCOTC
Copy link
Contributor

TCOTC commented Nov 24, 2024

不急

@kx1356
Copy link

kx1356 commented Nov 24, 2024

好事多磨吧

@kx1356
Copy link

kx1356 commented Dec 1, 2024

今天算法已经更新了,Upgrade FSRS to v3.3.0 #13323,是否3.2不远了。

@TCOTC
Copy link
Contributor

TCOTC commented Dec 1, 2024

其实还很遥远,可能要推迟

@ZSCN2024
Copy link

ZSCN2024 commented Dec 2, 2024

其实还很遥远,可能要推迟

💔💔💔

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests