MetaList 0.1.6__tar.gz → 0.1.7__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of MetaList might be problematic. Click here for more details.

Files changed (91) hide show
  1. MetaList-0.1.7/MetaList.egg-info/PKG-INFO +110 -0
  2. {MetaList-0.1.6 → MetaList-0.1.7}/MetaList.egg-info/SOURCES.txt +4 -3
  3. MetaList-0.1.7/PKG-INFO +110 -0
  4. MetaList-0.1.7/README.md +92 -0
  5. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/app.py +107 -29
  6. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/config.py +3 -1
  7. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/css/components/chat-ui.css +25 -0
  8. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/css/components/items-list.css +5 -4
  9. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/js/app-state.js +3 -1
  10. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/js/components/chat-ui.js +77 -100
  11. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/js/components/items-list.js +40 -3
  12. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/js/components/search-bar.js +1 -1
  13. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/js/components/suggestions-list.js +1 -1
  14. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/js/misc/item-formatter.js +5 -6
  15. MetaList-0.1.6/metalist/static/js/misc/formats.js → MetaList-0.1.7/metalist/static/js/misc/parsing.js +113 -0
  16. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/js/misc/server-proxy.js +7 -0
  17. MetaList-0.1.7/metalist/utils/chat_prompts.py +220 -0
  18. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/utils/decorate_single_item.py +6 -4
  19. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/utils/find.py +3 -3
  20. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/utils/initialize.py +4 -0
  21. MetaList-0.1.7/metalist/utils/search_index.py +62 -0
  22. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/utils/search_suggestions.py +11 -1
  23. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/utils/server.py +42 -16
  24. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/utils/tags_suggestions.py +21 -7
  25. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/utils/update_multiple_items.py +10 -15
  26. {MetaList-0.1.6 → MetaList-0.1.7}/setup.py +2 -2
  27. {MetaList-0.1.6 → MetaList-0.1.7}/tests/test_utils.py +2 -2
  28. MetaList-0.1.6/MetaList.egg-info/PKG-INFO +0 -59
  29. MetaList-0.1.6/PKG-INFO +0 -59
  30. MetaList-0.1.6/README.md +0 -41
  31. MetaList-0.1.6/metalist/static/js/misc/LLMs.js +0 -252
  32. {MetaList-0.1.6 → MetaList-0.1.7}/LICENSE +0 -0
  33. {MetaList-0.1.6 → MetaList-0.1.7}/MetaList.egg-info/dependency_links.txt +0 -0
  34. {MetaList-0.1.6 → MetaList-0.1.7}/MetaList.egg-info/entry_points.txt +0 -0
  35. {MetaList-0.1.6 → MetaList-0.1.7}/MetaList.egg-info/requires.txt +0 -0
  36. {MetaList-0.1.6 → MetaList-0.1.7}/MetaList.egg-info/top_level.txt +0 -0
  37. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/__init__.py +0 -0
  38. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/css/app.css +0 -0
  39. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/css/components/search-bar.css +0 -0
  40. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/css/components/suggestions-list.css +0 -0
  41. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/css/components/tags-bar.css +0 -0
  42. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/css/dark-theme.css +0 -0
  43. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/css/reserved-tags.css +0 -0
  44. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/css/unreset.css +0 -0
  45. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/html/index.html +0 -0
  46. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/img/caret-down-filled.svg +0 -0
  47. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/img/caret-right-filled.svg +0 -0
  48. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/img/chat-left-svgrepo-com.svg +0 -0
  49. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/img/checkbox-checked.svg +0 -0
  50. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/img/checkbox-unchecked.svg +0 -0
  51. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/img/favicon.ico +0 -0
  52. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/img/search.svg +0 -0
  53. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/img/tag.svg +0 -0
  54. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/js/components/tags-bar.js +0 -0
  55. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/js/config.js +0 -0
  56. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/js/misc/logging.js +0 -0
  57. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/js/misc/scrolling.js +0 -0
  58. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/js/misc/vdom.js +0 -0
  59. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/js/pub-sub-events.js +0 -0
  60. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/PubSubJS/LICENSE.md +0 -0
  61. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/PubSubJS/pubsub.js +0 -0
  62. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/katex/LICENSE +0 -0
  63. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/katex/contrib/auto-render.min.js +0 -0
  64. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/katex/contrib/copy-tex.min.css +0 -0
  65. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/katex/contrib/copy-tex.min.js +0 -0
  66. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/katex/contrib/mathtex-script-type.min.js +0 -0
  67. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/katex/katex.min.css +0 -0
  68. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/katex/katex.min.js +0 -0
  69. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/lodash/LICENSE +0 -0
  70. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/lodash/lodash.js +0 -0
  71. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/markdown-it/LICENSE +0 -0
  72. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/markdown-it/markdown-it.min.js +0 -0
  73. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/micronitjs/LICENSE +0 -0
  74. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/micronitjs/micronit.js +0 -0
  75. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/the-new-css-reset/LICENSE +0 -0
  76. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/the-new-css-reset/reset.css +0 -0
  77. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/voca/LICENSE.md +0 -0
  78. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/libs/voca/voca.min.js +0 -0
  79. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/tests/unit-tests/run-tests.html +0 -0
  80. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/static/tests/unit-tests/test-search-bar.js +0 -0
  81. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/utils/__init__.py +0 -0
  82. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/utils/crud.py +0 -0
  83. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/utils/generate.py +0 -0
  84. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/utils/misc/__init__.py +0 -0
  85. /MetaList-0.1.6/metalist/utils/misc/crypto.py → /MetaList-0.1.7/metalist/utils/misc/crypto-test.py +0 -0
  86. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/utils/ontology.py +0 -0
  87. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/utils/reporting.py +0 -0
  88. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/utils/search_filters.py +0 -0
  89. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/utils/snapshots.py +0 -0
  90. {MetaList-0.1.6 → MetaList-0.1.7}/metalist/utils/update_single_item.py +0 -0
  91. {MetaList-0.1.6 → MetaList-0.1.7}/setup.cfg +0 -0
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.1
2
+ Name: MetaList
3
+ Version: 0.1.7
4
+ Summary: A PKM web app integrated with LLMs
5
+ Home-page: https://github.com/evolvingstuff/MetaList
6
+ Author: Thomas Lahore
7
+ Author-email: tom.lahore@gmail.com
8
+ Classifier: Programming Language :: Python :: 3.8
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: bottle==0.12.25
15
+ Requires-Dist: bottle-sqlite==0.2.0
16
+ Requires-Dist: beautifulsoup4~=4.12.2
17
+ Requires-Dist: tqdm==4.66.1
18
+
19
+
20
+ # MetaList
21
+
22
+ <img src="docs/img/MetaList-Logo.png" width="130" />
23
+
24
+ [![PyPI](https://img.shields.io/pypi/v/metalist)](https://pypi.org/project/metalist/)
25
+ ![PyPI - Python Version](https://img.shields.io/badge/python-3.8+-blue)
26
+ ![Runs on Linux | MacOS | Windows](https://img.shields.io/badge/runs%20on-Linux%20%7C%20MacOS%20%7C%20Windows-blue)
27
+
28
+
29
+ ## What is MetaList?
30
+ MetaList is a personal knowledge management web application,
31
+ tightly integrated with large language models.
32
+
33
+ > ⚠️ **MetaList is currently in Development**: Expect breaking changes and bugs
34
+
35
+ ## Features
36
+
37
+ - [x] Chat feature: LLM integration (currently only [OpenAI API](https://openai.com/blog/openai-api))
38
+ - [x] [Chart.js](https://www.chartjs.org/) integration for LLM-generated visualizations
39
+ - [x] Hierarchical, collapsable note structure
40
+ - [x] Reordering notes in one context survives to other search contexts; notes maintain a [total order](https://en.wikipedia.org/wiki/Total_order)
41
+ - [x] Quick, responsive searching (tags, full-text, or combo of both)
42
+ - [x] Search suggestions
43
+ - [x] Tagging
44
+ - [x] Tag suggestions (uses a [Jaccard index](https://en.wikipedia.org/wiki/Jaccard_index))
45
+ - [x] Tags can imply other tags
46
+ - [x] Tag implication rules are also notes; they can themselves be tagged and searched
47
+ - [x] Nested subitems in notes inherit tags from their parents
48
+ - [x] [Markdown](https://markdown-it.github.io/)/[LaTeX](https://katex.org/) support
49
+ - [x] Infinite undo/redo
50
+ - [x] Infinite scrolling
51
+
52
+ ### Chat Feature Example
53
+
54
+ Search filters notes down to a relevant subset (a "search context"), which has the nice
55
+ effect of acting as a form of human-guided [retrieval augmented generation](https://www.promptingguide.ai/techniques/rag).
56
+ The chat feature is fully integrated with the underlying notes. Mousing over a reference in the chat will highlight
57
+ the referenced note, if it is within view, and clicking on the reference will navigate to and select the note,
58
+ even opening up collapsed items in the note hierarchy so that all the relevant info becomes visible.
59
+
60
+ Some examples of interactions with the chat feature (in this case, recommending new movies):
61
+
62
+ <img src="docs/img/demo-1.gif" width="800"/>
63
+ <img src="docs/img/chat-example-2.png" width="800"/>
64
+ <!--<img src="docs/img/chat-example-3.png" width="800"/>-->
65
+
66
+ ## Installation
67
+
68
+ It is recommended that you create a Python virtual environment before installing MetaList.
69
+
70
+ ```bash
71
+ python3 -m venv venv
72
+ source venv/bin/activate
73
+ ```
74
+
75
+ Then, install MetaList using pip:
76
+
77
+ ```bash
78
+ pip install metalist
79
+ ```
80
+
81
+ ## Usage
82
+
83
+ From the installation directory, run:
84
+ ```
85
+ metalist
86
+ ```
87
+
88
+ Navigate to `http://127.0.0.0:8080/`
89
+
90
+ Read the [cheatsheet](docs/cheatsheet.md).
91
+
92
+ ## Roadmap
93
+
94
+ - [ ] Chat integration with other models
95
+ - [ ] Menus / Settings
96
+ - [ ] Reminders
97
+ - [ ] Passwords / encryption at rest
98
+ - [ ] Import data from browser bookmarks
99
+ - [ ] Import data from other PKM apps
100
+ - [ ] Export notes to JSON, Markdown, HTML, PDF
101
+ - [ ] File handling
102
+ - [ ] Access to local file system
103
+ - [ ] Multi-user
104
+ - [ ] Mobile friendly
105
+ - [ ] More unit/integration testing
106
+
107
+ ## License
108
+
109
+ MetaList is licensed under the MIT license. See [LICENSE](LICENSE) for more information.
110
+
@@ -36,10 +36,9 @@ metalist/static/js/components/items-list.js
36
36
  metalist/static/js/components/search-bar.js
37
37
  metalist/static/js/components/suggestions-list.js
38
38
  metalist/static/js/components/tags-bar.js
39
- metalist/static/js/misc/LLMs.js
40
- metalist/static/js/misc/formats.js
41
39
  metalist/static/js/misc/item-formatter.js
42
40
  metalist/static/js/misc/logging.js
41
+ metalist/static/js/misc/parsing.js
43
42
  metalist/static/js/misc/scrolling.js
44
43
  metalist/static/js/misc/server-proxy.js
45
44
  metalist/static/js/misc/vdom.js
@@ -65,6 +64,7 @@ metalist/static/libs/voca/voca.min.js
65
64
  metalist/static/tests/unit-tests/run-tests.html
66
65
  metalist/static/tests/unit-tests/test-search-bar.js
67
66
  metalist/utils/__init__.py
67
+ metalist/utils/chat_prompts.py
68
68
  metalist/utils/crud.py
69
69
  metalist/utils/decorate_single_item.py
70
70
  metalist/utils/find.py
@@ -73,6 +73,7 @@ metalist/utils/initialize.py
73
73
  metalist/utils/ontology.py
74
74
  metalist/utils/reporting.py
75
75
  metalist/utils/search_filters.py
76
+ metalist/utils/search_index.py
76
77
  metalist/utils/search_suggestions.py
77
78
  metalist/utils/server.py
78
79
  metalist/utils/snapshots.py
@@ -80,5 +81,5 @@ metalist/utils/tags_suggestions.py
80
81
  metalist/utils/update_multiple_items.py
81
82
  metalist/utils/update_single_item.py
82
83
  metalist/utils/misc/__init__.py
83
- metalist/utils/misc/crypto.py
84
+ metalist/utils/misc/crypto-test.py
84
85
  tests/test_utils.py
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.1
2
+ Name: MetaList
3
+ Version: 0.1.7
4
+ Summary: A PKM web app integrated with LLMs
5
+ Home-page: https://github.com/evolvingstuff/MetaList
6
+ Author: Thomas Lahore
7
+ Author-email: tom.lahore@gmail.com
8
+ Classifier: Programming Language :: Python :: 3.8
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: bottle==0.12.25
15
+ Requires-Dist: bottle-sqlite==0.2.0
16
+ Requires-Dist: beautifulsoup4~=4.12.2
17
+ Requires-Dist: tqdm==4.66.1
18
+
19
+
20
+ # MetaList
21
+
22
+ <img src="docs/img/MetaList-Logo.png" width="130" />
23
+
24
+ [![PyPI](https://img.shields.io/pypi/v/metalist)](https://pypi.org/project/metalist/)
25
+ ![PyPI - Python Version](https://img.shields.io/badge/python-3.8+-blue)
26
+ ![Runs on Linux | MacOS | Windows](https://img.shields.io/badge/runs%20on-Linux%20%7C%20MacOS%20%7C%20Windows-blue)
27
+
28
+
29
+ ## What is MetaList?
30
+ MetaList is a personal knowledge management web application,
31
+ tightly integrated with large language models.
32
+
33
+ > ⚠️ **MetaList is currently in Development**: Expect breaking changes and bugs
34
+
35
+ ## Features
36
+
37
+ - [x] Chat feature: LLM integration (currently only [OpenAI API](https://openai.com/blog/openai-api))
38
+ - [x] [Chart.js](https://www.chartjs.org/) integration for LLM-generated visualizations
39
+ - [x] Hierarchical, collapsable note structure
40
+ - [x] Reordering notes in one context survives to other search contexts; notes maintain a [total order](https://en.wikipedia.org/wiki/Total_order)
41
+ - [x] Quick, responsive searching (tags, full-text, or combo of both)
42
+ - [x] Search suggestions
43
+ - [x] Tagging
44
+ - [x] Tag suggestions (uses a [Jaccard index](https://en.wikipedia.org/wiki/Jaccard_index))
45
+ - [x] Tags can imply other tags
46
+ - [x] Tag implication rules are also notes; they can themselves be tagged and searched
47
+ - [x] Nested subitems in notes inherit tags from their parents
48
+ - [x] [Markdown](https://markdown-it.github.io/)/[LaTeX](https://katex.org/) support
49
+ - [x] Infinite undo/redo
50
+ - [x] Infinite scrolling
51
+
52
+ ### Chat Feature Example
53
+
54
+ Search filters notes down to a relevant subset (a "search context"), which has the nice
55
+ effect of acting as a form of human-guided [retrieval augmented generation](https://www.promptingguide.ai/techniques/rag).
56
+ The chat feature is fully integrated with the underlying notes. Mousing over a reference in the chat will highlight
57
+ the referenced note, if it is within view, and clicking on the reference will navigate to and select the note,
58
+ even opening up collapsed items in the note hierarchy so that all the relevant info becomes visible.
59
+
60
+ Some examples of interactions with the chat feature (in this case, recommending new movies):
61
+
62
+ <img src="docs/img/demo-1.gif" width="800"/>
63
+ <img src="docs/img/chat-example-2.png" width="800"/>
64
+ <!--<img src="docs/img/chat-example-3.png" width="800"/>-->
65
+
66
+ ## Installation
67
+
68
+ It is recommended that you create a Python virtual environment before installing MetaList.
69
+
70
+ ```bash
71
+ python3 -m venv venv
72
+ source venv/bin/activate
73
+ ```
74
+
75
+ Then, install MetaList using pip:
76
+
77
+ ```bash
78
+ pip install metalist
79
+ ```
80
+
81
+ ## Usage
82
+
83
+ From the installation directory, run:
84
+ ```
85
+ metalist
86
+ ```
87
+
88
+ Navigate to `http://127.0.0.0:8080/`
89
+
90
+ Read the [cheatsheet](docs/cheatsheet.md).
91
+
92
+ ## Roadmap
93
+
94
+ - [ ] Chat integration with other models
95
+ - [ ] Menus / Settings
96
+ - [ ] Reminders
97
+ - [ ] Passwords / encryption at rest
98
+ - [ ] Import data from browser bookmarks
99
+ - [ ] Import data from other PKM apps
100
+ - [ ] Export notes to JSON, Markdown, HTML, PDF
101
+ - [ ] File handling
102
+ - [ ] Access to local file system
103
+ - [ ] Multi-user
104
+ - [ ] Mobile friendly
105
+ - [ ] More unit/integration testing
106
+
107
+ ## License
108
+
109
+ MetaList is licensed under the MIT license. See [LICENSE](LICENSE) for more information.
110
+
@@ -0,0 +1,92 @@
1
+
2
+ # MetaList
3
+
4
+ <img src="docs/img/MetaList-Logo.png" width="130" />
5
+
6
+ [![PyPI](https://img.shields.io/pypi/v/metalist)](https://pypi.org/project/metalist/)
7
+ ![PyPI - Python Version](https://img.shields.io/badge/python-3.8+-blue)
8
+ ![Runs on Linux | MacOS | Windows](https://img.shields.io/badge/runs%20on-Linux%20%7C%20MacOS%20%7C%20Windows-blue)
9
+
10
+
11
+ ## What is MetaList?
12
+ MetaList is a personal knowledge management web application,
13
+ tightly integrated with large language models.
14
+
15
+ > ⚠️ **MetaList is currently in Development**: Expect breaking changes and bugs
16
+
17
+ ## Features
18
+
19
+ - [x] Chat feature: LLM integration (currently only [OpenAI API](https://openai.com/blog/openai-api))
20
+ - [x] [Chart.js](https://www.chartjs.org/) integration for LLM-generated visualizations
21
+ - [x] Hierarchical, collapsable note structure
22
+ - [x] Reordering notes in one context survives to other search contexts; notes maintain a [total order](https://en.wikipedia.org/wiki/Total_order)
23
+ - [x] Quick, responsive searching (tags, full-text, or combo of both)
24
+ - [x] Search suggestions
25
+ - [x] Tagging
26
+ - [x] Tag suggestions (uses a [Jaccard index](https://en.wikipedia.org/wiki/Jaccard_index))
27
+ - [x] Tags can imply other tags
28
+ - [x] Tag implication rules are also notes; they can themselves be tagged and searched
29
+ - [x] Nested subitems in notes inherit tags from their parents
30
+ - [x] [Markdown](https://markdown-it.github.io/)/[LaTeX](https://katex.org/) support
31
+ - [x] Infinite undo/redo
32
+ - [x] Infinite scrolling
33
+
34
+ ### Chat Feature Example
35
+
36
+ Search filters notes down to a relevant subset (a "search context"), which has the nice
37
+ effect of acting as a form of human-guided [retrieval augmented generation](https://www.promptingguide.ai/techniques/rag).
38
+ The chat feature is fully integrated with the underlying notes. Mousing over a reference in the chat will highlight
39
+ the referenced note, if it is within view, and clicking on the reference will navigate to and select the note,
40
+ even opening up collapsed items in the note hierarchy so that all the relevant info becomes visible.
41
+
42
+ Some examples of interactions with the chat feature (in this case, recommending new movies):
43
+
44
+ <img src="docs/img/demo-1.gif" width="800"/>
45
+ <img src="docs/img/chat-example-2.png" width="800"/>
46
+ <!--<img src="docs/img/chat-example-3.png" width="800"/>-->
47
+
48
+ ## Installation
49
+
50
+ It is recommended that you create a Python virtual environment before installing MetaList.
51
+
52
+ ```bash
53
+ python3 -m venv venv
54
+ source venv/bin/activate
55
+ ```
56
+
57
+ Then, install MetaList using pip:
58
+
59
+ ```bash
60
+ pip install metalist
61
+ ```
62
+
63
+ ## Usage
64
+
65
+ From the installation directory, run:
66
+ ```
67
+ metalist
68
+ ```
69
+
70
+ Navigate to `http://127.0.0.0:8080/`
71
+
72
+ Read the [cheatsheet](docs/cheatsheet.md).
73
+
74
+ ## Roadmap
75
+
76
+ - [ ] Chat integration with other models
77
+ - [ ] Menus / Settings
78
+ - [ ] Reminders
79
+ - [ ] Passwords / encryption at rest
80
+ - [ ] Import data from browser bookmarks
81
+ - [ ] Import data from other PKM apps
82
+ - [ ] Export notes to JSON, Markdown, HTML, PDF
83
+ - [ ] File handling
84
+ - [ ] Access to local file system
85
+ - [ ] Multi-user
86
+ - [ ] Mobile friendly
87
+ - [ ] More unit/integration testing
88
+
89
+ ## License
90
+
91
+ MetaList is licensed under the MIT license. See [LICENSE](LICENSE) for more information.
92
+
@@ -1,12 +1,15 @@
1
1
  import os
2
- from bottle import Bottle, run, static_file, request
2
+ from typing import List
3
+ from bottle import Bottle, run, static_file, request, response
3
4
  import bottle_sqlite
4
-
5
- from metalist.config import reset_undo_stack_on_search, port
5
+ import requests
6
+ from metalist.config import reset_undo_stack_on_search, port, open_ai_url, open_ai_model, development_mode
7
+ from metalist.utils.chat_prompts import build_initial_prompts, build_selection_prompt
6
8
  from metalist.utils.crud import get_database_path
7
9
  from metalist.utils.search_suggestions import calculate_search_suggestions
8
10
  from metalist.utils.server import get_request_context, \
9
- generic_response, noop_response, error_response, filter_items, Context
11
+ generic_response, noop_response, error_response, filter_and_sort_items, Context, chat_response
12
+ from metalist.utils.search_index import SearchIndex
10
13
  from metalist.utils.tags_suggestions import calculate_tags_suggestions
11
14
  from metalist.utils.initialize import initialize_cache
12
15
  from metalist.utils.snapshots import Snapshots, SnapshotFragment, Snapshot, compress_snapshots
@@ -16,6 +19,7 @@ from metalist.utils import update_single_item, update_multiple_items
16
19
 
17
20
  cache = {}
18
21
  snapshots = Snapshots()
22
+ chat_history: List[dict] = list()
19
23
 
20
24
  app = Bottle()
21
25
  plugin = bottle_sqlite.Plugin(dbfile=get_database_path())
@@ -23,6 +27,8 @@ app.install(plugin)
23
27
 
24
28
 
25
29
  def run_app():
30
+ if development_mode:
31
+ print('DEV MODE = True')
26
32
  initialize_cache(cache)
27
33
  run(app, port=port, debug=False)
28
34
 
@@ -91,7 +97,7 @@ def todo(db):
91
97
  context = get_request_context(request, cache)
92
98
  snap_pre = SnapshotFragment(cache, context.item_subitem_id)
93
99
  update_single_item.todo(db, context, cache)
94
- filtered_items, reached_scroll_end = filter_items(cache, context)
100
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context)
95
101
  snap_post = SnapshotFragment(cache, context.item_subitem_id)
96
102
  snapshot = Snapshot('/todo', snap_pre, snap_post, context.item_subitem_id)
97
103
  snapshots.push(snapshot)
@@ -111,7 +117,7 @@ def done(db):
111
117
  context = get_request_context(request, cache)
112
118
  snap_pre = SnapshotFragment(cache, context.item_subitem_id)
113
119
  update_single_item.done(db, context, cache)
114
- filtered_items, reached_scroll_end = filter_items(cache, context)
120
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context)
115
121
  snap_post = SnapshotFragment(cache, context.item_subitem_id)
116
122
  snapshot = Snapshot('/done', snap_pre, snap_post, context.item_subitem_id)
117
123
  snapshots.push(snapshot)
@@ -131,7 +137,7 @@ def expand(db):
131
137
  context = get_request_context(request, cache)
132
138
  snap_pre = SnapshotFragment(cache, context.item_subitem_id)
133
139
  update_single_item.expand(db, context, cache)
134
- filtered_items, reached_scroll_end = filter_items(cache, context)
140
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context)
135
141
  snap_post = SnapshotFragment(cache, context.item_subitem_id)
136
142
  snapshot = Snapshot('/expand', snap_pre, snap_post, context.item_subitem_id)
137
143
  snapshots.push(snapshot)
@@ -151,7 +157,7 @@ def collapse(db):
151
157
  context = get_request_context(request, cache)
152
158
  snap_pre = SnapshotFragment(cache, context.item_subitem_id)
153
159
  update_single_item.collapse(db, context, cache)
154
- filtered_items, reached_scroll_end = filter_items(cache, context)
160
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context)
155
161
  snap_post = SnapshotFragment(cache, context.item_subitem_id)
156
162
  snapshot = Snapshot('/collapse', snap_pre, snap_post, context.item_subitem_id)
157
163
  snapshots.push(snapshot)
@@ -175,7 +181,7 @@ def delete_subitem(db):
175
181
  must_recalculate_ontology = update_multiple_items.remove_item(db, cache, context)
176
182
  if must_recalculate_ontology:
177
183
  update_multiple_items.recalculate_ontology(db, cache, context)
178
- filtered_items, reached_scroll_end = filter_items(cache, context, dirty_ranking=True)
184
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context, dirty_ranking=True)
179
185
  snap_post = SnapshotFragment(cache, None)
180
186
  snapshot = Snapshot('/delete-subitem (item)', snap_pre, snap_post, context.item_subitem_id)
181
187
  snapshots.push(snapshot)
@@ -186,7 +192,7 @@ def delete_subitem(db):
186
192
  must_recalculate_ontology = update_single_item.delete_subitem(db, context, cache)
187
193
  if must_recalculate_ontology:
188
194
  update_multiple_items.recalculate_ontology(db, cache, context)
189
- filtered_items, reached_scroll_end = filter_items(cache, context)
195
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context)
190
196
  snap_post = SnapshotFragment(cache, None)
191
197
  snapshot = Snapshot('/delete-subitem', snap_pre, snap_post, context.item_subitem_id)
192
198
  snapshots.push(snapshot)
@@ -227,7 +233,7 @@ def move_item_up(db):
227
233
  context = get_request_context(request, cache)
228
234
  snap_pre = SnapshotFragment(cache, context.item_subitem_id)
229
235
  update_multiple_items.move_item_up(db, cache, context)
230
- filtered_items, reached_scroll_end = filter_items(cache, context, dirty_ranking=True)
236
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context, dirty_ranking=True)
231
237
  snap_post = SnapshotFragment(cache, context.item_subitem_id)
232
238
  snapshot = Snapshot('/move-item-up', snap_pre, snap_post, context.item_subitem_id)
233
239
  snapshots.push(snapshot)
@@ -247,7 +253,7 @@ def move_item_down(db):
247
253
  context = get_request_context(request, cache)
248
254
  snap_pre = SnapshotFragment(cache, context.item_subitem_id)
249
255
  update_multiple_items.move_item_down(db, cache, context)
250
- filtered_items, reached_scroll_end = filter_items(cache, context, dirty_ranking=True)
256
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context, dirty_ranking=True)
251
257
  snap_post = SnapshotFragment(cache, context.item_subitem_id)
252
258
  snapshot = Snapshot('/move-item-down', snap_pre, snap_post, context.item_subitem_id)
253
259
  snapshots.push(snapshot)
@@ -267,7 +273,7 @@ def move_subitem_up(db):
267
273
  context = get_request_context(request, cache)
268
274
  snap_pre = SnapshotFragment(cache, context.item_subitem_id)
269
275
  new_item_subitem_id = update_single_item.move_subitem_up(db, context, cache)
270
- filtered_items, reached_scroll_end = filter_items(cache, context)
276
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context)
271
277
  snap_post = SnapshotFragment(cache, new_item_subitem_id)
272
278
  snapshot = Snapshot('/move-subitem-up', snap_pre, snap_post, context.item_subitem_id)
273
279
  snapshots.push(snapshot)
@@ -287,7 +293,7 @@ def move_subitem_down(db):
287
293
  context = get_request_context(request, cache)
288
294
  snap_pre = SnapshotFragment(cache, context.item_subitem_id)
289
295
  new_item_subitem_id = update_single_item.move_subitem_down(db, context, cache)
290
- filtered_items, reached_scroll_end = filter_items(cache, context)
296
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context)
291
297
  snap_post = SnapshotFragment(cache, new_item_subitem_id)
292
298
  snapshot = Snapshot('/move-subitem-down', snap_pre, snap_post, context.item_subitem_id)
293
299
  snapshots.push(snapshot)
@@ -307,7 +313,7 @@ def indent(db):
307
313
  context = get_request_context(request, cache)
308
314
  snap_pre = SnapshotFragment(cache, context.item_subitem_id)
309
315
  update_single_item.indent(db, context, cache)
310
- filtered_items, reached_scroll_end = filter_items(cache, context)
316
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context)
311
317
  snap_post = SnapshotFragment(cache, context.item_subitem_id)
312
318
  snapshot = Snapshot('/indent', snap_pre, snap_post, context.item_subitem_id)
313
319
  snapshots.push(snapshot)
@@ -327,7 +333,7 @@ def outdent(db):
327
333
  context = get_request_context(request, cache)
328
334
  snap_pre = SnapshotFragment(cache, context.item_subitem_id)
329
335
  update_single_item.outdent(db, context, cache)
330
- filtered_items, reached_scroll_end = filter_items(cache, context)
336
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context)
331
337
  snap_post = SnapshotFragment(cache, context.item_subitem_id)
332
338
  snapshot = Snapshot('/outdent', snap_pre, snap_post, context.item_subitem_id)
333
339
  snapshots.push(snapshot)
@@ -345,8 +351,7 @@ def search(db):
345
351
  context = get_request_context(request, cache)
346
352
  # TODO why recreate context?
347
353
  context = Context(context.app_state, search_text=context.search_text, search_filter=context.search_filter)
348
- filtered_items, reached_scroll_end = filter_items(cache, context, updated_search=True, dirty_ranking=True)
349
- print(f'debug: reached_scroll_end = {reached_scroll_end}')
354
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context, updated_search=True, dirty_ranking=True)
350
355
  if reset_undo_stack_on_search:
351
356
  snapshots.reset()
352
357
  else:
@@ -385,7 +390,7 @@ def add_item_sibling(db):
385
390
  context = get_request_context(request, cache)
386
391
  snap_pre = SnapshotFragment(cache, context.item_subitem_id)
387
392
  new_item_subitem_id = update_multiple_items.add_item_sibling(db, cache, context)
388
- filtered_items, reached_scroll_end = filter_items(cache, context, dirty_ranking=True)
393
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context, dirty_ranking=True)
389
394
  snap_post = SnapshotFragment(cache, new_item_subitem_id)
390
395
  snapshot = Snapshot('/add-item-sibling', snap_pre, snap_post, context.item_subitem_id)
391
396
  snapshots.push(snapshot)
@@ -401,7 +406,7 @@ def add_subitem_sibling(db):
401
406
  context = get_request_context(request, cache)
402
407
  snap_pre = SnapshotFragment(cache, context.item_subitem_id)
403
408
  new_item_subitem_id = update_single_item.add_subitem_sibling(db, context, cache)
404
- filtered_items, reached_scroll_end = filter_items(cache, context)
409
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context)
405
410
  snap_post = SnapshotFragment(cache, new_item_subitem_id)
406
411
  snapshot = Snapshot('/add-subitem-sibling', snap_pre, snap_post, context.item_subitem_id)
407
412
  snapshots.push(snapshot)
@@ -417,7 +422,7 @@ def add_subitem_child(db):
417
422
  context = get_request_context(request, cache)
418
423
  snap_pre = SnapshotFragment(cache, context.item_subitem_id)
419
424
  new_item_subitem_id = update_single_item.add_subitem_child(db, context, cache)
420
- filtered_items, reached_scroll_end = filter_items(cache, context)
425
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context)
421
426
  snap_post = SnapshotFragment(cache, new_item_subitem_id)
422
427
  snapshot = Snapshot('/add-subitem-child', snap_pre, snap_post, context.item_subitem_id)
423
428
  snapshots.push(snapshot)
@@ -436,7 +441,7 @@ def add_item_top(db):
436
441
  if len(context.search_filter['texts']) > 0 or context.search_filter['partial_text'] is not None:
437
442
  return error_response('Cannot add new items when using a text based search filter.')
438
443
  new_item_subitem_id = update_multiple_items.add_item_top(db, cache, context)
439
- filtered_items, reached_scroll_end = filter_items(cache, context, dirty_ranking=True)
444
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context, dirty_ranking=True)
440
445
  snap_post = SnapshotFragment(cache, new_item_subitem_id)
441
446
  snapshot = Snapshot('/add-item-top', snap_pre, snap_post, context.item_subitem_id)
442
447
  snapshots.push(snapshot)
@@ -453,7 +458,7 @@ def paste_sibling(db):
453
458
  snap_pre = SnapshotFragment(cache, context.item_subitem_id)
454
459
  assert context.clipboard is not None, 'missing clipboard from request'
455
460
  new_item_subitem_id = update_multiple_items.paste_sibling(db, cache, context)
456
- filtered_items, reached_scroll_end = filter_items(cache, context, dirty_ranking=True)
461
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context, dirty_ranking=True)
457
462
  snap_post = SnapshotFragment(cache, new_item_subitem_id)
458
463
  snapshot = Snapshot('/paste-sibling', snap_pre, snap_post, context.item_subitem_id)
459
464
  snapshots.push(snapshot)
@@ -470,7 +475,7 @@ def paste_child(db):
470
475
  snap_pre = SnapshotFragment(cache, context.item_subitem_id)
471
476
  assert context.clipboard is not None, 'missing clipboard from request'
472
477
  new_item_subitem_id = update_single_item.paste_child(db, context, cache)
473
- filtered_items, reached_scroll_end = filter_items(cache, context)
478
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context)
474
479
  snap_post = SnapshotFragment(cache, new_item_subitem_id)
475
480
  snapshot = Snapshot('/paste-child', snap_pre, snap_post, context.item_subitem_id)
476
481
  snapshots.push(snapshot)
@@ -484,7 +489,7 @@ def pagination_update(db):
484
489
  global cache, snapshots
485
490
  context = get_request_context(request, cache)
486
491
  # no snapshot
487
- filtered_items, reached_scroll_end = filter_items(cache, context)
492
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context)
488
493
  return generic_response(filtered_items, reached_scroll_end, new_item_subitem_id=context.item_subitem_id)
489
494
 
490
495
 
@@ -497,7 +502,7 @@ def update_tags(db):
497
502
  must_recalculate_ontology = update_single_item.update_tags(db, context, cache)
498
503
  if must_recalculate_ontology:
499
504
  update_multiple_items.recalculate_ontology(db, cache, context)
500
- filtered_items, reached_scroll_end = filter_items(cache, context)
505
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context)
501
506
  snap_post = SnapshotFragment(cache, context.item_subitem_id)
502
507
  snapshot = Snapshot('/update-tags', snap_pre, snap_post, context.item_subitem_id)
503
508
  snapshots.push(snapshot)
@@ -513,7 +518,7 @@ def open_to(db):
513
518
  context = get_request_context(request, cache)
514
519
  snap_pre = SnapshotFragment(cache, context.item_subitem_id)
515
520
  update_single_item.open_to(db, context, cache)
516
- filtered_items, reached_scroll_end = filter_items(cache, context)
521
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context)
517
522
  snap_post = SnapshotFragment(cache, context.item_subitem_id)
518
523
  snapshot = Snapshot('/open-to', snap_pre, snap_post, context.item_subitem_id)
519
524
  snapshots.push(snapshot)
@@ -530,7 +535,7 @@ def undo(db):
530
535
  # no snapshot
531
536
  try:
532
537
  new_item_subitem_id = update_multiple_items.undo(db, snapshots, cache)
533
- filtered_items, reached_scroll_end = filter_items(cache, context, dirty_ranking=True)
538
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context, dirty_ranking=True)
534
539
  crud.commit(db)
535
540
  return generic_response(filtered_items, reached_scroll_end, new_item_subitem_id=new_item_subitem_id)
536
541
  except Exception as e:
@@ -546,7 +551,7 @@ def redo(db):
546
551
  # no snapshot
547
552
  try:
548
553
  new_item_subitem_id = update_multiple_items.redo(db, snapshots, cache)
549
- filtered_items, reached_scroll_end = filter_items(cache, context, dirty_ranking=True)
554
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context, dirty_ranking=True)
550
555
  crud.commit(db)
551
556
  return generic_response(filtered_items, reached_scroll_end, new_item_subitem_id=new_item_subitem_id)
552
557
  except Exception as e:
@@ -554,5 +559,78 @@ def redo(db):
554
559
  return noop_response('nothing to redo')
555
560
 
556
561
 
562
+ @app.post('/chat-open')
563
+ def chat_open(db):
564
+ global chat_history
565
+ chat_history = [] # reset history
566
+ return noop_response('chat-open')
567
+
568
+
569
+ @app.post('/chat-close')
570
+ def chat_close(db):
571
+ global chat_history
572
+ chat_history = [] # reset history
573
+ return noop_response('chat-close')
574
+
575
+
576
+ @app.post('/chat-select-reference')
577
+ def chat_select_reference(db):
578
+ global chat_history, cache
579
+ context = get_request_context(request, cache)
580
+ system_message = build_selection_prompt(context)
581
+ chat_history.append(system_message)
582
+ return noop_response('chat-select-reference')
583
+
584
+
585
+ @app.post('/chat-send-message')
586
+ def chat_send_message(db):
587
+ global chat_history, cache
588
+ context = get_request_context(request, cache)
589
+ assert context.open_ai_api_key is not None, "OpenAI key is None"
590
+ if len(chat_history) == 0:
591
+ # get relevant items based on search filter
592
+ filtered_items, reached_scroll_end = filter_and_sort_items(cache, context, dirty_ranking=True)
593
+ prompts = build_initial_prompts(context, filtered_items)
594
+ for prompt in prompts:
595
+ chat_history.append(prompt)
596
+ else:
597
+ assert chat_history[0]['role'] == 'system'
598
+ user_message = {'role': 'user', 'content': context.chat_user_message}
599
+ chat_history.append(user_message)
600
+
601
+ # web_crawl_system_messages = process_user_message_for_urls(context.chat_user_message)
602
+ # chat_history.extend(web_crawl_system_messages)
603
+
604
+ ########################################################
605
+
606
+ # Prepare the payload for the OpenAI API
607
+ data = {
608
+ "model": open_ai_model,
609
+ "messages": chat_history
610
+ }
611
+ headers = {
612
+ "Authorization": f"Bearer {context.open_ai_api_key}",
613
+ "Content-Type": "application/json"
614
+ }
615
+
616
+ # Make a synchronous POST request to the OpenAI Chat API # TODO FastAPI
617
+ openai_response = requests.post(open_ai_url, json=data, headers=headers)
618
+
619
+ # Check if the request was successful and process the response
620
+ if openai_response.status_code == 200:
621
+ openai_data = openai_response.json()
622
+ assistant_message = openai_data['choices'][0]['message']
623
+ msg = {
624
+ 'role': assistant_message['role'],
625
+ 'content': assistant_message['content']
626
+ }
627
+ chat_history.append(msg)
628
+ return chat_response(chat_history)
629
+ else:
630
+ return error_response('bad response from open ai')
631
+
632
+
557
633
  if __name__ == '__main__':
634
+ global development_mode
635
+ development_mode = True
558
636
  run_app()