goto-assistant 0.3.0 → 0.4.0

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "goto-assistant",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "Lightweight, self-hosted personal AI assistant",
5
5
  "license": "MIT",
6
6
  "packageManager": "pnpm@10.29.3",
package/public/index.html CHANGED
@@ -5,13 +5,14 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
6
6
  <meta name="color-scheme" content="light dark">
7
7
  <title>goto-assistant</title>
8
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2.1.1/css/pico.min.css">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
11
  <link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,200..800&family=Instrument+Serif:ital@0;1&display=swap" rel="stylesheet">
12
12
  <link rel="stylesheet" href="/style.css">
13
- <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
14
- <script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
13
+ <script src="https://cdn.jsdelivr.net/npm/marked@15.0.12/marked.min.js"></script>
14
+ <script src="https://cdn.jsdelivr.net/npm/dompurify@3.3.1/dist/purify.min.js"></script>
15
+ <script src="https://cdn.jsdelivr.net/npm/cronstrue@2.61.0/dist/cronstrue.min.js"></script>
15
16
  <script src="/chat-core.js"></script>
16
17
  <script src="/task-chat.js"></script>
17
18
  </head>
@@ -72,6 +73,7 @@
72
73
  <button class="btn-icon" id="taskToggleBtn" title="Enable/Disable"></button>
73
74
  <button class="btn-icon" id="taskDeleteBtn" title="Delete">&#128465;</button>
74
75
  </div>
76
+ <button class="task-chat-toggle" id="taskChatToggle" title="Chat">&#128172;</button>
75
77
  </div>
76
78
  <div class="task-detail-body">
77
79
  <div class="task-info" id="taskInfo">
@@ -79,7 +81,15 @@
79
81
  <div class="task-content" id="taskContent"></div>
80
82
  <div class="task-results" id="taskResults"></div>
81
83
  </div>
82
- <div class="task-chat-panel">
84
+ <div class="task-create-empty" id="taskCreateEmpty">
85
+ <p>Describe the task you'd like to create</p>
86
+ <button class="task-create-empty-btn" id="taskCreateOpenChat">Start chatting &rarr;</button>
87
+ </div>
88
+ <div class="task-chat-panel mobile-slide-overlay" id="taskChatPanel">
89
+ <div class="task-chat-header">
90
+ <h3>Task Chat</h3>
91
+ <button class="task-chat-close" id="taskChatClose">&times;</button>
92
+ </div>
83
93
  <div class="task-chat-messages" id="taskChatMessages"></div>
84
94
  <div class="task-chat-input">
85
95
  <div class="setup-chat-input-row">
@@ -414,6 +424,7 @@
414
424
  function showChatMain() {
415
425
  document.querySelector('.chat-main').style.display = '';
416
426
  document.getElementById('taskDetail').style.display = 'none';
427
+ document.getElementById('taskChatPanel').classList.remove('open');
417
428
  disconnectTaskChat();
418
429
  }
419
430
 
@@ -507,14 +518,16 @@
507
518
  document.getElementById('taskDetailTitle').textContent = task.name || task.id;
508
519
  document.getElementById('taskDetailActions').style.display = '';
509
520
  document.getElementById('taskInfo').style.display = '';
521
+ document.querySelector('.task-detail-body').classList.remove('create-mode');
510
522
 
511
523
  // Meta
512
524
  const meta = document.getElementById('taskMeta');
525
+ const humanSchedule = task.schedule ? cronToHuman(task.schedule) : null;
513
526
  meta.innerHTML = `
514
527
  <div class="task-meta-row"><span class="task-meta-label">ID</span><span class="task-meta-value">${escapeHtml(task.id || '')}</span></div>
515
528
  <div class="task-meta-row"><span class="task-meta-label">Type</span><span class="task-meta-value"><span class="task-type-badge">${task.type === 'AI' ? 'AI' : 'CMD'}</span></span></div>
516
529
  <div class="task-meta-row"><span class="task-meta-label">Status</span><span class="task-meta-value">${task.enabled ? '<span class="task-enabled-badge">Enabled</span>' : '<span class="task-disabled-badge">Disabled</span>'}</span></div>
517
- ${task.schedule ? `<div class="task-meta-row"><span class="task-meta-label">Schedule</span><span class="task-meta-value"><code>${escapeHtml(task.schedule)}</code></span></div>` : ''}
530
+ ${task.schedule ? `<div class="task-meta-row"><span class="task-meta-label">Schedule</span><span class="task-meta-value">${humanSchedule ? `<span class="task-schedule-human">${escapeHtml(humanSchedule)}</span> <code class="task-schedule-raw">${escapeHtml(task.schedule)}</code>` : `<code>${escapeHtml(task.schedule)}</code>`}</span></div>` : ''}
518
531
  `;
