khoj 1.17.1.dev229__py3-none-any.whl → 1.20.3__py3-none-any.whl
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.
- khoj/app/settings.py +7 -2
- khoj/configure.py +21 -0
- khoj/database/adapters/__init__.py +9 -5
- khoj/database/admin.py +2 -0
- khoj/database/migrations/0055_alter_agent_style_icon.py +37 -0
- khoj/database/models/__init__.py +2 -0
- khoj/interface/compiled/404/index.html +1 -0
- khoj/interface/compiled/_next/static/chunks/1603-fb2d80ae73990df3.js +1 -0
- khoj/interface/compiled/_next/static/chunks/2614-7cf01576d4457a75.js +3 -0
- khoj/interface/compiled/_next/static/chunks/3062-a42d847c919a9ea4.js +29 -0
- khoj/interface/compiled/_next/static/chunks/3678-8c0e55c3b5d83a22.js +25 -0
- khoj/interface/compiled/_next/static/chunks/4504-1629487c8bc82203.js +1 -0
- khoj/interface/compiled/_next/static/chunks/6297-d1c842ed3f714ab0.js +1 -0
- khoj/interface/compiled/_next/static/chunks/6648-ff677e51f1b2bcf1.js +1 -0
- khoj/interface/compiled/_next/static/chunks/7023-52c1be60135eb057.js +2 -0
- khoj/interface/compiled/_next/static/chunks/7071-b4711cecca6619a8.js +1 -0
- khoj/interface/compiled/_next/static/chunks/743-1a64254447cda71f.js +1 -0
- khoj/interface/compiled/_next/static/chunks/8423-132ea64eac83fd43.js +1 -0
- khoj/interface/compiled/_next/static/chunks/9001-acbca3e19b1a5ddf.js +21 -0
- khoj/interface/compiled/_next/static/chunks/9162-4a6d0d0dc5e27618.js +11 -0
- khoj/interface/compiled/_next/static/chunks/9178-859894e0d9d67aa5.js +1 -0
- khoj/interface/compiled/_next/static/chunks/9417-2e54c6fd056982d8.js +1 -0
- khoj/interface/compiled/_next/static/chunks/9693-91b03052c5cabded.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/_not-found/page-07ff4ab42b07845e.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/agents/layout-e71c8e913cccf792.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/agents/page-922694b75f1fb67b.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/automations/layout-27c28e923c9b1ff0.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/automations/page-46913728bcb8f4c6.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/chat/layout-8102549127db3067.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/chat/page-51ab7c4b766ff344.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/factchecker/layout-7b30c541c05fb904.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/factchecker/page-60be5e3295e2c0bc.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/layout-f3e40d346da53112.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/page-635b30b582201b05.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/search/layout-3720f1362310bebb.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/search/page-dcd385f03255ef36.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/settings/layout-6f9314b0d7a26046.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/settings/page-e83f6fa32691ca64.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/share/chat/layout-39f03f9e32399f0f.js +1 -0
- khoj/interface/compiled/_next/static/chunks/app/share/chat/page-699b364dc6fbf139.js +1 -0
- khoj/interface/compiled/_next/static/chunks/d3ac728e-a9e3522eef9b6b28.js +1 -0
- khoj/interface/compiled/_next/static/chunks/fd9d1056-2b978342deb60015.js +1 -0
- khoj/interface/compiled/_next/static/chunks/framework-8e0e0f4a6b83a956.js +33 -0
- khoj/interface/compiled/_next/static/chunks/main-175c164f5e0f026c.js +1 -0
- khoj/interface/compiled/_next/static/chunks/main-app-6d6ee3495efe03d4.js +1 -0
- khoj/interface/compiled/_next/static/chunks/pages/_app-f870474a17b7f2fd.js +1 -0
- khoj/interface/compiled/_next/static/chunks/pages/_error-c66a4e8afc46f17b.js +1 -0
- khoj/interface/compiled/_next/static/chunks/polyfills-78c92fac7aa8fdd8.js +1 -0
- khoj/interface/compiled/_next/static/chunks/webpack-97d9516936918753.js +1 -0
- khoj/interface/compiled/_next/static/css/1538cedb321e3a97.css +1 -0
- khoj/interface/compiled/_next/static/css/1de368beed21dfba.css +25 -0
- khoj/interface/compiled/_next/static/css/2272c73fc7a3b571.css +1 -0
- khoj/interface/compiled/_next/static/css/4cae6c0e5c72fb2d.css +1 -0
- khoj/interface/compiled/_next/static/css/553f9cdcc7a2bcd6.css +1 -0
- khoj/interface/compiled/_next/static/css/a22d83f18a32957e.css +1 -0
- khoj/interface/compiled/_next/static/css/a3530ec58b0b660f.css +1 -0
- khoj/interface/compiled/_next/static/css/b81e909d403fb2df.css +1 -0
- khoj/interface/compiled/_next/static/css/df6f4c34ec280d53.css +1 -0
- khoj/interface/compiled/_next/static/iCI2Ycgat9pFGG9HeJvvH/_buildManifest.js +1 -0
- khoj/interface/compiled/_next/static/iCI2Ycgat9pFGG9HeJvvH/_ssgManifest.js +1 -0
- khoj/interface/compiled/_next/static/media/0e790e04fd40ad16-s.p.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/4221e1667cd19c7d-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/6c276159aa0eb14b-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/6cc0b9500e4f9168-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/9d9319a7a2ac39c6-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_AMS-Regular.1608a09b.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_AMS-Regular.4aafdb68.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_AMS-Regular.a79f1c31.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Caligraphic-Bold.b6770918.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Caligraphic-Bold.cce5b8ec.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Caligraphic-Bold.ec17d132.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Caligraphic-Regular.07ef19e7.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Caligraphic-Regular.55fac258.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Caligraphic-Regular.dad44a7f.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Fraktur-Bold.9f256b85.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Fraktur-Bold.b18f59e1.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Fraktur-Bold.d42a5579.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Fraktur-Regular.7c187121.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Fraktur-Regular.d3c882a6.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Fraktur-Regular.ed38e79f.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Main-Bold.b74a1a8b.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Main-Bold.c3fb5ac2.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Main-Bold.d181c465.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Main-BoldItalic.6f2bb1df.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Main-BoldItalic.70d8b0a5.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Main-BoldItalic.e3f82f9d.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Main-Italic.47373d1e.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Main-Italic.8916142b.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Main-Italic.9024d815.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Main-Regular.0462f03b.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Main-Regular.7f51fe03.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Main-Regular.b7f8fe9b.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Math-BoldItalic.572d331f.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Math-BoldItalic.a879cf83.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Math-BoldItalic.f1035d8d.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Math-Italic.5295ba48.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Math-Italic.939bc644.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Math-Italic.f28c23ac.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_SansSerif-Bold.8c5b5494.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_SansSerif-Bold.94e1e8dc.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_SansSerif-Bold.bf59d231.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_SansSerif-Italic.3b1e59b3.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_SansSerif-Italic.7c9bc82b.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_SansSerif-Italic.b4c20c84.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_SansSerif-Regular.74048478.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_SansSerif-Regular.ba21ed5f.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_SansSerif-Regular.d4d7ba48.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Script-Regular.03e9641d.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Script-Regular.07505710.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Script-Regular.fe9cbbe1.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Size1-Regular.e1e279cb.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Size1-Regular.eae34984.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Size1-Regular.fabc004a.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Size2-Regular.57727022.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Size2-Regular.5916a24f.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Size2-Regular.d6b476ec.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Size3-Regular.9acaf01c.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Size3-Regular.a144ef58.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Size3-Regular.b4230e7e.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Size4-Regular.10d95fd3.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Size4-Regular.7a996c9d.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Size4-Regular.fbccdabe.ttf +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Typewriter-Regular.6258592b.woff +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Typewriter-Regular.a8709e36.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/KaTeX_Typewriter-Regular.d97aaf4a.ttf +0 -0
- khoj/interface/compiled/_next/static/media/a75c8ea86756d52d-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/abce7c400ca31a51-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/f759c939737fb668-s.woff2 +0 -0
- khoj/interface/compiled/_next/static/media/flags.3afdda2f.webp +0 -0
- khoj/interface/compiled/_next/static/media/flags@2x.5fbe9fc1.webp +0 -0
- khoj/interface/compiled/_next/static/media/globe.98e105ca.webp +0 -0
- khoj/interface/compiled/_next/static/media/globe@2x.974df6f8.webp +0 -0
- khoj/interface/compiled/agents/index.html +1 -0
- khoj/interface/compiled/agents/index.txt +7 -0
- khoj/interface/compiled/agents.svg +6 -0
- khoj/interface/compiled/automation.svg +37 -0
- khoj/interface/compiled/automations/index.html +1 -0
- khoj/interface/compiled/automations/index.txt +8 -0
- khoj/interface/compiled/chat/index.html +1 -0
- khoj/interface/compiled/chat/index.txt +7 -0
- khoj/interface/compiled/close.svg +5 -0
- khoj/interface/compiled/factchecker/index.html +1 -0
- khoj/interface/compiled/factchecker/index.txt +7 -0
- khoj/interface/compiled/favicon.ico +0 -0
- khoj/interface/compiled/index.html +1 -0
- khoj/interface/compiled/index.txt +7 -0
- khoj/interface/compiled/logo.svg +24 -0
- khoj/interface/compiled/search/index.html +1 -0
- khoj/interface/compiled/search/index.txt +7 -0
- khoj/interface/compiled/settings/index.html +1 -0
- khoj/interface/compiled/settings/index.txt +8 -0
- khoj/interface/compiled/share/chat/index.html +1 -0
- khoj/interface/compiled/share/chat/index.txt +7 -0
- khoj/interface/web/assets/icons/agents.svg +17 -5
- khoj/interface/web/assets/icons/automation.svg +40 -35
- khoj/interface/web/assets/icons/search.svg +40 -8
- khoj/interface/web/login.html +1 -1
- khoj/routers/api_chat.py +5 -16
- khoj/routers/helpers.py +74 -0
- khoj/routers/web_client.py +30 -131
- khoj/utils/constants.py +1 -0
- {khoj-1.17.1.dev229.dist-info → khoj-1.20.3.dist-info}/METADATA +1 -1
- khoj-1.20.3.dist-info/RECORD +353 -0
- khoj/interface/web/404.html +0 -56
- khoj/interface/web/agent.html +0 -312
- khoj/interface/web/agents.html +0 -276
- khoj/interface/web/assets/icons/cancel.svg +0 -3
- khoj/interface/web/assets/icons/collapse.svg +0 -17
- khoj/interface/web/assets/icons/computer.png +0 -0
- khoj/interface/web/assets/icons/confirm-icon.svg +0 -1
- khoj/interface/web/assets/icons/credit-card.png +0 -0
- khoj/interface/web/assets/icons/delete.svg +0 -26
- khoj/interface/web/assets/icons/docx.svg +0 -7
- khoj/interface/web/assets/icons/edit.svg +0 -4
- khoj/interface/web/assets/icons/key.svg +0 -4
- khoj/interface/web/assets/icons/markdown.svg +0 -1
- khoj/interface/web/assets/icons/new.svg +0 -23
- khoj/interface/web/assets/icons/notion.svg +0 -4
- khoj/interface/web/assets/icons/openai-logomark.svg +0 -1
- khoj/interface/web/assets/icons/org.svg +0 -1
- khoj/interface/web/assets/icons/pdf.svg +0 -23
- khoj/interface/web/assets/icons/pencil-edit.svg +0 -5
- khoj/interface/web/assets/icons/plaintext.svg +0 -1
- khoj/interface/web/assets/icons/question-mark-icon.svg +0 -1
- khoj/interface/web/assets/icons/speaker.svg +0 -4
- khoj/interface/web/assets/icons/stop-solid.svg +0 -37
- khoj/interface/web/assets/icons/user-silhouette.svg +0 -4
- khoj/interface/web/assets/icons/voice.svg +0 -8
- khoj/interface/web/assets/icons/web.svg +0 -2
- khoj/interface/web/assets/icons/whatsapp.svg +0 -17
- khoj/interface/web/assets/markdown-it.min.js +0 -8476
- khoj/interface/web/assets/natural-cron.min.js +0 -1
- khoj/interface/web/assets/org.min.js +0 -1823
- khoj/interface/web/assets/pico.min.css +0 -5
- khoj/interface/web/assets/purify.min.js +0 -3
- khoj/interface/web/chat.html +0 -3436
- khoj/interface/web/config_automation.html +0 -1103
- khoj/interface/web/content_source_computer_input.html +0 -139
- khoj/interface/web/content_source_notion_input.html +0 -94
- khoj/interface/web/public_conversation.html +0 -2006
- khoj/interface/web/search.html +0 -470
- khoj/interface/web/settings.html +0 -1011
- khoj-1.17.1.dev229.dist-info/RECORD +0 -244
- /khoj/interface/{web/assets/icons → compiled}/chat.svg +0 -0
- /khoj/interface/{web/assets/icons → compiled}/copy-button-success.svg +0 -0
- /khoj/interface/{web/assets/icons → compiled}/copy-button.svg +0 -0
- /khoj/interface/{web/assets/icons → compiled}/send.svg +0 -0
- /khoj/interface/{web/assets/icons → compiled}/share.svg +0 -0
- /khoj/interface/{web/assets/icons/thumbs-down-svgrepo-com.svg → compiled/thumbs-down.svg} +0 -0
- /khoj/interface/{web/assets/icons/thumbs-up-svgrepo-com.svg → compiled/thumbs-up.svg} +0 -0
- {khoj-1.17.1.dev229.dist-info → khoj-1.20.3.dist-info}/WHEEL +0 -0
- {khoj-1.17.1.dev229.dist-info → khoj-1.20.3.dist-info}/entry_points.txt +0 -0
- {khoj-1.17.1.dev229.dist-info → khoj-1.20.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,1103 +0,0 @@
|
|
|
1
|
-
{% extends "base_config.html" %}
|
|
2
|
-
{% block content %}
|
|
3
|
-
<div class="page">
|
|
4
|
-
<div class="section">
|
|
5
|
-
<div id="overlay" style="display: none;"></div>
|
|
6
|
-
<h2 class="section-title">
|
|
7
|
-
<img class="card-icon" src="/static/assets/icons/automation.svg?v={{ khoj_version }}" alt="Automate">
|
|
8
|
-
<span class="card-title-text">Automate (Preview)</span>
|
|
9
|
-
<div class="instructions">
|
|
10
|
-
Automations allow you to schedule smart reminders using Khoj. This is an experimental feature, so your results may vary! Send any feedback to <a class="inline-link-light" href="mailto:team@khoj.dev">team@khoj.dev</a>.
|
|
11
|
-
</div>
|
|
12
|
-
<div class="instructions notice">
|
|
13
|
-
Sending automation results to <a class="inline-link-light" href="mailto:{{ username}}">{{ username }}</a>.
|
|
14
|
-
</div>
|
|
15
|
-
</h2>
|
|
16
|
-
<div class="section-body">
|
|
17
|
-
<button id="create-automation-button" type="button" class="positive-button">
|
|
18
|
-
<img class="automation-action-icon" src="/static/assets/icons/new.svg" alt="Automations">
|
|
19
|
-
<span id="create-automation-button-text">Build Your Own</span>
|
|
20
|
-
</button>
|
|
21
|
-
<div id="automations" class="section-cards"></div>
|
|
22
|
-
<div id="suggested-automations">
|
|
23
|
-
<h2 class="section-title">
|
|
24
|
-
<span class="card-title-text">Suggested Automations</span>
|
|
25
|
-
</h2>
|
|
26
|
-
<div id="suggested-automations-list" class="section-cards"></div>
|
|
27
|
-
</div>
|
|
28
|
-
</div>
|
|
29
|
-
</div>
|
|
30
|
-
<div id="footer">
|
|
31
|
-
<a href="/">Back to Chat</a>
|
|
32
|
-
</div>
|
|
33
|
-
</div>
|
|
34
|
-
<script>
|
|
35
|
-
document.getElementById("automations-nav").classList.add("khoj-nav-selected");
|
|
36
|
-
</script>
|
|
37
|
-
|
|
38
|
-
<script src="/static/assets/natural-cron.min.js"></script>
|
|
39
|
-
<style>
|
|
40
|
-
td {
|
|
41
|
-
padding: 10px 0;
|
|
42
|
-
}
|
|
43
|
-
#overlay {
|
|
44
|
-
position: fixed;
|
|
45
|
-
top: 0;
|
|
46
|
-
left: 0;
|
|
47
|
-
width: 100%;
|
|
48
|
-
height: 100%;
|
|
49
|
-
background-color: black;
|
|
50
|
-
opacity: 0.5;
|
|
51
|
-
z-index: 1;
|
|
52
|
-
}
|
|
53
|
-
div.automation {
|
|
54
|
-
width: 100%;
|
|
55
|
-
height: 100%;
|
|
56
|
-
grid-template-rows: none;
|
|
57
|
-
background-color: white;
|
|
58
|
-
border-radius: 20px;
|
|
59
|
-
padding: 20px;
|
|
60
|
-
box-shadow: rgba(3, 3, 3, 0.08) 0px 1px 12px;
|
|
61
|
-
transition: box-shadow 0.3s ease, transform 0.3s ease;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
div#footer {
|
|
65
|
-
width: auto;
|
|
66
|
-
padding: 10px;
|
|
67
|
-
background-color: var(--background-color);
|
|
68
|
-
border-top: 1px solid var(--main-text-color);
|
|
69
|
-
text-align: left;
|
|
70
|
-
margin-top: 12px;
|
|
71
|
-
margin-bottom: 12px;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
div#footer a {
|
|
75
|
-
font-size: 18px;
|
|
76
|
-
font-weight: bold;
|
|
77
|
-
color: var(--primary-color);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
img.automation-share-icon,
|
|
81
|
-
img.automation-edit-cancel-icon,
|
|
82
|
-
img.automation-edit-icon {
|
|
83
|
-
width: 24px;
|
|
84
|
-
height: 24px;
|
|
85
|
-
object-fit: cover;
|
|
86
|
-
cursor: pointer;
|
|
87
|
-
margin: auto;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
textarea.fake-input,
|
|
91
|
-
input.fake-input {
|
|
92
|
-
height: auto;
|
|
93
|
-
padding-top: 0px;
|
|
94
|
-
padding-bottom: 0px;
|
|
95
|
-
padding-left: 0px;
|
|
96
|
-
padding-right: 0px;
|
|
97
|
-
border: none;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
#create-automation-button {
|
|
101
|
-
width: auto;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
div.notice {
|
|
105
|
-
border-top: 1px solid black;
|
|
106
|
-
padding-top: 8px;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
div#suggested-automations-list,
|
|
110
|
-
div#automations {
|
|
111
|
-
margin-bottom: 12px;
|
|
112
|
-
grid-template-columns: 1fr 1fr;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
button.negative-button {
|
|
116
|
-
background-color: gainsboro;
|
|
117
|
-
}
|
|
118
|
-
.positive-button {
|
|
119
|
-
background-color: var(--primary-hover)
|
|
120
|
-
}
|
|
121
|
-
.positive-button:hover {
|
|
122
|
-
background-color: var(--summer-sun);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
div.automation-buttons {
|
|
126
|
-
display: grid;
|
|
127
|
-
grid-gap: 8px;
|
|
128
|
-
grid-template-columns: 1fr 1fr 1fr;
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
button.save-automation-button {
|
|
132
|
-
background-color: var(--summer-sun);
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
button.save-automation-button,
|
|
136
|
-
button.cancel-edit-automation-button,
|
|
137
|
-
button.send-preview-automation-button,
|
|
138
|
-
button.delete-automation-button {
|
|
139
|
-
padding: 8px;
|
|
140
|
-
margin-bottom: 0;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
button.send-preview-automation-button {
|
|
144
|
-
border-color: var(--summer-sun);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
button.save-automation-button:hover {
|
|
148
|
-
background-color: var(--primary-hover);
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
div.new-automation {
|
|
152
|
-
border-radius: 20px;
|
|
153
|
-
box-shadow: 0 4px 6px 0 hsla(0, 0%, 0%, 0.2);
|
|
154
|
-
margin-bottom: 20px;
|
|
155
|
-
transition: box-shadow 0.3s ease, transform 0.3s ease;
|
|
156
|
-
position: absolute;
|
|
157
|
-
top: 50%;
|
|
158
|
-
left: 50%;
|
|
159
|
-
width: auto;
|
|
160
|
-
transform: translate(-50%, -50%);
|
|
161
|
-
z-index: 2;
|
|
162
|
-
height: auto;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
div.automation:not(.new-automation):hover {
|
|
166
|
-
box-shadow: 0 10px 15px 0 hsla(0, 0%, 0%, 0.1);
|
|
167
|
-
transform: translateY(-5px);
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
.hide-details {
|
|
171
|
-
display: none !important;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
div.card-header {
|
|
175
|
-
display: grid;
|
|
176
|
-
grid-template-rows: auto 1fr;
|
|
177
|
-
grid-gap: 8px;
|
|
178
|
-
align-items: baseline;
|
|
179
|
-
}
|
|
180
|
-
input.schedule {
|
|
181
|
-
font-size: medium;
|
|
182
|
-
height: auto;
|
|
183
|
-
font-weight: lighter !important;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
h2.section-title {
|
|
187
|
-
font-size: larger;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
div.card-header input {
|
|
191
|
-
font-weight: bold;
|
|
192
|
-
margin-bottom: 0 !important;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
div.automation textarea {
|
|
196
|
-
width: 100%;
|
|
197
|
-
box-sizing: border-box;
|
|
198
|
-
border-radius: 4px;
|
|
199
|
-
resize: none;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
img.promo-image {
|
|
203
|
-
width: 100%;
|
|
204
|
-
height: 100px;
|
|
205
|
-
object-fit: cover;
|
|
206
|
-
border-radius: 4px;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
div.card-header textarea,
|
|
211
|
-
div.card-header input,
|
|
212
|
-
div.card-header:hover {
|
|
213
|
-
cursor: pointer;
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
div.toggle-icon {
|
|
217
|
-
width: 24px;
|
|
218
|
-
height: 24px;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
div.subject-wrapper {
|
|
222
|
-
display: grid;
|
|
223
|
-
grid-template-columns: 1fr auto auto;
|
|
224
|
-
grid-gap: 8px;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
div.subject-wrapper p {
|
|
228
|
-
margin: 0;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
@keyframes confirmation {
|
|
232
|
-
0% { background-color: normal; transform: scale(1); }
|
|
233
|
-
50% { background-color: var(--primary); transform: scale(1.1); }
|
|
234
|
-
100% { background-color: normal; transform: scale(1); }
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
.confirmation {
|
|
238
|
-
animation: confirmation 1s;
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
@media screen and (max-width: 600px) {
|
|
242
|
-
div#automations,
|
|
243
|
-
div#suggested-automations-list {
|
|
244
|
-
grid-template-columns: 1fr;
|
|
245
|
-
}
|
|
246
|
-
div.automation-buttons {
|
|
247
|
-
grid-template-columns: 1fr;
|
|
248
|
-
}
|
|
249
|
-
div.new-automation {
|
|
250
|
-
width: 100%;
|
|
251
|
-
height: auto;
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
</style>
|
|
256
|
-
<script>
|
|
257
|
-
function deleteAutomation(automationId) {
|
|
258
|
-
const AutomationList = document.getElementById("automations");
|
|
259
|
-
if ("{{ username }}" === "None") {
|
|
260
|
-
const AutomationItem = document.getElementById(`automation-card-${automationId}`);
|
|
261
|
-
AutomationList.removeChild(AutomationItem);
|
|
262
|
-
// Remove the Automation from the DOM and return
|
|
263
|
-
AutomationItem.remove();
|
|
264
|
-
document.getElementById('overlay').style.display = 'none';
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
fetch(`/api/automation?automation_id=${automationId}`, {
|
|
268
|
-
method: 'DELETE',
|
|
269
|
-
})
|
|
270
|
-
.then(response => {
|
|
271
|
-
if (response.status == 200 || response.status == 204) {
|
|
272
|
-
const AutomationItem = document.getElementById(`automation-card-${automationId}`);
|
|
273
|
-
AutomationList.removeChild(AutomationItem);
|
|
274
|
-
document.getElementById('overlay').style.display = 'none';
|
|
275
|
-
}
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function updateAutomationRow(automation) {
|
|
280
|
-
let automationId = automation.id;
|
|
281
|
-
let automationSubject = DOMPurify.sanitize(automation.subject);
|
|
282
|
-
let automationSchedule = DOMPurify.sanitize(automation.schedule);
|
|
283
|
-
let automationQueryToRun = DOMPurify.sanitize(automation.query_to_run);
|
|
284
|
-
let automationCrontime = DOMPurify.sanitize(automation.crontime);
|
|
285
|
-
let automationNextRun = `Next run at ${automation.next}\nCron: ${automationCrontime}`;
|
|
286
|
-
|
|
287
|
-
let scheduleEl = document.getElementById(`automation-schedule-${automationId}`);
|
|
288
|
-
scheduleEl.setAttribute('data-original', automationSchedule);
|
|
289
|
-
scheduleEl.setAttribute('data-cron', automationCrontime);
|
|
290
|
-
scheduleEl.setAttribute('title', automationNextRun);
|
|
291
|
-
scheduleEl.value = automationSchedule;
|
|
292
|
-
|
|
293
|
-
let subjectEl = document.getElementById(`automation-subject-${automationId}`);
|
|
294
|
-
subjectEl.setAttribute('data-original', automationSubject);
|
|
295
|
-
subjectEl.value = automationSubject;
|
|
296
|
-
|
|
297
|
-
let queryEl = document.getElementById(`automation-queryToRun-${automationId}`);
|
|
298
|
-
queryEl.setAttribute('data-original', automationQueryToRun);
|
|
299
|
-
queryEl.value = automationQueryToRun;
|
|
300
|
-
}
|
|
301
|
-
|
|
302
|
-
function onClickEditAutomationCard(automationId) {
|
|
303
|
-
const automationIDElements = document.querySelectorAll(`.${automationId}`);
|
|
304
|
-
automationIDElements.forEach(el => {
|
|
305
|
-
if (el.classList.contains("automation-edit-icon")) {
|
|
306
|
-
el.classList.remove("automation-edit-icon");
|
|
307
|
-
el.classList.add("automation-edit-cancel-icon");
|
|
308
|
-
el.src = "/static/assets/icons/cancel.svg";
|
|
309
|
-
el.onclick = function(event) { clickCancelEdit(event, automationId); };
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (el.classList.contains("hide-details")) {
|
|
313
|
-
el.classList.add("hide-details-placeholder");
|
|
314
|
-
el.classList.remove("hide-details");
|
|
315
|
-
}
|
|
316
|
-
if (el.classList.contains("fake-input")) {
|
|
317
|
-
el.classList.add("fake-input-placeholder");
|
|
318
|
-
el.classList.remove("fake-input");
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
function sendAPreviewAutomation(automationId) {
|
|
324
|
-
const notificationEl = document.getElementById(`automation-success-${automationId}`);
|
|
325
|
-
|
|
326
|
-
fetch(`/api/trigger/automation?automation_id=${automationId}`, { method: 'POST' })
|
|
327
|
-
.then(response =>
|
|
328
|
-
{
|
|
329
|
-
if (!response.ok) {
|
|
330
|
-
throw new Error('Network response was not ok');
|
|
331
|
-
}
|
|
332
|
-
return response;
|
|
333
|
-
})
|
|
334
|
-
.then(automations => {
|
|
335
|
-
notificationEl.style.display = 'block';
|
|
336
|
-
notificationEl.textContent = "Automation triggered. Check your inbox in a few minutes!";
|
|
337
|
-
})
|
|
338
|
-
.catch(error => {
|
|
339
|
-
notificationEl.style.display = 'block';
|
|
340
|
-
notificationEl.textContent = "Sorry, something went wrong. Try again later."
|
|
341
|
-
})
|
|
342
|
-
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
function clickCancelEdit(event, automationId) {
|
|
346
|
-
event.preventDefault();
|
|
347
|
-
event.stopPropagation();
|
|
348
|
-
const automationIDElements = document.querySelectorAll(`.${automationId}`);
|
|
349
|
-
automationIDElements.forEach(el => {
|
|
350
|
-
if (el.classList.contains("automation-edit-cancel-icon")) {
|
|
351
|
-
el.classList.remove("automation-edit-cancel-icon");
|
|
352
|
-
el.classList.add("automation-edit-icon");
|
|
353
|
-
el.src = "/static/assets/icons/pencil-edit.svg";
|
|
354
|
-
el.onclick = function() { onClickEditAutomationCard(automationId); };
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (el.classList.contains("hide-details-placeholder")) {
|
|
358
|
-
el.classList.remove("hide-details-placeholder");
|
|
359
|
-
el.classList.add("hide-details");
|
|
360
|
-
}
|
|
361
|
-
if (el.classList.contains("fake-input-placeholder")) {
|
|
362
|
-
el.classList.remove("fake-input-placeholder");
|
|
363
|
-
el.classList.add("fake-input");
|
|
364
|
-
}
|
|
365
|
-
})
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
function copyShareLink(event, automationId, subject, crontime, queryToRun) {
|
|
369
|
-
event.preventDefault();
|
|
370
|
-
event.stopPropagation();
|
|
371
|
-
|
|
372
|
-
const encodedSubject = encodeURIComponent(subject);
|
|
373
|
-
const encodedCrontime = encodeURIComponent(crontime);
|
|
374
|
-
const encodedQueryToRun = encodeURIComponent(queryToRun);
|
|
375
|
-
|
|
376
|
-
const shareLink = `${window.location.origin}/automations?subject=${encodedSubject}&crontime=${encodedCrontime}&queryToRun=${encodedQueryToRun}`;
|
|
377
|
-
const button = document.getElementById(`share-link-${automationId}`);
|
|
378
|
-
|
|
379
|
-
navigator.clipboard.writeText(shareLink).then(() => {
|
|
380
|
-
button.src = "/static/assets/icons/copy-button-success.svg";
|
|
381
|
-
setTimeout(() => {
|
|
382
|
-
button.src = "/static/assets/icons/share.svg";
|
|
383
|
-
}, 1000);
|
|
384
|
-
}).catch((error) => {
|
|
385
|
-
console.error("Error copying share link output to clipboard:", error);
|
|
386
|
-
setTimeout(() => {
|
|
387
|
-
button.src = "/static/assets/icons/share.svg";
|
|
388
|
-
}, 1000);
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
|
|
392
|
-
function generateAutomationRow(automation, isSuggested=false) {
|
|
393
|
-
let automationId = automation.id;
|
|
394
|
-
let automationSubject = DOMPurify.sanitize(automation.subject);
|
|
395
|
-
let automationSchedule = DOMPurify.sanitize(automation.schedule);
|
|
396
|
-
let automationQueryToRun = DOMPurify.sanitize(automation.query_to_run);
|
|
397
|
-
let automationCrontime = DOMPurify.sanitize(automation.crontime);
|
|
398
|
-
let automationNextRun = `Next run at ${automation.next}\nCron: ${automationCrontime}`;
|
|
399
|
-
|
|
400
|
-
// Create card top elements
|
|
401
|
-
let automationEl = document.createElement("div");
|
|
402
|
-
let automationCardEl = document.createElement("div");
|
|
403
|
-
automationCardEl.id = `automation-card-${automationId}`;
|
|
404
|
-
automationCardEl.classList.add("card", "automation");
|
|
405
|
-
|
|
406
|
-
// Create card header elements
|
|
407
|
-
let automationCardFormEl = document.createElement("div");
|
|
408
|
-
automationCardFormEl.className = "card-header";
|
|
409
|
-
|
|
410
|
-
let automationButtonsWrapperEl = document.createElement("div");
|
|
411
|
-
automationButtonsWrapperEl.id = "automation-buttons-wrapper";
|
|
412
|
-
|
|
413
|
-
let automationSuccessEl = document.createElement("div");
|
|
414
|
-
automationSuccessEl.id = `automation-success-${automationId}`;
|
|
415
|
-
automationSuccessEl.style.display = "none";
|
|
416
|
-
|
|
417
|
-
// Create automation card form section
|
|
418
|
-
automationCardFormEl.onclick = function() { onClickEditAutomationCard(automationId); };
|
|
419
|
-
|
|
420
|
-
// automation subject input
|
|
421
|
-
let subjectWrapperEl = document.createElement("div");
|
|
422
|
-
subjectWrapperEl.className = "subject-wrapper";
|
|
423
|
-
let subjectEl = document.createElement("input");
|
|
424
|
-
subjectEl.type = "text";
|
|
425
|
-
subjectEl.id = `automation-subject-${automationId}`;
|
|
426
|
-
subjectEl.classList.add(automationId, "fake-input");
|
|
427
|
-
subjectEl.name = "subject";
|
|
428
|
-
subjectEl.setAttribute("data-original", automationSubject);
|
|
429
|
-
subjectEl.value = automationSubject;
|
|
430
|
-
|
|
431
|
-
// automation share link
|
|
432
|
-
let shareLinkEl = document.createElement("img");
|
|
433
|
-
shareLinkEl.id = `share-link-${automationId}`,
|
|
434
|
-
shareLinkEl.className = "automation-share-icon";
|
|
435
|
-
shareLinkEl.src = "/static/assets/icons/share.svg";
|
|
436
|
-
shareLinkEl.alt = "Share";
|
|
437
|
-
shareLinkEl.onclick = function(event) { copyShareLink(event, automationId, automationSubject, automationCrontime, automationQueryToRun); };
|
|
438
|
-
|
|
439
|
-
// automation edit action
|
|
440
|
-
let editIconEl = document.createElement("img");
|
|
441
|
-
editIconEl.classList.add("automation-edit-icon", automationId);
|
|
442
|
-
editIconEl.src = "/static/assets/icons/pencil-edit.svg";
|
|
443
|
-
editIconEl.alt = "Automations";
|
|
444
|
-
editIconEl.onclick = function() { onClickEditAutomationCard(automationId); };
|
|
445
|
-
|
|
446
|
-
// automation schedule input
|
|
447
|
-
let scheduleEl = document.createElement("input");
|
|
448
|
-
scheduleEl.type = "text";
|
|
449
|
-
scheduleEl.id = `automation-schedule-${automationId}`;
|
|
450
|
-
scheduleEl.name = "schedule";
|
|
451
|
-
scheduleEl.classList.add("schedule", automationId, "fake-input");
|
|
452
|
-
scheduleEl.setAttribute("data-cron", automationCrontime);
|
|
453
|
-
scheduleEl.setAttribute("data-original", automationSchedule);
|
|
454
|
-
scheduleEl.title = automationNextRun;
|
|
455
|
-
scheduleEl.value = automationSchedule;
|
|
456
|
-
|
|
457
|
-
// automation query to run input
|
|
458
|
-
let queryToRunEl = document.createElement("textarea");
|
|
459
|
-
queryToRunEl.id = `automation-queryToRun-${automationId}`;
|
|
460
|
-
queryToRunEl.classList.add("automation-instructions", automationId, "fake-input");
|
|
461
|
-
queryToRunEl.setAttribute("data-original", automationQueryToRun);
|
|
462
|
-
queryToRunEl.name = "query-to-run";
|
|
463
|
-
queryToRunEl.textContent = automationQueryToRun;
|
|
464
|
-
|
|
465
|
-
// Create automation actions section
|
|
466
|
-
let automationButtonsEl = document.createElement("div");
|
|
467
|
-
automationButtonsEl.className = "automation-buttons";
|
|
468
|
-
if (!isSuggested) {
|
|
469
|
-
automationButtonsEl.classList.add("hide-details", automationId);
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
// save automation button
|
|
473
|
-
let saveAutomationButtonEl = document.createElement("button");
|
|
474
|
-
saveAutomationButtonEl.type = "button";
|
|
475
|
-
saveAutomationButtonEl.className = "save-automation-button positive-button";
|
|
476
|
-
saveAutomationButtonEl.id = `save-automation-button-${automationId}`;
|
|
477
|
-
saveAutomationButtonEl.textContent = isSuggested ? "Add" : "Save";
|
|
478
|
-
saveAutomationButtonEl.onclick = async () => { await saveAutomation(automation.id, isSuggested); };
|
|
479
|
-
|
|
480
|
-
// promo image for suggested automations
|
|
481
|
-
let promoImageEl = isSuggested ? document.createElement("img") : null;
|
|
482
|
-
if (isSuggested) {
|
|
483
|
-
promoImageEl.className = "promo-image";
|
|
484
|
-
promoImageEl.src = automation.promoImage;
|
|
485
|
-
promoImageEl.alt = "Promo Image";
|
|
486
|
-
}
|
|
487
|
-
|
|
488
|
-
// delete automation button
|
|
489
|
-
let emptyDivEl = document.createElement("div");
|
|
490
|
-
emptyDivEl.className = "empty-div";
|
|
491
|
-
let deleteAutomationButtonEl = !isSuggested ? document.createElement("button") : emptyDivEl;
|
|
492
|
-
if (!isSuggested) {
|
|
493
|
-
deleteAutomationButtonEl.type = "button";
|
|
494
|
-
deleteAutomationButtonEl.className = "delete-automation-button negative-button";
|
|
495
|
-
deleteAutomationButtonEl.id = `delete-automation-button-${automationId}`;
|
|
496
|
-
deleteAutomationButtonEl.textContent = "Delete";
|
|
497
|
-
deleteAutomationButtonEl.onclick = function() { deleteAutomation(automationId); document.getElementById('overlay').style.display = 'none'; };
|
|
498
|
-
}
|
|
499
|
-
|
|
500
|
-
// send preview automation button
|
|
501
|
-
emptyDivEl = document.createElement("div");
|
|
502
|
-
emptyDivEl.className = "empty-div";
|
|
503
|
-
let sendPreviewAutomationButtonEl = !isSuggested ? document.createElement("button") : emptyDivEl;
|
|
504
|
-
if (!isSuggested) {
|
|
505
|
-
sendPreviewAutomationButtonEl.type = "button";
|
|
506
|
-
sendPreviewAutomationButtonEl.className = "send-preview-automation-button positive-button";
|
|
507
|
-
sendPreviewAutomationButtonEl.title = "Immediately get a preview of this automation";
|
|
508
|
-
sendPreviewAutomationButtonEl.textContent = "Preview";
|
|
509
|
-
sendPreviewAutomationButtonEl.onclick = function() { sendAPreviewAutomation(automationId); };
|
|
510
|
-
}
|
|
511
|
-
|
|
512
|
-
// Construct automation card from elements
|
|
513
|
-
subjectWrapperEl.append(subjectEl, shareLinkEl, editIconEl);
|
|
514
|
-
automationButtonsEl.append(deleteAutomationButtonEl, sendPreviewAutomationButtonEl, saveAutomationButtonEl);
|
|
515
|
-
|
|
516
|
-
automationCardFormEl.append(subjectWrapperEl, scheduleEl, queryToRunEl);
|
|
517
|
-
if (isSuggested) {
|
|
518
|
-
automationCardFormEl.append(promoImageEl);
|
|
519
|
-
}
|
|
520
|
-
automationButtonsWrapperEl.append(automationButtonsEl);
|
|
521
|
-
|
|
522
|
-
automationCardEl.append(automationCardFormEl, automationButtonsWrapperEl, automationSuccessEl);
|
|
523
|
-
automationEl.append(automationCardEl);
|
|
524
|
-
|
|
525
|
-
return automationEl.firstElementChild;
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
let timestamp = Math.floor(Date.now() / 1000);
|
|
529
|
-
|
|
530
|
-
let suggestedAutomationsMetadata = [
|
|
531
|
-
{
|
|
532
|
-
"subject": "Weekly Newsletter",
|
|
533
|
-
"query_to_run": "Compile a message including: 1. A recap of news from last week 2. A reminder to work out and stay hydrated 3. A quote to inspire me for the week ahead",
|
|
534
|
-
"schedule": "9AM every Monday",
|
|
535
|
-
"next": "Next run at 9AM on Monday",
|
|
536
|
-
"crontime": "0 9 * * 1",
|
|
537
|
-
"id": "suggested-automation" + timestamp,
|
|
538
|
-
"promoImage": "https://assets.khoj.dev/abstract_rectangles.webp",
|
|
539
|
-
},
|
|
540
|
-
{
|
|
541
|
-
"subject": "Daily Weather Update",
|
|
542
|
-
"query_to_run": "Get the weather forecast for today",
|
|
543
|
-
"schedule": "9AM every morning",
|
|
544
|
-
"next": "Next run at 9AM today",
|
|
545
|
-
"crontime": "0 9 * * *",
|
|
546
|
-
"id": "suggested-automation" + (timestamp + 1),
|
|
547
|
-
"promoImage": "https://assets.khoj.dev/blue_waves.webp",
|
|
548
|
-
},
|
|
549
|
-
{
|
|
550
|
-
"subject": "Front Page of Hacker News",
|
|
551
|
-
"query_to_run": "Summarize the top 5 posts from https://news.ycombinator.com/best and share them with me, including links",
|
|
552
|
-
"schedule": "9PM on every Wednesday",
|
|
553
|
-
"next": "Next run at 9PM on Wednesday",
|
|
554
|
-
"crontime": "0 21 * * 3",
|
|
555
|
-
"id": "suggested-automation" + (timestamp + 2),
|
|
556
|
-
"promoImage": "https://assets.khoj.dev/purple_triangles.webp",
|
|
557
|
-
},
|
|
558
|
-
{
|
|
559
|
-
"subject": "Market Summary",
|
|
560
|
-
"query_to_run": "Get the market summary for today and share it with me. Focus on tech stocks and the S&P 500.",
|
|
561
|
-
"schedule": "9AM on every weekday",
|
|
562
|
-
"next": "Next run at 9AM on Monday",
|
|
563
|
-
"crontime": "0 9 * * 1-5",
|
|
564
|
-
"id": "suggested-automation" + (timestamp + 3),
|
|
565
|
-
"promoImage": "https://assets.khoj.dev/blue_gears.webp",
|
|
566
|
-
}
|
|
567
|
-
];
|
|
568
|
-
|
|
569
|
-
function listAutomations() {
|
|
570
|
-
const AutomationsList = document.getElementById("automations");
|
|
571
|
-
fetch('/api/automations')
|
|
572
|
-
.then(response => response.json())
|
|
573
|
-
.then(automations => {
|
|
574
|
-
if (!automations?.length > 0) return;
|
|
575
|
-
AutomationsList.innerHTML = ''; // Clear existing content
|
|
576
|
-
AutomationsList.append(...automations.map(automation => generateAutomationRow(automation)));
|
|
577
|
-
// Check if any of the automations 'query-to-run' fields match the suggested automations
|
|
578
|
-
automations.forEach(automation => {
|
|
579
|
-
suggestedAutomationsMetadata.forEach(suggestedAutomation => {
|
|
580
|
-
if (automation.query_to_run === suggestedAutomation.query_to_run) {
|
|
581
|
-
let suggestedAutomationEl = document.getElementById(`automation-card-${suggestedAutomation.id}`);
|
|
582
|
-
suggestedAutomationEl.remove();
|
|
583
|
-
}
|
|
584
|
-
});
|
|
585
|
-
});
|
|
586
|
-
// Check if subject, crontime, query_to_run are all filled out. If so, show it as a populated suggested automation.
|
|
587
|
-
const subject = DOMPurify.sanitize("{{ subject }}");
|
|
588
|
-
const crontime = DOMPurify.sanitize("{{ crontime }}");
|
|
589
|
-
const query = DOMPurify.sanitize("{{ queryToRun }}");
|
|
590
|
-
|
|
591
|
-
if (subject && crontime && query) {
|
|
592
|
-
const preFilledAutomation = createPreFilledAutomation(subject, crontime, query);
|
|
593
|
-
}
|
|
594
|
-
});
|
|
595
|
-
}
|
|
596
|
-
|
|
597
|
-
if ("{{ username }}" !== "None") {
|
|
598
|
-
listAutomations();
|
|
599
|
-
} else {
|
|
600
|
-
// Check if subject, crontime, query_to_run are all filled out. If so, show it as a populated suggested automation.
|
|
601
|
-
const subject = DOMPurify.sanitize("{{ subject }}");
|
|
602
|
-
const crontime = DOMPurify.sanitize("{{ crontime }}");
|
|
603
|
-
const query = DOMPurify.sanitize("{{ queryToRun }}");
|
|
604
|
-
|
|
605
|
-
if (subject && crontime && query) {
|
|
606
|
-
const preFilledAutomation = createPreFilledAutomation(subject, crontime, query);
|
|
607
|
-
}
|
|
608
|
-
}
|
|
609
|
-
|
|
610
|
-
if (suggestedAutomationsMetadata.length > 0) {
|
|
611
|
-
suggestedAutomationsMetadata.forEach(automation => {
|
|
612
|
-
automation.id = "suggested-automation" + timestamp;
|
|
613
|
-
timestamp++;
|
|
614
|
-
});
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
function listSuggestedAutomations() {
|
|
618
|
-
const SuggestedAutomationsList = document.getElementById("suggested-automations-list");
|
|
619
|
-
SuggestedAutomationsList.innerHTML = ''; // Clear existing content
|
|
620
|
-
SuggestedAutomationsList.append(...suggestedAutomationsMetadata.map(automation => generateAutomationRow(automation, true)));
|
|
621
|
-
}
|
|
622
|
-
listSuggestedAutomations();
|
|
623
|
-
|
|
624
|
-
function enableSaveOnlyWhenInputsChanged() {
|
|
625
|
-
const inputs = document.querySelectorAll('input[name="schedule"], textarea[name="query-to-run"], input[name="subject"]');
|
|
626
|
-
inputs.forEach(input => {
|
|
627
|
-
input.addEventListener('change', function() {
|
|
628
|
-
// Get automation id by splitting the id by "-" and taking all elements after the second one
|
|
629
|
-
const automationId = this.id.split("-").slice(2).join("-");
|
|
630
|
-
let anyChanged = false;
|
|
631
|
-
let inputNameStubs = ["subject", "query-to-run", "schedule"]
|
|
632
|
-
for (let stub of inputNameStubs) {
|
|
633
|
-
let el = document.getElementById(`automation-${stub}-${automationId}`);
|
|
634
|
-
let originalValue = el.getAttribute('data-original');
|
|
635
|
-
let currentValue = el.value;
|
|
636
|
-
if (originalValue !== currentValue) {
|
|
637
|
-
anyChanged = true;
|
|
638
|
-
break;
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
document.getElementById(`save-automation-button-${automationId}`).disabled = !anyChanged;
|
|
642
|
-
});
|
|
643
|
-
});
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
function createScheduleSelector(automationId) {
|
|
647
|
-
var scheduleContainer = document.createElement('div');
|
|
648
|
-
scheduleContainer.id = `schedule-container-${automationId}`;
|
|
649
|
-
|
|
650
|
-
var frequencyLabel = document.createElement('label');
|
|
651
|
-
frequencyLabel.for = `frequency-selector-${automationId}`;
|
|
652
|
-
frequencyLabel.textContent = 'Every';
|
|
653
|
-
var frequencySelector = document.createElement('select')
|
|
654
|
-
frequencySelector.id = `frequency-selector-${automationId}`;
|
|
655
|
-
var dayLabel = document.createElement('label');
|
|
656
|
-
dayLabel.id = `day-selector-label-${automationId}`;
|
|
657
|
-
dayLabel.for = `day-selector-${automationId}`;
|
|
658
|
-
dayLabel.textContent = 'on';
|
|
659
|
-
var daySelector = document.createElement('select');
|
|
660
|
-
daySelector.id = `day-selector-${automationId}`;
|
|
661
|
-
var dateLabel = document.createElement('label');
|
|
662
|
-
dateLabel.id = `date-label-${automationId}`;
|
|
663
|
-
dateLabel.for = `date-selector-${automationId}`;
|
|
664
|
-
dateLabel.textContent = 'on the';
|
|
665
|
-
var dateSelector = document.createElement('select');
|
|
666
|
-
dateSelector.id = `date-selector-${automationId}`;
|
|
667
|
-
var timeLabel = document.createElement('label');
|
|
668
|
-
timeLabel.for = `time-selector-${automationId}`;
|
|
669
|
-
timeLabel.textContent = 'at';
|
|
670
|
-
var timeSelector = document.createElement('select');
|
|
671
|
-
timeSelector.id = `time-selector-${automationId}`;
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
// Populate frequency selector with options for day, week, and month
|
|
675
|
-
var frequencies = ['day', 'week', 'month'];
|
|
676
|
-
for (var i = 0; i < frequencies.length; i++) {
|
|
677
|
-
var option = document.createElement('option');
|
|
678
|
-
option.value = frequencies[i];
|
|
679
|
-
option.text = frequencies[i];
|
|
680
|
-
frequencySelector.appendChild(option);
|
|
681
|
-
}
|
|
682
|
-
|
|
683
|
-
// Event listener for frequency selector change
|
|
684
|
-
frequencySelector.addEventListener('change', function() {
|
|
685
|
-
switch (this.value) {
|
|
686
|
-
case 'day':
|
|
687
|
-
daySelector.style.display = 'none';
|
|
688
|
-
dateSelector.style.display = 'none';
|
|
689
|
-
break;
|
|
690
|
-
case 'week':
|
|
691
|
-
daySelector.style.display = 'block';
|
|
692
|
-
dateSelector.style.display = 'none';
|
|
693
|
-
break;
|
|
694
|
-
case 'month':
|
|
695
|
-
daySelector.style.display = 'none';
|
|
696
|
-
dateSelector.style.display = 'block';
|
|
697
|
-
break;
|
|
698
|
-
}
|
|
699
|
-
});
|
|
700
|
-
|
|
701
|
-
// Populate the date selector with options for each day of the month
|
|
702
|
-
for (var i = 1; i <= 31; i++) {
|
|
703
|
-
var option = document.createElement('option');
|
|
704
|
-
option.value = i;
|
|
705
|
-
option.text = i;
|
|
706
|
-
dateSelector.appendChild(option);
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
// Populate the day selector with options for each day of the week
|
|
710
|
-
var days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
|
|
711
|
-
for (var i = 0; i < days.length; i++) {
|
|
712
|
-
var option = document.createElement('option');
|
|
713
|
-
option.value = i;
|
|
714
|
-
option.text = days[i];
|
|
715
|
-
daySelector.appendChild(option);
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
var timePeriods = ['AM', 'PM'];
|
|
719
|
-
// Populate the time selector with options for each hour of the day
|
|
720
|
-
for (var i = 0; i < timePeriods.length; i++) {
|
|
721
|
-
for (var hour = 0; hour < 12; hour++) {
|
|
722
|
-
for (var minute = 0; minute < 60; minute+=15) {
|
|
723
|
-
// Ensure all minutes are two digits
|
|
724
|
-
var paddedMinute = String(minute).padStart(2, '0');
|
|
725
|
-
var option = document.createElement('option');
|
|
726
|
-
var friendlyHour = hour === 0 ? 12 : hour;
|
|
727
|
-
option.value = `${friendlyHour}:${paddedMinute} ${timePeriods[i]}`;
|
|
728
|
-
option.text = `${friendlyHour}:${paddedMinute} ${timePeriods[i]}`;
|
|
729
|
-
timeSelector.appendChild(option);
|
|
730
|
-
}
|
|
731
|
-
}
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
// Populate date selector with options 1 through 31
|
|
735
|
-
for (var i = 1; i <= 31; i++) {
|
|
736
|
-
var option = document.createElement('option');
|
|
737
|
-
option.value = i;
|
|
738
|
-
option.text = i;
|
|
739
|
-
dateSelector.appendChild(option);
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
var hoursMinutesSelectorContainer = document.createElement('div');
|
|
743
|
-
hoursMinutesSelectorContainer.classList.add('hours-minutes-selector-container');
|
|
744
|
-
hoursMinutesSelectorContainer.appendChild(timeLabel);
|
|
745
|
-
hoursMinutesSelectorContainer.appendChild(timeSelector);
|
|
746
|
-
|
|
747
|
-
scheduleContainer.appendChild(frequencyLabel);
|
|
748
|
-
scheduleContainer.appendChild(frequencySelector);
|
|
749
|
-
scheduleContainer.appendChild(dayLabel);
|
|
750
|
-
scheduleContainer.appendChild(daySelector);
|
|
751
|
-
scheduleContainer.appendChild(dateLabel);
|
|
752
|
-
scheduleContainer.appendChild(dateSelector);
|
|
753
|
-
scheduleContainer.appendChild(hoursMinutesSelectorContainer);
|
|
754
|
-
|
|
755
|
-
return scheduleContainer;
|
|
756
|
-
}
|
|
757
|
-
|
|
758
|
-
function setupScheduleViewListener(cronString, automationId) {
|
|
759
|
-
// Parse the cron string
|
|
760
|
-
var cronParts = cronString.split(' ');
|
|
761
|
-
var minutes = cronParts[0];
|
|
762
|
-
var hours = cronParts[1];
|
|
763
|
-
var dayOfMonth = cronParts[2];
|
|
764
|
-
var month = cronParts[3];
|
|
765
|
-
var dayOfWeek = cronParts[4];
|
|
766
|
-
|
|
767
|
-
var timeSelector = document.getElementById(`time-selector-${automationId}`);
|
|
768
|
-
|
|
769
|
-
// Set the initial value of the time selector based on the cron string. Convert 24-hour time to 12-hour time
|
|
770
|
-
if (hours === '*' && minutes === '*') {
|
|
771
|
-
var currentTime = new Date();
|
|
772
|
-
hours = currentTime.getHours();
|
|
773
|
-
minutes = currentTime.getMinutes();
|
|
774
|
-
}
|
|
775
|
-
var hours = parseInt(hours);
|
|
776
|
-
var minutes = parseInt(minutes);
|
|
777
|
-
minutes = Math.round(minutes / 15) * 15;
|
|
778
|
-
if (minutes === 60) {
|
|
779
|
-
hours = (hours % 12) + 1;
|
|
780
|
-
minutes = 0;
|
|
781
|
-
}
|
|
782
|
-
var timePeriod = hours >= 12 ? 'PM' : 'AM';
|
|
783
|
-
hours = hours % 12;
|
|
784
|
-
hours = hours ? hours : 12; // 0 should be 12
|
|
785
|
-
minutes = Math.round(minutes / 15) * 15;
|
|
786
|
-
minutes = String(minutes).padStart(2, '0');
|
|
787
|
-
// Resolve minutes to the nearest 15 minute interval
|
|
788
|
-
|
|
789
|
-
timeSelector.value = `${hours}:${minutes} ${timePeriod}`;
|
|
790
|
-
|
|
791
|
-
const frequencySelector = document.getElementById(`frequency-selector-${automationId}`);
|
|
792
|
-
const daySelector = document.getElementById(`day-selector-${automationId}`);
|
|
793
|
-
const daySelectorLabel = document.getElementById(`day-selector-label-${automationId}`);
|
|
794
|
-
const dateSelector = document.getElementById(`date-selector-${automationId}`);
|
|
795
|
-
const dateLabel = document.getElementById(`date-label-${automationId}`);
|
|
796
|
-
|
|
797
|
-
// Event listener for frequency selector change
|
|
798
|
-
frequencySelector.addEventListener('change', function() {
|
|
799
|
-
processFrequencySelector(frequencySelector, daySelector, daySelectorLabel, dateSelector, dateLabel);
|
|
800
|
-
});
|
|
801
|
-
|
|
802
|
-
// Set the initial value based on the frequency selector value
|
|
803
|
-
processFrequencySelector(frequencySelector, daySelector, daySelectorLabel, dateSelector, dateLabel);
|
|
804
|
-
}
|
|
805
|
-
|
|
806
|
-
function processFrequencySelector(frequencySelector, daySelector, daySelectorLabel, dateSelector, dateLabel) {
|
|
807
|
-
switch (frequencySelector.value) {
|
|
808
|
-
case 'day':
|
|
809
|
-
daySelector.style.display = 'none';
|
|
810
|
-
dateSelector.style.display = 'none';
|
|
811
|
-
daySelectorLabel.style.display = 'none';
|
|
812
|
-
dateLabel.style.display = 'none';
|
|
813
|
-
break;
|
|
814
|
-
case 'week':
|
|
815
|
-
daySelector.style.display = 'block';
|
|
816
|
-
dateSelector.style.display = 'none';
|
|
817
|
-
daySelectorLabel.style.display = 'block';
|
|
818
|
-
dateLabel.style.display = 'none';
|
|
819
|
-
break;
|
|
820
|
-
case 'month':
|
|
821
|
-
daySelector.style.display = 'none';
|
|
822
|
-
dateSelector.style.display = 'block';
|
|
823
|
-
daySelectorLabel.style.display = 'none';
|
|
824
|
-
dateLabel.style.display = 'block';
|
|
825
|
-
break;
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
function convertFrequencyToCron(automationId) {
|
|
830
|
-
var frequencySelector = document.getElementById(`frequency-selector-${automationId}`);
|
|
831
|
-
var daySelector = document.getElementById(`day-selector-${automationId}`);
|
|
832
|
-
var dateSelector = document.getElementById(`date-selector-${automationId}`);
|
|
833
|
-
var timeSelector = document.getElementById(`time-selector-${automationId}`);
|
|
834
|
-
|
|
835
|
-
var hours = timeSelector.value.split(':')[0];
|
|
836
|
-
var minutes = timeSelector.value.split(':')[1].split(' ')[0];
|
|
837
|
-
|
|
838
|
-
var cronString = '';
|
|
839
|
-
switch (frequencySelector.value) {
|
|
840
|
-
case 'day':
|
|
841
|
-
cronString = `${minutes} ${hours} * * *`;
|
|
842
|
-
break;
|
|
843
|
-
case 'week':
|
|
844
|
-
cronString = `${minutes} ${hours} * * ${daySelector.value}`;
|
|
845
|
-
break;
|
|
846
|
-
case 'month':
|
|
847
|
-
cronString = `${minutes} ${hours} ${dateSelector.value} * *`;
|
|
848
|
-
break;
|
|
849
|
-
}
|
|
850
|
-
|
|
851
|
-
return cronString;
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
async function saveAutomation(automationId, create=false) {
|
|
855
|
-
if ("{{ username }}" == "None") {
|
|
856
|
-
url_encoded_href = encodeURIComponent(window.location.href);
|
|
857
|
-
window.location.href = `/login?next=${url_encoded_href}`;
|
|
858
|
-
return;
|
|
859
|
-
}
|
|
860
|
-
|
|
861
|
-
const scheduleEl = document.getElementById(`automation-schedule-${automationId}`);
|
|
862
|
-
const notificationEl = document.getElementById(`automation-success-${automationId}`);
|
|
863
|
-
const saveButtonEl = document.getElementById(`save-automation-button-${automationId}`);
|
|
864
|
-
const queryToRunEl = document.getElementById(`automation-queryToRun-${automationId}`);
|
|
865
|
-
const queryToRun = encodeURIComponent(queryToRunEl.value);
|
|
866
|
-
const actOn = create ? "Creat" : "Sav";
|
|
867
|
-
var cronTime = null;
|
|
868
|
-
|
|
869
|
-
if (queryToRun == "") {
|
|
870
|
-
if (!create && scheduleEl.value == "") {
|
|
871
|
-
notificationEl.textContent = `⚠️ Failed to automate. All input fields need to be filled.`;
|
|
872
|
-
notificationEl.style.display = "block";
|
|
873
|
-
let originalQueryToRunElBorder = queryToRunEl.style.border;
|
|
874
|
-
if (queryToRun === "") queryToRunEl.style.border = "2px solid red";
|
|
875
|
-
let originalScheduleElBorder = scheduleEl.style.border;
|
|
876
|
-
if (scheduleEl.value === "") scheduleEl.style.border = "2px solid red";
|
|
877
|
-
setTimeout(function() {
|
|
878
|
-
if (queryToRun == "") queryToRunEl.style.border = originalQueryToRunElBorder;
|
|
879
|
-
if (scheduleEl.value == "") scheduleEl.style.border = originalScheduleElBorder;
|
|
880
|
-
}, 2000);
|
|
881
|
-
|
|
882
|
-
return;
|
|
883
|
-
}
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
// Get client location information from IP
|
|
887
|
-
const ip_response = await fetch("https://ipapi.co/json");
|
|
888
|
-
let ip_data = null;
|
|
889
|
-
if (ip_response.ok) {
|
|
890
|
-
ip_data = await ip_response.json();
|
|
891
|
-
}
|
|
892
|
-
|
|
893
|
-
// Get cron string from natural language user schedule, if changed
|
|
894
|
-
if (create && !scheduleEl) {
|
|
895
|
-
crontime = convertFrequencyToCron(automationId);
|
|
896
|
-
} else {
|
|
897
|
-
crontime = scheduleEl.getAttribute('data-original') !== scheduleEl.value ? getCronString(scheduleEl.value) : scheduleEl.getAttribute('data-cron');
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
if (crontime.startsWith("ERROR:")) {
|
|
901
|
-
notificationEl.textContent = `⚠️ Failed to automate. Fix or simplify Schedule input field.`;
|
|
902
|
-
notificationEl.style.display = "block";
|
|
903
|
-
let originalScheduleElBorder = scheduleEl.style.border;
|
|
904
|
-
scheduleEl.style.border = "2px solid red";
|
|
905
|
-
setTimeout(function() {
|
|
906
|
-
scheduleEl.style.border = originalScheduleElBorder;
|
|
907
|
-
}, 2000);
|
|
908
|
-
|
|
909
|
-
return;
|
|
910
|
-
}
|
|
911
|
-
const encodedCrontime = encodeURIComponent(crontime);
|
|
912
|
-
|
|
913
|
-
// Construct query string and select method for API call
|
|
914
|
-
let query_string = `q=${queryToRun}&crontime=${encodedCrontime}`;
|
|
915
|
-
if (ip_data) {
|
|
916
|
-
query_string += `&city=${ip_data.city}®ion=${ip_data.region}&country=${ip_data.country_name}&timezone=${ip_data.timezone}`;
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
let method = "POST";
|
|
920
|
-
if (!create) {
|
|
921
|
-
const subjectEl = document.getElementById(`automation-subject-${automationId}`);
|
|
922
|
-
const subject = encodeURIComponent(subjectEl.value);
|
|
923
|
-
query_string += `&automation_id=${automationId}`;
|
|
924
|
-
query_string += `&subject=${subject}`;
|
|
925
|
-
method = "PUT"
|
|
926
|
-
}
|
|
927
|
-
|
|
928
|
-
// Create a loading animation while waiting for the API response
|
|
929
|
-
// TODO add a more pleasant loading symbol here.
|
|
930
|
-
notificationEl.textContent = `⏳ ${actOn}ing automation...`;
|
|
931
|
-
notificationEl.style.display = "block";
|
|
932
|
-
|
|
933
|
-
fetch(`/api/automation?${query_string}`, {
|
|
934
|
-
method: method,
|
|
935
|
-
headers: {
|
|
936
|
-
'Content-Type': 'application/json',
|
|
937
|
-
},
|
|
938
|
-
})
|
|
939
|
-
.then(response => response.ok ? response.json() : Promise.reject(data))
|
|
940
|
-
.then(automation => {
|
|
941
|
-
// Remove modal overlay
|
|
942
|
-
document.getElementById('overlay').style.display = 'none';
|
|
943
|
-
if (create) {
|
|
944
|
-
const automationEl = document.getElementById(`automation-card-${automationId}`);
|
|
945
|
-
// Create a more interesting confirmation animation.
|
|
946
|
-
automationEl.classList.remove("new-automation");
|
|
947
|
-
setTimeout(function() {
|
|
948
|
-
// Check if automationEl is a child of #automations or #suggested-automations-list
|
|
949
|
-
// If #suggested-automations-list, remove the element from the list and add it to #automations
|
|
950
|
-
let parentEl = automationEl.parentElement;
|
|
951
|
-
let isSuggested = parentEl.id === "suggested-automations-list";
|
|
952
|
-
if (isSuggested) {
|
|
953
|
-
parentEl.removeChild(automationEl);
|
|
954
|
-
document.getElementById("automations").prepend(automationEl);
|
|
955
|
-
}
|
|
956
|
-
automationEl.replaceWith(generateAutomationRow(automation));
|
|
957
|
-
}, 1000);
|
|
958
|
-
} else {
|
|
959
|
-
updateAutomationRow(automation);
|
|
960
|
-
}
|
|
961
|
-
|
|
962
|
-
notificationEl.style.display = "none";
|
|
963
|
-
saveButtonEl.textContent = `✅ Automation ${actOn}ed`;
|
|
964
|
-
setTimeout(function() {
|
|
965
|
-
const automationIDElements = document.querySelectorAll(`.${automationId}`);
|
|
966
|
-
automationIDElements.forEach(el => {
|
|
967
|
-
// If it has the class automation-buttons, turn on the hide-details class
|
|
968
|
-
if (el.classList.contains("automation-buttons"))
|
|
969
|
-
{
|
|
970
|
-
el.classList.add("hide-details");
|
|
971
|
-
}
|
|
972
|
-
// If it has the class automationId, turn on the fake-input class
|
|
973
|
-
else if (el.classList.contains(automationId))
|
|
974
|
-
{
|
|
975
|
-
el.classList.add("fake-input");
|
|
976
|
-
}
|
|
977
|
-
});
|
|
978
|
-
saveButtonEl.textContent = "Save";
|
|
979
|
-
}, 2000);
|
|
980
|
-
})
|
|
981
|
-
.catch(error => {
|
|
982
|
-
notificationEl.textContent = `⚠️ Failed to ${actOn.toLowerCase()}e automation.`;
|
|
983
|
-
notificationEl.style.display = "block";
|
|
984
|
-
saveButtonEl.textContent = `⚠️ Failed to ${actOn.toLowerCase()}e automation`;
|
|
985
|
-
setTimeout(function() {
|
|
986
|
-
saveButtonEl.textContent = `${actOn}e`;
|
|
987
|
-
}, 2000);
|
|
988
|
-
return;
|
|
989
|
-
});
|
|
990
|
-
}
|
|
991
|
-
|
|
992
|
-
function createAutomationEl(placeholderId = null) {
|
|
993
|
-
let automationEl = document.createElement("div");
|
|
994
|
-
automationEl.classList.add("card", "automation", "new-automation");
|
|
995
|
-
placeholderId = placeholderId ?? `automation_${Date.now()}`;
|
|
996
|
-
automationEl.id = "automation-card-" + placeholderId;
|
|
997
|
-
|
|
998
|
-
// Create label for schedule
|
|
999
|
-
let scheduleLabel = document.createElement("label");
|
|
1000
|
-
scheduleLabel.setAttribute("for", "schedule");
|
|
1001
|
-
scheduleLabel.textContent = "New Automation";
|
|
1002
|
-
|
|
1003
|
-
// Create schedule selector
|
|
1004
|
-
let scheduleSelector = createScheduleSelector(placeholderId);
|
|
1005
|
-
|
|
1006
|
-
// Create label for query-to-run
|
|
1007
|
-
let queryLabel = document.createElement("label");
|
|
1008
|
-
queryLabel.setAttribute("for", "query-to-run");
|
|
1009
|
-
queryLabel.textContent = "What would you like to receive in your automation?";
|
|
1010
|
-
|
|
1011
|
-
// Create textarea for query-to-run
|
|
1012
|
-
let queryTextarea = document.createElement("textarea");
|
|
1013
|
-
queryTextarea.id = `automation-queryToRun-${placeholderId}`;
|
|
1014
|
-
queryTextarea.classList.add(`automation-queryToRun-${placeholderId}`);
|
|
1015
|
-
queryTextarea.placeholder = "Provide me with a mindful moment, reminding me to be centered.";
|
|
1016
|
-
|
|
1017
|
-
// Create buttons container
|
|
1018
|
-
let buttonsContainer = document.createElement("div");
|
|
1019
|
-
buttonsContainer.classList.add("automation-buttons");
|
|
1020
|
-
|
|
1021
|
-
// Create cancel button
|
|
1022
|
-
let deleteButton = document.createElement("button");
|
|
1023
|
-
deleteButton.type = "button";
|
|
1024
|
-
deleteButton.classList.add("delete-automation-button", "negative-button");
|
|
1025
|
-
deleteButton.textContent = "Cancel";
|
|
1026
|
-
deleteButton.id = `delete-automation-button-${placeholderId}`;
|
|
1027
|
-
deleteButton.onclick = () => deleteAutomation(placeholderId, true);
|
|
1028
|
-
|
|
1029
|
-
// Create save button
|
|
1030
|
-
let saveButton = document.createElement("button");
|
|
1031
|
-
saveButton.type = "button";
|
|
1032
|
-
saveButton.classList.add("save-automation-button");
|
|
1033
|
-
saveButton.textContent = "Create";
|
|
1034
|
-
saveButton.id = `save-automation-button-${placeholderId}`;
|
|
1035
|
-
saveButton.onclick = () => saveAutomation(placeholderId, true);
|
|
1036
|
-
|
|
1037
|
-
// Create success message container
|
|
1038
|
-
let successMessage = document.createElement("div");
|
|
1039
|
-
successMessage.id = `automation-success-${placeholderId}`;
|
|
1040
|
-
successMessage.style.display = "none";
|
|
1041
|
-
|
|
1042
|
-
// Append schedule label and selector
|
|
1043
|
-
automationEl.appendChild(scheduleLabel);
|
|
1044
|
-
automationEl.appendChild(scheduleSelector);
|
|
1045
|
-
|
|
1046
|
-
// Append query label and textarea
|
|
1047
|
-
automationEl.appendChild(queryLabel);
|
|
1048
|
-
automationEl.appendChild(queryTextarea);
|
|
1049
|
-
|
|
1050
|
-
// Append buttons to their container
|
|
1051
|
-
buttonsContainer.appendChild(deleteButton);
|
|
1052
|
-
buttonsContainer.appendChild(saveButton);
|
|
1053
|
-
|
|
1054
|
-
// Append buttons container to automationEl
|
|
1055
|
-
automationEl.appendChild(buttonsContainer);
|
|
1056
|
-
|
|
1057
|
-
// Append success message to automationEl
|
|
1058
|
-
automationEl.appendChild(successMessage);
|
|
1059
|
-
|
|
1060
|
-
return automationEl;
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
const createAutomationButtonEl = document.getElementById("create-automation-button");
|
|
1064
|
-
createAutomationButtonEl.addEventListener("click", function(event) {
|
|
1065
|
-
event.preventDefault();
|
|
1066
|
-
|
|
1067
|
-
// Insert automationEl into the DOM
|
|
1068
|
-
let placeholderId = `automation_${Date.now()}`;
|
|
1069
|
-
let automationEl = createAutomationEl(placeholderId);
|
|
1070
|
-
document.getElementById("automations").insertBefore(automationEl, document.getElementById("automations").firstChild);
|
|
1071
|
-
|
|
1072
|
-
setupScheduleViewListener("* * * * *", placeholderId);
|
|
1073
|
-
})
|
|
1074
|
-
|
|
1075
|
-
function createPreFilledAutomation(subject, crontime, query) {
|
|
1076
|
-
document.getElementById('overlay').style.display = 'block';
|
|
1077
|
-
|
|
1078
|
-
let placeholderId = `automation_${Date.now()}`;
|
|
1079
|
-
let automationEl = createAutomationEl(placeholderId);
|
|
1080
|
-
|
|
1081
|
-
// Configure automationEl with pre-filled values
|
|
1082
|
-
automationEl.classList.add(`${placeholderId}`);
|
|
1083
|
-
automationEl.getElementsByClassName(`automation-queryToRun-${placeholderId}`)[0].value = query;
|
|
1084
|
-
|
|
1085
|
-
// Create input for subject
|
|
1086
|
-
let subjectEl = document.createElement("input");
|
|
1087
|
-
subjectEl.type = "text";
|
|
1088
|
-
subjectEl.id = `automation-subject-${placeholderId}`;
|
|
1089
|
-
subjectEl.value = subject;
|
|
1090
|
-
|
|
1091
|
-
// Insert subjectEl after label for subject
|
|
1092
|
-
let subjectLabel = automationEl.querySelector(`label[for="automation-subject-${placeholderId}"]`);
|
|
1093
|
-
automationEl.firstChild.insertAdjacentElement('afterend', subjectEl);
|
|
1094
|
-
automationEl.firstChild.label = "subject";
|
|
1095
|
-
|
|
1096
|
-
// Insert automationEl into the DOM
|
|
1097
|
-
document.getElementById("automations").insertBefore(automationEl, document.getElementById("automations").firstChild);
|
|
1098
|
-
|
|
1099
|
-
setupScheduleViewListener(crontime, placeholderId);
|
|
1100
|
-
}
|
|
1101
|
-
|
|
1102
|
-
</script>
|
|
1103
|
-
{% endblock %}
|