plainbook 0.0.6__py3-none-any.whl → 0.0.8__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.
@@ -0,0 +1,76 @@
1
+ export default {
2
+ props: ['isLocked', 'running', 'hasNotebook', 'lastRunIndex', 'cellCount', 'hasApiKey'],
3
+ emits: [
4
+ 'lock', 'refresh', 'interrupt', 'regenerate-all',
5
+ 'reset-run-all', 'open-info', 'open-settings'
6
+ ],
7
+ template: /* html */ `
8
+ <nav class="navbar is-dark is-fixed-top" role="navigation" aria-label="main navigation">
9
+ <div id="the-navbar-menu" class="navbar-menu">
10
+ <div class="navbar-start">
11
+ <div class="navbar-item">
12
+ <div class="buttons">
13
+ <button v-if="isLocked" class="button is-warning" title="Unlock Notebook" @click="$emit('lock', false)">
14
+ <span class="icon"><i class="fa fa-lock"></i></span>
15
+ </button>
16
+ <button v-else class="button is-light" title="Lock Notebook" @click="$emit('lock', true)">
17
+ <span class="icon"><i class="fa fa-unlock"></i></span>
18
+ </button>
19
+
20
+ <button v-if="!running && hasNotebook"
21
+ @click="$emit('refresh')"
22
+ class="button is-light" title="Reload Notebook">
23
+ <span class="icon"><i class="fa fa-refresh"></i></span>
24
+ <span>Refresh</span>
25
+ </button>
26
+
27
+ <button v-if="running && hasNotebook"
28
+ @click="$emit('interrupt')"
29
+ class="button is-danger" title="Interrupt Execution">
30
+ <span class="icon"><i class="fa fa-stop"></i></span>
31
+ <span>Running...</span>
32
+ </button>
33
+
34
+ <button v-if="!running && hasNotebook && lastRunIndex >= cellCount - 1"
35
+ class="button is-light" title="All cells have been run">
36
+ <span class="icon"><i class="fa fa-check-circle"></i></span>
37
+ <span>Up to Date</span>
38
+ </button>
39
+
40
+ <button v-if="!running && hasNotebook"
41
+ :disabled="cellCount === 0 || isLocked"
42
+ @click="$emit('regenerate-all')"
43
+ title="Regenerate all code from descriptions"
44
+ class="button is-success">
45
+ <span class="icon"><i class="fa fa-repeat"></i></span>
46
+ <span>Regenerate All</span>
47
+ </button>
48
+
49
+ <button v-if="!running && hasNotebook"
50
+ :disabled="cellCount === 0"
51
+ @click="$emit('reset-run-all')"
52
+ title="Reset and run all cells"
53
+ class="button is-primary">
54
+ <span class="icon"><i class="fa fa-play"></i></span>
55
+ <span>Reset and Run All</span>
56
+ </button>
57
+ </div>
58
+ </div>
59
+ </div>
60
+ <div class="navbar-end">
61
+ <div class="navbar-item">
62
+ <div class="buttons">
63
+ <button class="button is-light" @click="$emit('open-info')" title="About Plainbook">
64
+ <span class="icon"><i class="fa fa-info"></i></span>
65
+ </button>
66
+ <button class="button" :class="hasApiKey ? 'is-light' : 'is-warning'"
67
+ @click="$emit('open-settings')" title="Settings">
68
+ <span class="icon"><i :class="hasApiKey ? 'fa fa-cog' : 'fa fa-warning'"></i></span>
69
+ <span>Settings</span>
70
+ </button>
71
+ </div>
72
+ </div>
73
+ </div>
74
+ </div>
75
+ </nav>`
76
+ };
@@ -0,0 +1,21 @@
1
+ // CellInsertionZone.js
2
+ export default {
3
+ props: [],
4
+ emits: ['insert'],
5
+ template: /* html */ `
6
+ <div class="cell-insert-zone">
7
+ <div class="cell-insert-buttons">
8
+ <button
9
+ class="button insert-cell is-info is-small py-0 px-3"
10
+ @click.stop="$emit('insert', 'markdown')">
11
+ Insert Comment
12
+ </button>
13
+ <button
14
+ class="button insert-cell is-info is-small py-0 px-3"
15
+ @click.stop="$emit('insert', 'code')">
16
+ Insert Action
17
+ </button>
18
+ </div>
19
+ </div>
20
+ `
21
+ };
plainbook/js/CodeCell.js CHANGED
@@ -128,7 +128,8 @@ export default {
128
128
  if (!isCollapsed.value && isEditing.value) nextTick(autoResize);
129
129
  };
130
130
 
131
- return { isCollapsed, toggleCollapse, isEditing, cancelEdit, localSource, highlightedCode, enterEditMode, saveCode, textareaEl, autoResize, handleTabKey };
131
+ return { isCollapsed, toggleCollapse, isEditing, cancelEdit, localSource,
132
+ localIsLocked, highlightedCode, enterEditMode, saveCode, textareaEl, autoResize, handleTabKey };
132
133
  },