519
532
 
520
533
  // Content (prompt or command)
@@ -597,7 +610,7 @@
597
610
  ? DOMPurify.sanitize(marked.parse(outputText))
598
611
  : escapeHtml(outputText);
599
612
  html += `<div class="task-result-item">
600
- <div class="task-result-header"><span class="task-result-time">${escapeHtml(time)}${duration ? ' &middot; ' + escapeHtml(duration) : ''}</span><span class="task-result-status ${status}">${isError ? 'Failed' : 'OK'}</span></div>
613
+ <div class="task-result-header"><span class="task-result-time">${escapeHtml(time)}</span><div class="task-result-header-right">${duration ? '<span class="task-result-duration">' + escapeHtml(duration) + '</span>' : ''}<span class="task-result-status ${status}">${isError ? 'Failed' : 'OK'}</span></div></div>
601
614
  <div class="task-result-output">${renderedOutput}</div>
602
615
  </div>`;
603
616
  });
@@ -611,8 +624,13 @@
611
624
  document.getElementById('taskDetailTitle').textContent = 'Create Task';
612
625
  document.getElementById('taskDetailActions').style.display = 'none';
613
626
  document.getElementById('taskInfo').style.display = 'none';
627
+ document.querySelector('.task-detail-body').classList.add('create-mode');
614
628
  showTaskDetail();
615
629
  initTaskChat(null);
630
+ // Auto-open chat overlay on mobile
631
+ if (window.matchMedia('(max-width: 768px)').matches) {
632
+ document.getElementById('taskChatPanel').classList.add('open');
633
+ }
616
634
  }
617
635
 
618
636
  // On task chat done, refresh task info without clearing chat messages
@@ -664,6 +682,17 @@
664
682
  }
665
683
  });
666
684
 
685
+ // Mobile task chat overlay toggle
686
+ document.getElementById('taskChatToggle').addEventListener('click', () => {
687
+ document.getElementById('taskChatPanel').classList.add('open');
688
+ });
689
+ document.getElementById('taskChatClose').addEventListener('click', () => {
690
+ document.getElementById('taskChatPanel').classList.remove('open');
691
+ });
692
+ document.getElementById('taskCreateOpenChat').addEventListener('click', () => {
693
+ document.getElementById('taskChatPanel').classList.add('open');
694
+ });
695
+
667
696
  connectWs();
668
697
  loadConversations();
669
698
  </script>
package/public/setup.html CHANGED
@@ -5,13 +5,13 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
6
6
  <meta name="color-scheme" content="light dark">
7
7
  <title>goto-assistant — Setup</title>
8
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css">
8
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@picocss/pico@2.1.1/css/pico.min.css">
9
9
  <link rel="preconnect" href="https://fonts.googleapis.com">
10
10
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
11
11
  <link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,200..800&family=Instrument+Serif:ital@0;1&display=swap" rel="stylesheet">
12
12
  <link rel="stylesheet" href="/style.css">
13
- <script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
14
- <script src="https://cdn.jsdelivr.net/npm/dompurify/dist/purify.min.js"></script>
13
+ <script src="https://cdn.jsdelivr.net/npm/marked@15.0.12/marked.min.js"></script>
14
+ <script src="https://cdn.jsdelivr.net/npm/dompurify@3.3.1/dist/purify.min.js"></script>
15
15
  <script src="/cron-sync.js"></script>
16
16
  <script src="/setup.js"></script>
17
17
  <script src="/chat-core.js"></script>
@@ -59,7 +59,7 @@
59
59
  </div>
60
60
 
61
61
  <!-- Right: chat panel -->
62
- <div class="setup-chat" id="setupChat">
62
+ <div class="setup-chat mobile-slide-overlay" id="setupChat">
63
63
  <div class="setup-chat-header">
64
64
  <h3>Setup Wizard</h3>
65
65
  <button class="setup-chat-close" id="chatCloseBtn">&times;</button>
package/public/style.css CHANGED
@@ -177,7 +177,8 @@ body {
177
177
  .btn-icon, .settings-btn, .hamburger-btn,
178
178
  .file-upload-btn, .file-preview-remove,
179
179
  .conversation-item .delete-btn,
180
- .setup-chat-close,
180
+ .setup-chat-close, .task-chat-close, .task-chat-toggle,
181
+ .task-create-empty-btn,
181
182
  .sidebar-tab, .task-back-btn,
182
183
  .task-detail-actions .btn-icon {
183
184
  background: none; border: none; box-shadow: none;
@@ -703,7 +704,7 @@ body {
703
704
  }
704
705
 
705
706
  .task-detail-body {
706
- flex: 1; display: flex; flex-direction: column;
707
+ flex: 1; display: flex; flex-direction: row;
707
708
  overflow: hidden;
708
709
  }
709
710
 
@@ -711,8 +712,8 @@ body {
711
712
  .task-info {
712
713
  padding: 16px 20px;
713
714
  overflow-y: auto;
714
- max-height: 50%;
715
- border-bottom: 1px solid var(--surface-border);
715
+ flex: 1;
716
+ min-width: 0;
716
717
  }
717
718
 
718
719
  .task-meta {
@@ -740,6 +741,14 @@ body {
740
741
  padding: 2px 6px;
741
742
  border-radius: 4px;
742
743
  }
744
+ .task-schedule-human {
745
+ font-weight: 450;
746
+ }
747
+ .task-schedule-raw {
748
+ font-size: 11px;
749
+ color: var(--text-muted);
750
+ margin-left: 6px;
751
+ }
743
752
 
744
753
  .task-enabled-badge {
745
754
  color: #22c55e; font-weight: 600; font-size: 12px;
@@ -783,12 +792,24 @@ body {
783
792
  }
784
793
  .task-result-header {
785
794
  display: flex; justify-content: space-between; align-items: center;
786
- padding: 6px 12px;
795
+ padding: 8px 12px;
787
796
  background: var(--surface-2);
788
797
  border-bottom: 1px solid var(--surface-border);
789
798
  }
790
799
  .task-result-time {
791
- font-size: 12px; color: var(--text-muted);
800
+ font-size: 13px; font-weight: 500; color: var(--text-secondary);
801
+ }
802
+ .task-result-header-right {
803
+ display: flex; align-items: center; gap: 8px;
804
+ }
805
+ .task-result-duration {
806
+ font-family: var(--font-mono);
807
+ font-size: 12px;
808
+ color: var(--text-secondary);
809
+ background: var(--surface-1);
810
+ border: 1px solid var(--surface-border);
811
+ padding: 1px 7px;
812
+ border-radius: 4px;
792
813
  }
793
814
  .task-result-status {
794
815
  font-size: 11px; font-weight: 700; text-transform: uppercase;
@@ -859,9 +880,75 @@ body {
859
880
  .task-result-output img { max-width: 100%; border-radius: 6px; }
860
881
 
861
882
  /* ---- Task Inline Chat ---- */
883
+ .task-chat-toggle {
884
+ display: none;
885
+ cursor: pointer; font-size: 18px; padding: 6px 8px;
886
+ border-radius: 8px;
887
+ color: var(--text-secondary);
888
+ transition: all 0.15s ease;
889
+ }
890
+ .task-chat-toggle:hover {
891
+ background: var(--surface-2);
892
+ color: var(--text-primary);
893
+ }
862
894
  .task-chat-panel {
863
- flex: 1; display: flex; flex-direction: column;
895
+ width: 360px; flex-shrink: 0;
896
+ display: flex; flex-direction: column;
864
897
  min-height: 0; overflow: hidden;
898
+ border-left: 1px solid var(--surface-border);
899
+ }
900
+ .task-chat-header {
901
+ display: none;
902
+ padding: 14px 16px;
903
+ border-bottom: 1px solid var(--surface-border);
904
+ justify-content: space-between; align-items: center;
905
+ }
906
+ .task-chat-header h3 {
907
+ margin: 0; font-size: 15px; font-weight: 600;
908
+ }
909
+ .task-chat-close {
910
+ font-size: 20px; cursor: pointer;
911
+ color: var(--text-muted); padding: 4px 8px;
912
+ border-radius: 8px;
913
+ transition: all 0.15s ease;
914
+ }
915
+ .task-chat-close:hover {
916
+ background: var(--surface-2);
917
+ color: var(--text-primary);
918
+ }
919
+ .task-detail-body.create-mode .task-chat-panel {
920
+ flex: 1; width: auto; border-left: none;
921
+ }
922
+ .task-create-empty {
923
+ display: none;
924
+ flex-direction: column;
925
+ align-items: center;
926
+ justify-content: center;
927
+ gap: 16px;
928
+ color: var(--text-muted);
929
+ font-style: italic;
930
+ font-family: var(--font-display);
931
+ font-size: 18px;
932
+ }
933
+ .task-create-empty p { margin: 0; }
934
+ .task-detail-body.create-mode .task-create-empty { display: flex; flex: 1; }
935
+ .task-create-empty-btn {
936
+ background: var(--accent);
937
+ color: #fff;
938
+ border-radius: 12px;
939
+ padding: 10px 24px;
940
+ font-family: var(--font-body);
941
+ font-weight: 600;
942
+ font-size: 14px;
943
+ cursor: pointer;
944
+ transition: all 0.15s ease;
945
+ }
946
+ .task-create-empty-btn:hover {
947
+ background: var(--accent-hover);
948
+ transform: translateY(-1px);
949
+ }
950
+ @media (min-width: 769px) {
951
+ .task-create-empty { display: none !important; }
865
952
  }
866
953
  .task-chat-messages {
867
954
  flex: 1; overflow-y: auto; padding: 16px 20px;
@@ -1060,26 +1147,37 @@ body {
1060
1147
 
1061
1148
  .input-area { padding: 10px 12px 12px; padding-bottom: calc(12px + env(safe-area-inset-bottom)); }
1062
1149
  .input-row { max-width: 100%; border-radius: 16px; }
1063
- .input-row textarea { font-size: 16px; }
1150
+ .input-row textarea,
1151
+ .setup-chat-input-row textarea { font-size: 16px; }
1064
1152
 
1065
1153
  .conversation-item .delete-btn { display: inline-block; }
1066
1154
 
1067
1155
  /* Task mobile */
1068
1156
  .task-meta { flex-direction: column; gap: 4px; }
1069
- .task-chat-panel { min-height: 200px; }
1070
- .task-info { max-height: 40%; }
1157
+ .task-detail-body { flex-direction: column; }
1158
+ .task-info { flex: 1; }
1159
+ .task-chat-toggle { display: block; }
1160
+ /* Shared mobile slide-in overlay */
1161
+ .mobile-slide-overlay {
1162
+ position: fixed; top: 0; right: 0; bottom: 0; left: 0;
1163
+ /* width: 100% is NOT redundant — it overrides explicit widths (e.g. .setup-chat 400px,
1164
+ .task-chat-panel 360px) that would otherwise take priority over left/right: 0 */
1165
+ width: 100%;
1166
+ background: var(--surface-0);
1167
+ transform: translateX(100%);
1168
+ transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1169
+ z-index: 15;
1170
+ }
1171
+ .mobile-slide-overlay.open { transform: translateX(0); }
1172
+
1173
+ .task-chat-panel { border-left: none; }
1174
+ .task-chat-header { display: flex; }
1071
1175
  .task-chat-input { padding: 10px 12px 12px; padding-bottom: calc(12px + env(safe-area-inset-bottom)); }
1072
1176
 
1073
1177
  /* Setup mobile */
1074
1178
  .setup-layout { flex-direction: column; }
1075
1179
  .setup-form { flex: 1; }
1076
- .setup-chat {
1077
- position: fixed; top: 0; right: 0; bottom: 0; width: 100%;
1078
- transform: translateX(100%);
1079
- transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
1080
- z-index: 15;
1081
- }
1082
- .setup-chat.open { transform: translateX(0); }
1180
+ .setup-chat-input { padding-bottom: calc(12px + env(safe-area-inset-bottom)); }
1083
1181
  }
1084
1182
 
1085
1183
  @media (min-width: 769px) {
@@ -1,6 +1,15 @@
1
1
  // task-chat.js — Inline chat logic for task mode (create/modify tasks via AI).
2
2
  // Loaded as a plain <script> in the browser; importable via require() in tests.
3
3
 
4
+ function cronToHuman(expr) {
5
+ if (typeof cronstrue === 'undefined' || !expr) return null;
6
+ try {
7
+ return cronstrue.toString(expr);
8
+ } catch (e) {
9
+ return null;
10
+ }
11
+ }
12
+
4
13
  var taskRunState = {};
5
14
  // Shape: { [taskId]: { pollTimer, timeoutTimer, runStartTime, pendingResult } }
6
15
  // pendingResult: null while running, string (chat text) when result arrived but user wasn't viewing this task
@@ -241,6 +250,7 @@ function disconnectTaskChat() {
241
250
 
242
251
  if (typeof module !== 'undefined' && module.exports) {
243
252
  module.exports = {
253
+ cronToHuman: cronToHuman,
244
254
  taskChatState: taskChatState,
245
255
  taskRunState: taskRunState,
246
256
  cancelTaskRun: cancelTaskRun,