133
134
  template: /* html */ `
134
135
  <div class="code-cell-wrapper" style="position: relative; min-height: 1.75rem;">
@@ -1,7 +1,7 @@
1
1
  import { ref, computed, watch, nextTick } from './vue.esm-browser.js';
2
2
 
3
3
  const ExplanationRenderer = {
4
- props: ['source', 'isActive', 'index', 'lastRunIndex', 'asRead', 'startEditKey', 'isLocked'],
4
+ props: ['source', 'isActive', 'needsRunning', 'asRead', 'startEditKey', 'isLocked'],
5
5
  emits: ['update:source', 'save', 'saveandrun', 'gencode', 'validate',
6
6
  'run', 'delete', 'moveUp', 'moveDown'],
7
7
  setup(props, { emit }) {
@@ -95,19 +95,11 @@ const ExplanationRenderer = {
95
95
  <div class="toolbar-left">
96
96
  <button class="button run-button is-small is-primary mr-1"
97
97
  title="Run this cell and all necessary preceding cells" @click.stop="$emit('run')">
98
- <template v-if="lastRunIndex === index">
99
- <span class="icon"><i class="fa fa-repeat"></i></span><span>Re-Run</span>
100
- </template>
101
- <template v-else-if="lastRunIndex < index">
102
- <span class="icon"><i class="fa fa-step-forward"></i></span><span>Run Up To Here</span>
103
- </template>
104
- <template v-else>
105
- <span class="icon"><i class="fa fa-step-forward"></i></span><span>Run From Start To Here</span>
106
- </template>
98
+ <span class="icon"><i class="fa fa-step-forward"></i></span><span>Run</span>
107
99
  </button>
108
100
  <button class="button is-small" style="opacity: 0.6;">
109
101
  <span v-if="asRead">Unmodified</span>
110
- <span v-else-if="lastRunIndex < index">Needs running</span>
102
+ <span v-else-if="needsRunning">Needs running</span>
111
103
  <span v-else>Up to date</span>
112
104
  </button>
113
105
  </div>
@@ -159,7 +151,8 @@ const ExplanationRenderer = {
159
151
  Save
160
152
  </button>
161
153
  <button class="button is-small is-primary" :disabled="localIsLocked" @click="saveAndRun">
162
- Save and Run
154
+ <span class="icon"><i class="fa fa-play"></i></span>
155
+ <span>Save and Run</span>
163
156
  </button>
164
157
  </div>
165
158
  </div>
@@ -0,0 +1,29 @@
1
+ export default {
2
+ props: ['isActive'],
3
+ emits: ['close'],
4
+ template: /* html */ `
5
+ <div class="modal" :class="{'is-active': isActive}">
6
+ <div class="modal-background" @click="$emit('close')"></div>
7
+ <div class="modal-card">
8
+ <header class="modal-card-head">
9
+ <p class="modal-card-title">Information</p>
10
+ <button class="delete" aria-label="close" @click="$emit('close')"></button>
11
+ </header>
12
+ <section class="modal-card-body">
13
+ <h1 class="title">Plainbook</h1>
14
+ <div class="content">
15
+ <p>Plainbook is an interactive notebook application for creating executable documents in natural language.</p>
16
+ <ul>
17
+ <li><strong>Action cells:</strong> Generate Python code from English explanations using AI.</li>
18
+ <li><strong>Markdown cells:</strong> Rich text for documentation.</li>
19
+ </ul>
20
+ <p>Locking prevents accidental edits while allowing validation and execution.</p>
21
+ <p><a href="https://github.com/lucadealfaro/plainbook" target="_blank">Plainbook Home Page</a></p>
22
+ </div>
23
+ </section>
24
+ <footer class="modal-card-foot">
25
+ <button class="button" @click="$emit('close')">Close</button>
26
+ </footer>
27
+ </div>
28
+ </div>`
29
+ };
@@ -1,7 +1,7 @@
1
1
  import { ref, watch, nextTick } from './vue.esm-browser.js';
2
2
 
3
3
  const MarkdownCell = {
4
- props: ['source', 'startEditKey', 'isActive', 'index', 'isLocked'],
4
+ props: ['source', 'startEditKey', 'isActive', 'isLocked'],
5
5
  emits: ['save', 'delete', 'moveUp', 'moveDown'],
6
6
  setup(props, { emit }) {
7
7
  const md = new markdownit({ html: true });
@@ -0,0 +1,72 @@
1
+ // NotebookCell.js
2
+ import MarkdownCell from './MarkdownCell.js';
3
+ import CodeCell from './CodeCell.js';
4
+ import ExplanationEditor from './ExplanationEditor.js';
5
+ import ValidationCell from './ValidationCell.js';
6
+ import OutputRenderer from './OutputRenderer.js';
7
+
8
+ export default {
9
+ components: { MarkdownCell, CodeCell, ExplanationEditor, ValidationCell, OutputRenderer },
10
+ props: ['cell', 'isActive', 'isLocked', 'needsRunning', 'asRead', 'markdownEditKey', 'explanationEditKey'],
11
+ emits: [
12
+ 'save-markdown', 'save-explanation', 'save-code',
13
+ 'run-cell', 'save-and-run', 'generate-code',
14
+ 'validate-code', 'dismiss-validation',
15
+ 'delete', 'move-up', 'move-down'
16
+ ],
17
+ template: /* html */ `
18
+ <div class="notebook-cell box p-0 mb-2 is-clipped shadow-sm"
19
+ :style="{
20
+ border: isActive ? '2px solid #1d4ed8' : '1px solid transparent',
21
+ cursor: 'pointer'
22
+ }">
23
+
24
+ <markdown-cell
25
+ v-if="cell.cell_type === 'markdown'"
26
+ v-model:source="cell.source"
27
+ :is-active="isActive"
28
+ :start-edit-key="markdownEditKey"
29
+ :isLocked="isLocked"
30
+ @save="$emit('save-markdown', $event)"
31
+ @delete="$emit('delete')"
32
+ @moveUp="$emit('move-up')"
33
+ @moveDown="$emit('move-down')" />
34
+
35
+ <div v-else-if="cell.cell_type === 'code'">
36
+ <div v-if="cell.metadata?.explanation" class="has-background-light p-0 border-bottom">
37
+ <explanation-editor
38
+ v-model:source="cell.metadata.explanation"
39
+ :isActive="isActive"
40
+ :isLocked="isLocked"
41
+ :asRead="asRead"
42
+ :needs-running="needsRunning"
43
+ :start-edit-key="explanationEditKey"
44
+ @save="$emit('save-explanation', $event)"
45
+ @gencode="$emit('generate-code')"
46
+ @validate="$emit('validate-code')"
47
+ @run="$emit('run-cell')"
48
+ @saveandrun="$emit('save-and-run', $event)"
49
+ @delete="$emit('delete')"
50
+ @moveUp="$emit('move-up')"
51
+ @moveDown="$emit('move-down')" />
52
+ </div>
53
+
54
+ <validation-cell
55
+ v-if="cell.metadata?.validation && !cell.metadata?.validation.is_hidden"
56
+ :validation="cell.metadata.validation"
57
+ @dismiss_validation="$emit('dismiss-validation')" />
58
+
59
+ <code-cell
60
+ v-model:source="cell.source"
61
+ :execution-count="cell.execution_count"
62
+ :is-active="isActive"
63
+ :is-locked="isLocked"
64
+ @save="$emit('save-code', $event)" />
65
+
66
+ <div v-if="cell.outputs?.length" class="p-2 border-top has-background-white">
67
+ <output-renderer v-for="(out, oIdx) in cell.outputs" :key="oIdx" :output="out" />
68
+ </div>
69
+ </div>
70
+ </div>
71
+ `
72
+ };
@@ -0,0 +1,46 @@
1
+ import { ref, watch } from './vue.esm-browser.js';
2
+
3
+ export default {
4
+ props: ['isActive', 'apiKey'],
5
+ emits: ['close', 'save'],
6
+ setup(props, { emit }) {
7
+ // Create a local draft of the API key
8
+ const localKey = ref(props.apiKey);
9
+
10
+ // Sync local draft whenever the modal is opened with the current parent value
11
+ watch(() => props.isActive, (active) => {
12
+ if (active) {
13
+ localKey.value = props.apiKey;
14
+ }
15
+ });
16
+
17
+ const handleSave = () => {
18
+ emit('save', localKey.value);
19
+ };
20
+
21
+ return { localKey, handleSave };
22
+ },
23
+ template: /* html */ `
24
+ <div class="modal" :class="{'is-active': isActive}">
25
+ <div class="modal-background" @click="$emit('close')"></div>
26
+ <div class="modal-card">
27
+ <header class="modal-card-head">
28
+ <p class="modal-card-title">Settings</p>
29
+ <button class="delete" aria-label="close" @click="$emit('close')"></button>
30
+ </header>
31
+ <section class="modal-card-body">
32
+ <div class="field">
33
+ <label class="label">Gemini API Key</label>
34
+ <div class="control">
35
+ <input class="input" type="text"
36
+ v-model="localKey"
37
+ placeholder="Enter your Gemini API key">
38
+ </div>
39
+ </div>
40
+ </section>
41
+ <footer class="modal-card-foot" style="justify-content: flex-end;">
42
+ <button class="button is-primary" @click="handleSave">Save</button>
43
+ </footer>
44
+ </div>
45
+ </div>`
46
+ };
@@ -1,7 +1,7 @@
1
1
  import {ref, watch, nextTick, computed} from './vue.esm-browser.js';
2
2
 
3
3
  export default {
4
- props: ['validation', 'index'],
4
+ props: ['validation'],
5
5
  emits: ['dismiss_validation'],
6
6
 
7
7
  setup(props, { emit }) {
@@ -25,7 +25,7 @@ export default {
25
25
  if (is_hidden.value) {
26
26
  is_hidden.value = true;
27
27
  }
28
- emit('dismiss_validation', props.index);
28
+ emit('dismiss_validation');
29
29
  };
30
30
  return { dismiss, renderedMarkdown, message, is_valid, is_hidden };
31
31
  },
plainbook/js/nb.js CHANGED
@@ -1,13 +1,13 @@
1
1
  import { createApp, ref, onMounted, onBeforeUnmount, nextTick, getCurrentInstance } from './vue.esm-browser.js';
2
2
 
3
- import MarkdownCell from './MarkdownCell.js';
4
- import OutputRenderer from './OutputRenderer.js';
5
- import CodeCell from './CodeCell.js';
6
- import ExplanationEditor from './ExplanationEditor.js';
7
- import ValidationCell from './ValidationCell.js';
3
+ import AppNavbar from './AppNavbar.js';
4
+ import NotebookCell from './NotebookCell.js';
5
+ import CellInsertionZone from './CellInsertionZone.js';
6
+ import SettingsModal from './SettingsModal.js';
7
+ import InfoModal from './InfoModal.js';
8
8
 
9
9
  createApp({
10
- components: { MarkdownCell, CodeCell, ExplanationEditor, OutputRenderer, ValidationCell },
10
+ components: { AppNavbar, NotebookCell, CellInsertionZone, SettingsModal, InfoModal },
11
11
  setup() {
12
12
  // Extract token from URL
13
13
  const urlParams = new URLSearchParams(window.location.search);
@@ -152,6 +152,9 @@ createApp({
152
152
  };
153
153
 
154
154
  const generateCode = async (cellIndex) => {
155
+ if (!geminiApiKey.value) {
156
+ throw new Error('Gemini API key is not set. Please set it in the settings.');
157
+ };
155
158
  asRead.value = false;
156
159
  try {
157
160
  const response = await fetch(`/generate_code?token=${authToken}`, {
@@ -172,6 +175,9 @@ createApp({
172
175
  };
173
176
 
174
177
  const regenerateAllCode = async () => {
178
+ if (!geminiApiKey.value) {
179
+ throw new Error('Gemini API key is not set. Please set it in the settings.');
180
+ };
175
181
  for (let i = 0; i < notebook.value.cells.length; i++) {
176
182
  if (notebook.value.cells[i].cell_type === 'code') {
177
183
  await generateCode(i);
@@ -185,6 +191,9 @@ createApp({
185
191
  };
186
192
 
187
193
  const validateCode = async (cellIndex) => {
194
+ if (!geminiApiKey.value) {
195
+ throw new Error('Gemini API key is not set. Please set it in the settings.');
196
+ };
188
197
  asRead.value = false;
189
198
  try {
190
199
  const response = await fetch(`/validate_code?token=${authToken}`, {
@@ -386,6 +395,8 @@ createApp({
386
395
  const r = await response.json();
387
396
  if (r.status === 'error') {
388
397
  throw new Error(r.message || 'Execution failed');
398
+ } else if (r.details !== 'ok') {
399
+ throw new Error('Cell execution error');
389
400
  } else {
390
401
  console.log('Cell executed:', cellIndex, r.details);
391
402
  // Update outputs in the notebook model
@@ -396,9 +407,11 @@ createApp({
396
407
  if (r.last_executed_cell !== undefined && r.last_executed_cell !== null) {
397
408
  lastRunIndex.value = r.last_executed_cell;
398
409
  }
410
+ //
399
411
  }
400
412
  } catch (err) {
401
- throw new Error('Run error: ' + err.message);
413
+ running.value = false; // No longer running.
414
+ throw new Error(err.message);
402
415
  }
403
416
  };
404
417
 
@@ -452,13 +465,7 @@ createApp({
452
465
  }
453
466
  };
454
467
 
455
- const openSettings = () => {
456
- // Get the Gemini API key from the server settings.
457
-
458
- showSettings.value = true;
459
- };
460
-
461
- const closeSettings = async () => {
468
+ const saveSettings = async (newKey) => {
462
469
  // Save the Gemini API key to the server
463
470
  try {
464
471
  const response = await fetch(`/set_key?token=${authToken}`, {
@@ -474,21 +481,13 @@ createApp({
474
481
  } catch (err) {
475
482
  throw new Error('Error saving API key: ' + err.message);
476
483
  }
477
- showSettings.value = false;
484
+ geminiApiKey.value = newKey;
478
485
  };
479
486
 
480
487
  const genError = () => {
481
488
  throw new Error('This is a generated error for testing purposes. This is a generated error for testing purposes. This is a generated error for testing purposes. This is a generated error for testing purposes. ');
482
489
  }
483
490
 
484
- const openInfo = () => {
485
- showInfo.value = true;
486
- };
487
-
488
- const closeInfo = () => {
489
- showInfo.value = false;
490
- };
491
-
492
491
  const closeUiError = () => {
493
492
  uiError.value = null;
494
493
  };
@@ -519,8 +518,7 @@ createApp({
519
518
  validateCode, dismissValidation, resetAndRunAllCells,
520
519
  setActiveCell, runCell, running, lastRunIndex, asRead, runAllCells,
521
520
  interruptKernel, insertCell, markdownEditKey,
522
- openSettings, closeSettings, showSettings,
523
- openInfo, closeInfo, showInfo,
521
+ saveSettings, showSettings, showInfo,
524
522
  genError, uiError, closeUiError,
525
523
  explanationEditKey, deleteCell, moveCell, geminiApiKey };
526
524
  },
plainbook/main.py CHANGED
@@ -183,7 +183,8 @@ def execute_cell():
183
183
  print(f"Executing cell {cell_index}")
184
184
  try:
185
185
  outputs, details = notebook.execute_cell(cell_index)
186
- return dict(status="ok", details=details, outputs=outputs, last_executed_cell=notebook.last_executed_cell)
186
+ return dict(status="ok", details=details,
187
+ outputs=outputs, last_executed_cell=notebook.last_executed_cell)
187
188
  except CellExecutionError as e:
188
189
  # The execution error is already captured in the cell outputs.
189
190
  return dict(status="ok", details="CellExecutionError",
@@ -29,235 +29,100 @@
29
29
  </style>
30
30
  </head>
31
31
  <body class="has-navbar-fixed-top">
32
-
33
- <div id="app"></div>
34
32
 
35
33
  <script type="text/x-template" id="app-template">
36
- <nav class="navbar is-dark is-fixed-top" role="navigation" aria-label="main navigation">
37
- <div id="the-navbar-menu" class="navbar-menu">
38
- <div class="navbar-start">
39
- <div class="navbar-item">
40
- <div class="buttons">
41
- <!-- Locking -->
42
- <button v-if="isLocked" class="button is-warning" title="Unlock Notebook" @click="lockNotebook(false)">
43
- <span class="icon"><i class="fa fa-lock"></i></span>
44
- </button>
45
- <button v-else class="button is-light" title="Lock Notebook" @click="lockNotebook(true)">
46
- <span class="icon"><i class="fa fa-unlock"></i></span>
47
- </button>
48
- <!-- Reload notebook -->
49
- <button v-if="!running && notebook"
50
- @click="reloadNotebook"
51
- class="button is-light" title="Reload Notebook">
52
- <span class="icon"><i class="fa fa-refresh"></i></span>
53
- <span>Refresh</span>
54
- </button>
55
- <!-- Interrupt execution -->
56
- <button v-if="running && notebook"
57
- @click="interruptKernel"
58
- class="button is-danger" title="Interrupt Execution">
59
- <span class="icon"><i class="fa fa-stop"></i></span>
60
- <span>Running...</span>
61
- </button>
62
- <!-- Status -->
63
- <button v-if="!running && notebook && lastRunIndex >= notebook.cells.length - 1"
64
- class="button is-light" title="All cells have been run">
65
- <span class="icon"><i class="fa fa-check-circle"></i></span>
66
- <span>Up to Date</span>
67
- </button>
68
- <!-- Running -->
69
- <!-- <button v-if="!running && notebook"
70
- :disabled="notebook.cells.length === 0 || isLocked"
71
- @click="regenerateAndRunAllCode" title="Regenerate all code from descriptions and run it all"
72
- class="button is-success">
73
- <span class="icon"><i class="fa fa-repeat"></i></span>
74
- <span class="icon"><i class="fa fa-play"></i></span>
75
- </button> -->
76
- <button v-if="!running && notebook"
77
- :disabled="notebook.cells.length === 0 || isLocked"
78
- @click="regenerateAllCode" title="Regenerate all code from descriptions"
79
- class="button is-success">
80
- <span class="icon"><i class="fa fa-repeat"></i></span>
81
- <span>Regenerate All</span>
82
- </button>
83
- <button v-if="!running && notebook"
84
- :disabled="notebook.cells.length === 0"
85
- @click="resetAndRunAllCells" title="Reset and run all cells"
86
- class="button is-primary">
87
- <span class="icon"><i class="fa fa-play"></i></span>
88
- <span>Reset and Run All</span>
89
- </button>
90
- </div>
91
- </div>
92
- </div>
93
- <div class="navbar-end">
94
- <div class="navbar-item">
95
- <div class="buttons">
96
- <!-- <button id="btn-info" class="button is-danger" @click="genError">
97
- <span class="icon"><i class="fa fa-exclamation"></i></span>
98
- </button> -->
99
- <button id="btn-info" class="button is-light" @click="openInfo" title="About Plainbook">
100
- <span class="icon"><i class="fa fa-info"></i></span>
101
- </button>
102
- <button id="btn-settings" class="button is-light" @click="openSettings" title="Settings">
103
- <span class="icon"><i :class="geminiApiKey ? 'fa fa-cog' : 'fa fa-warning'"></i></span>
104
- <span>Settings</span>
105
- </button>
106
- </div>
107
- </div>
108
- </div>
109
- </div>
110
- </nav>
111
34
 
112
- <div v-if="uiError" class="notification mb-0 mt-0 px-4 pl-2 pr-6 has-text-danger has-background-danger-light"
113
- style="width: 100%; border-radius: 0; align-items: center; justify-content: space-between;">
114
- <span><strong>Error:</strong> {{ uiError }}</span>
115
- <button class="delete" @click="closeUiError"></button>
116
- </div>
35
+ <app-navbar
36
+ :is-locked="isLocked"
37
+ :running="running"
38
+ :has-notebook="!!notebook"
39
+ :last-run-index="lastRunIndex"
40
+ :cell-count="notebook ? notebook.cells.length : 0"
41
+ :has-api-key="!!geminiApiKey"
42
+ @lock="lockNotebook"
43
+ @refresh="reloadNotebook"
44
+ @interrupt="interruptKernel"
45
+ @regenerate-all="regenerateAllCode"
46
+ @reset-run-all="resetAndRunAllCells"
47
+ @open-info="showInfo = true"
48
+ @open-settings="showSettings = true"
49
+ />
117
50
 
118
- <div class="section notebook-area">
119
- <h1 class="title">
120
- {{ notebook_name }}
121
- </h1>
122
- <div class="notebook-container px-2 py-2">
123
- <div v-if="loading" class="has-text-centered py-6">
124
- <button class="button is-loading is-ghost is-large">Loading</button>
125
- <p class="has-text-grey">Fetching notebook data...</p>
126
- </div>
127
- <div v-else-if="error" class="notification is-danger is-light">
128
- <button class="delete" @click="error = null"></button>
129
- <strong>Error:</strong> {{ error }}
130
- </div>
131
- <div v-else="notebook">
132
- <div v-if="!isLocked && notebook.cells.length === 0" class="py-1" style="display:flex; justify-content:center; gap:0.5rem;">
133
- <button class="button insert-cell is-info is-small py-0 px-3" @click.stop="insertCell(0, 'markdown')">Insert Comment</button>
134
- <button class="button insert-cell is-info is-small py-0 px-3" @click.stop="insertCell(0, 'code')">Insert Action</button>
51
+ <div v-if="uiError" class="notification mb-0 mt-0 px-4 pl-2 pr-6 has-text-danger has-background-danger-light"
52
+ style="width: 100%; border-radius: 0; align-items: center; justify-content: space-between;">
53
+ <span><strong>Error:</strong> {{ uiError }}</span>
54
+ <button class="delete" @click="closeUiError"></button>
55
+ </div>
56
+
57
+ <div class="section notebook-area">
58
+ <h1 class="title">
59
+ {{ notebook_name }}
60
+ </h1>
61
+ <div class="notebook-container px-2 py-2">
62
+ <div v-if="loading" class="has-text-centered py-6">
63
+ <button class="button is-loading is-ghost is-large">Loading</button>
64
+ <p class="has-text-grey">Fetching notebook data...</p>
65
+ </div>
66
+ <div v-else-if="error" class="notification is-danger is-light">
67
+ <button class="delete" @click="error = null"></button>
68
+ <strong>Error:</strong> {{ error }}
135
69
  </div>
136
- <template v-for="(cell, index) in notebook.cells" :key="index">
137
- <!-- Insert zone before each cell -->
138
- <div v-if="!isLocked" class="cell-insert-zone">
139
- <div class="cell-insert-buttons">
140
- <button class="button insert-cell is-info is-small py-0 px-3" @click.stop="insertCell(index, 'markdown')">Insert Comment</button>
141
- <button class="button insert-cell is-info is-small py-0 px-3" @click.stop="insertCell(index, 'code')">Insert Action</button>
142
- </div>
143
- </div>
70
+ <div v-else="notebook">
144
71
 
145
- <div class="notebook-cell box p-0 mb-2 is-clipped shadow-sm"
146
- @click="setActiveCell(index)"
147
- :style="{
148
- border: activeIndex === index ? '2px solid #1d4ed8' : '1px solid transparent',
149
- cursor: 'pointer'
150
- }">
151
- <markdown-cell v-if="cell.cell_type === 'markdown'" :source="cell.source"
152
- :is-active="activeIndex === index"
153
- :start-edit-key="markdownEditKey[index]"
154
- :index="index"
155
- :isLocked="isLocked"
156
- @save="(content) => sendMarkdownToServer(content, index)"
157
- @delete="deleteCell(index)"
158
- @moveUp="moveCell(index, -1)"
159
- @moveDown="moveCell(index, 1)" />
72
+ <cell-insertion-zone v-if="!isLocked"
73
+ @insert="(celltype) => insertCell(0, celltype)"
74
+ />
160
75
 
161
- <div v-if="cell.cell_type === 'code'">
162
- <div v-if="cell.metadata?.explanation" class="has-background-light p-0 border-bottom">
163
- <explanation-editor :source="cell.metadata.explanation"
164
- :isActive="activeIndex === index"
165
- :isLocked="isLocked"
166
- :index="index" :asRead="asRead"
167
- :lastRunIndex="lastRunIndex"
168
- :start-edit-key="explanationEditKey[index]"
169
- @save="(content) => sendExplanationToServer(content, index)"
170
- @gencode="() => generateCode(index)"
171
- @validate="() => validateCode(index)"
172
- @run="() => runCell(index)"
173
- @saveandrun="(content) => saveExplanationAndRun (content, index)"
174
- @delete="deleteCell(index)"
175
- @moveUp="moveCell(index, -1)"
176
- @moveDown="moveCell(index, 1)" />
177
- </div>
178
- <div v-if="cell.metadata?.validation && !cell.metadata?.validation.is_hidden">
179
- <validation-cell :validation="cell.metadata.validation"
180
- :index="index"
181
- @dismiss_validation="dismissValidation" />
182
- </div>
183
- <code-cell :source="cell.source"
184
- :execution-count="cell.execution_count"
185
- :is-active="activeIndex === index"
186
- :is-locked="isLocked"
187
- @save="(content) => sendCodeToServer(content, index)" />
76
+ <template v-for="(cell, index) in notebook.cells" :key="index">
77
+
78
+ <notebook-cell
79
+ :cell="cell"
80
+ :is-active="activeIndex === index"
81
+ :needs-running="index > lastRunIndex"
82
+ :is-locked="isLocked"
83
+ :last-run-index="lastRunIndex"
84
+ :as-read="asRead"
85
+ :markdown-edit-key="markdownEditKey[index]"
86
+ :explanation-edit-key="explanationEditKey[index]"
87
+ @click="setActiveCell(index)"
188
88
 
189
- <div v-if="cell.outputs?.length" class="p-2 border-top has-background-white">
190
- <output-renderer v-for="(out, oIdx) in cell.outputs" :key="oIdx" :output="out" />
191
- </div>
192
- </div>
193
- </div>
194
- </template>
195
-
196
- <!-- Insert zone after the last cell -->
197
- <div v-if="!isLocked && !notebook.cells.length > 0" class="cell-insert-zone">
198
- <div class="cell-insert-buttons">
199
- <button class="button insert-cell is-info is-small py-0 px-3" @click.stop="insertCell(notebook.cells.length, 'markdown')">Insert Comment</button>
200
- <button class="button insert-cell is-info is-small py-0 px-3" @click.stop="insertCell(notebook.cells.length, 'code')">Insert Action</button>
201
- </div>
202
- </div>
203
- </div>
204
- </div>
205
- </div>
89
+ @save-markdown="(content) => sendMarkdownToServer(content, index)"
90
+ @save-explanation="(content) => sendExplanationToServer(content, index)"
91
+ @save-code="(content) => sendCodeToServer(content, index)"
92
+ @save-and-run="(content) => saveExplanationAndRun(content, index)"
93
+
94
+ @run-cell="runCell(index)"
95
+ @generate-code="generateCode(index)"
96
+ @validate-code="validateCode(index)"
97
+ @dismiss-validation="dismissValidation(index)"
98
+
99
+ @delete="deleteCell(index)"
100
+ @move-up="moveCell(index, -1)"
101
+ @move-down="moveCell(index, 1)"
102
+ />
206
103
 
207
- <!-- Settings Modal -->
208
- <div class="modal" :class="{'is-active': showSettings}">
209
- <div class="modal-background" @click="closeSettings"></div>
210
- <div class="modal-card">
211
- <header class="modal-card-head">
212
- <p class="modal-card-title">Settings</p>
213
- <button class="delete" aria-label="close" @click="closeSettings"></button>
214
- </header>
215
- <section class="modal-card-body">
216
- <div class="field">
217
- <label class="label">Gemini API Key</label>
218
- <div class="control">
219
- <input class="input" type="text" v-model="geminiApiKey" placeholder="Enter your Gemini API key">
220
- </div>
221
- <p class="help">Your API key is stored locally in ~/.settings/plainbook and used for AI-powered features.</p>
222
- </div>
223
- </section>
224
- <footer class="modal-card-foot">
225
- <button class="button" @click="closeSettings">Close</button>
226
- </footer>
227
- </div>
228
- </div>
104
+ <cell-insertion-zone v-if="!isLocked"
105
+ @insert="(celltype) => insertCell(index + 1, celltype)"
106
+ />
229
107
 
230
- <!-- Info Modal -->
231
- <div class="modal" :class="{'is-active': showInfo}">
232
- <div class="modal-background" @click="closeInfo"></div>
233
- <div class="modal-card">
234
- <header class="modal-card-head">
235
- <p class="modal-card-title">Information</p>
236
- <button class="delete" aria-label="close" @click="closeInfo"></button>
237
- </header>
238
- <section class="modal-card-body">
239
- <h1 class="title">Plainbook</h1>
240
- <div class="content">
241
- <p>Plainbook is an interactive notebook application for creating executable doocuments in
242
- natural language. A notebook consists of:</p>
243
- <ul>
244
- <li><strong>Action cells</strong> that contain an English explanation. The explanation is
245
- used to generate Python code using an AI model. The generated code is then executed,
246
- and the output is displayed below the cell.</li>
247
- <li><strong>Markdown cells</strong> for writing explanations, comments, and documentation in rich text format.</li>
248
- </ul>
249
- <p>If you lock a notebook, editing of cells and generation of code will be disabled
250
- to prevent accidental changes. However, you can still validate the code against
251
- its description, and you can still run the notebook.
252
- Unlock it to be able to edit again.</p>
253
- <p><a href="https://github.com/lucadealfaro/plainbook" target="_blank">Plainbook Home Page</a></p>
108
+ </template>
109
+
254
110
  </div>
255
- </section>
256
- <footer class="modal-card-foot">
257
- <button class="button" @click="closeInfo">Close</button>
258
- </footer>
111
+ </div>
259
112
  </div>
260
- </div>
113
+
114
+ <!-- Settings Modal -->
115
+ <settings-modal
116
+ :is-active="showSettings"
117
+ :api-key="geminiApiKey"
118
+ @close="showSettings = false"
119
+ @save="(newKey) => {showSettings = false; saveSettings(newKey); }"
120
+ />
121
+ <!-- Info Modal -->
122
+ <info-modal
123
+ :is-active="showInfo"
124
+ @close="showInfo = false"
125
+ />
261
126
 
262
127
  </script>
263
128
 
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plainbook
3
- Version: 0.0.6
3
+ Version: 0.0.8
4
4
  Summary: Plain Language Notebooks
5
5
  Author-email: Luca de Alfaro <dealfaro@acm.org>
6
- License: BSD-3-Clause
6
+ License-Expression: BSD-3-Clause
7
7
  Project-URL: Homepage, https://github.com/lucadealfaro/plainbook
8
8
  Project-URL: Issues, https://github.com/lucadealfaro/plainbook/issues
9
9
  Keywords: notebook,jupyter,natural language,data science
@@ -1,6 +1,6 @@
1
1
  plainbook/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
2
2
  plainbook/gemini.py,sha256=4lgvMdQdcKyMZLSnu9yhwlaI4LaSvvyr_-SA0DdI628,2915
3
- plainbook/main.py,sha256=YL5pi6a7sacKpzM2ExnLbQLds6g58UEtfRbEHlbnTV4,10151
3
+ plainbook/main.py,sha256=XruVZYj-nQ-2V9wiGKO7AV0-Via0Rz2CUafuX1id3J8,10172
4
4
  plainbook/plainbook.py,sha256=zgi3e50r9vFfMLxXuMVcvyB9I7yEJOgZthxnz3RxuKw,17063
5
5
  plainbook/css/font-awesome.css,sha256=NuCn4IvuZXdBaFKJOAcsU2Q3ZpwbdFisd5dux4jkQ5w,37414
6
6
  plainbook/css/font-awesome.min.css,sha256=eZrrJcwDc_3uDhsdt61sL2oOBY362qM3lon1gyExkL0,31000
@@ -12,20 +12,25 @@ plainbook/fonts/fontawesome-webfont.svg,sha256=rWFXkmwWIrpOHQPUePFUE2hSS_xG9R5C_
12
12
  plainbook/fonts/fontawesome-webfont.ttf,sha256=qljzPyOaD7AvXHpsRcBD16msmgkzNYBmlOzW1O3A1qg,165548
13
13
  plainbook/fonts/fontawesome-webfont.woff,sha256=ugxZ3rVFD1y0Gz-TYJ7i0NmVQVh33foiPoqKdTNHTwc,98024
14
14
  plainbook/fonts/fontawesome-webfont.woff2,sha256=Kt78vAQefRj88tQXh53FoJmXqmTWdbejxLbOM9oT8_4,77160
15
- plainbook/js/CodeCell.js,sha256=wleWbT_kwX0WiJUBWfND81MOwZsRLxytwaHw7FQ3Lx0,6915
16
- plainbook/js/ExplanationEditor.js,sha256=g9An3cNJyu1Z8cuXS9Vk2vmBL7d2uc434taAjrd1iqM,7510
17
- plainbook/js/MarkdownCell.js,sha256=NGDAGpb0FkTZ-wh-izYKlQ7ZkcdfQAX4GBc_x45ormQ,5301
15
+ plainbook/js/AppNavbar.js,sha256=nEX2CXdI0136Usj-YVts6QXV4xd_vbsXh9-mPvgbi5E,3968
16
+ plainbook/js/CellInsertionZone.js,sha256=XPo2hmuG9lfVzYbZ1bBO-44HiyCFngHzxoUa56v42s8,676
17
+ plainbook/js/CodeCell.js,sha256=cK6YOKVu58g4h7KmH2aL3LA99oxe8Z3ZZpLRF0k6XZY,6943
18
+ plainbook/js/ExplanationEditor.js,sha256=FDflmKZKzNHkKgXHwsYrbQDLDK1ROVg00-5p9cj2jlc,7088
19
+ plainbook/js/InfoModal.js,sha256=ShylUmDQdQ-2g-z6PlvZ_TTlxCSdNGJJICu-RAAwab8,1442
20
+ plainbook/js/MarkdownCell.js,sha256=SxU_LO4TGcxOEy6sKognF_gHUXwmqR6BDsv4z0tlgVQ,5292
21
+ plainbook/js/NotebookCell.js,sha256=PNWdd6VSWtD1nHeUCHPEZWIwBMQT26rjN6-P0rPCEZI,3239
18
22
  plainbook/js/OutputRenderer.js,sha256=KXECI2xd0Sk_TnZnAfqfhL0dEIT9pjZZTXo6Qdiw0o4,2981
19
- plainbook/js/ValidationCell.js,sha256=iVwISyGa1owLNakit-M4PXapNkC1C2rsLXEJLK_Cdhs,1617
23
+ plainbook/js/SettingsModal.js,sha256=HpU2gKj26zZHhilcfp3hghEsW-sxsgq1HBlcsIBDGCI,1689
24
+ plainbook/js/ValidationCell.js,sha256=4XdlBNELTpeARiNVh55_7ay24CEY-aUNJy1knX8I9MU,1595
20
25
  plainbook/js/markdown-it.min.js,sha256=hNyljag6giCsjv_yKmxK8_VeHzvMDvc5u8AzmRvm1BI,103012
21
- plainbook/js/nb.js,sha256=J5IuoMUq9f7ber4ijgz7Zq0K8EkVXWj5xb1DRyoOAkg,22584
26
+ plainbook/js/nb.js,sha256=CZs32sAevGsea7PUjJK0Prd-7_mz1G82_S9_Hpx5gmQ,22822
22
27
  plainbook/js/prism-python.min.js,sha256=7UOFaFvPLUk1yNu6tL3hZgPaEyngktK_NsPa3WfpqFw,2113
23
28
  plainbook/js/prism.min.js,sha256=57iL3cbHV7L8jLET4kaYAasUp47BqPraTWOR41c_X58,18997
24
29
  plainbook/js/vue.esm-browser.js,sha256=75FuLhUTPk19sncwNIrm0BGEL0_Qw298-_v01fPWYoI,542872
25
- plainbook/views/index.html,sha256=9ZQLPcPOHDth33mppTjsvyL3J9UpEK-8yAF9p73duV0,15220
26
- plainbook-0.0.6.dist-info/licenses/LICENSE.md,sha256=JW6Hp9mBeCkRkCSeiT2KmIU3XA5mJorjcIlM2cOOxyU,1462
27
- plainbook-0.0.6.dist-info/METADATA,sha256=6kV5bzLNaJDN3LMuGCIE2a7u2SGS847oFMGqcF-_3sk,3012
28
- plainbook-0.0.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
- plainbook-0.0.6.dist-info/entry_points.txt,sha256=fktud-zRh9ZZ4rXv5w6kW-78u44lSYklXY1ttMTB9k8,50
30
- plainbook-0.0.6.dist-info/top_level.txt,sha256=Y3jOV2n79dczNw9tJ7acufz3y3ns2pWw8b06sIN4ltc,10
31
- plainbook-0.0.6.dist-info/RECORD,,
30
+ plainbook/views/index.html,sha256=2LEoKQMoAuCwDSTg8kpstvINJUL33QTcfLkEspS1wYI,5557
31
+ plainbook-0.0.8.dist-info/licenses/LICENSE.md,sha256=JW6Hp9mBeCkRkCSeiT2KmIU3XA5mJorjcIlM2cOOxyU,1462
32
+ plainbook-0.0.8.dist-info/METADATA,sha256=oqeZaTNU720_qAR6HqaiCu6Kvmpd7pj8ofV1hxkvOvo,3023
33
+ plainbook-0.0.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
+ plainbook-0.0.8.dist-info/entry_points.txt,sha256=fktud-zRh9ZZ4rXv5w6kW-78u44lSYklXY1ttMTB9k8,50
35
+ plainbook-0.0.8.dist-info/top_level.txt,sha256=Y3jOV2n79dczNw9tJ7acufz3y3ns2pWw8b06sIN4ltc,10
36
+ plainbook-0.0.8.dist-info/RECORD,,