plainbook 0.0.7__py3-none-any.whl → 0.0.9__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);
@@ -395,6 +395,8 @@ createApp({
395
395
  const r = await response.json();
396
396
  if (r.status === 'error') {
397
397
  throw new Error(r.message || 'Execution failed');
398
+ } else if (r.details !== 'ok') {
399
+ throw new Error('Cell execution error');
398
400
  } else {
399
401
  console.log('Cell executed:', cellIndex, r.details);
400
402
  // Update outputs in the notebook model
@@ -405,9 +407,11 @@ createApp({
405
407
  if (r.last_executed_cell !== undefined && r.last_executed_cell !== null) {
406
408
  lastRunIndex.value = r.last_executed_cell;
407
409
  }
410
+ //
408
411
  }
409
412
  } catch (err) {
410
- throw new Error('Run error: ' + err.message);
413
+ running.value = false; // No longer running.
414
+ throw new Error(err.message);
411
415
  }
412
416
  };
413
417
 
@@ -461,13 +465,7 @@ createApp({
461
465
  }
462
466
  };
463
467
 
464
- const openSettings = () => {
465
- // Get the Gemini API key from the server settings.
466
-
467
- showSettings.value = true;
468
- };
469
-
470
- const closeSettings = async () => {
468
+ const saveSettings = async (newKey) => {
471
469
  // Save the Gemini API key to the server
472
470
  try {
473
471
  const response = await fetch(`/set_key?token=${authToken}`, {
@@ -483,21 +481,13 @@ createApp({
483
481
  } catch (err) {
484
482
  throw new Error('Error saving API key: ' + err.message);
485
483
  }
486
- showSettings.value = false;
484
+ geminiApiKey.value = newKey;
487
485
  };
488
486
 
489
487
  const genError = () => {
490
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. ');
491
489
  }
492
490
 
493
- const openInfo = () => {
494
- showInfo.value = true;
495
- };
496
-
497
- const closeInfo = () => {
498
- showInfo.value = false;
499
- };
500
-
501
491
  const closeUiError = () => {
502
492
  uiError.value = null;
503
493
  };
@@ -528,8 +518,7 @@ createApp({
528
518
  validateCode, dismissValidation, resetAndRunAllCells,
529
519
  setActiveCell, runCell, running, lastRunIndex, asRead, runAllCells,
530
520
  interruptKernel, insertCell, markdownEditKey,
531
- openSettings, closeSettings, showSettings,
532
- openInfo, closeInfo, showInfo,
521
+ saveSettings, showSettings, showInfo,
533
522
  genError, uiError, closeUiError,
534
523
  explanationEditKey, deleteCell, moveCell, geminiApiKey };
535
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",
@@ -33,81 +33,22 @@
33
33
  <div id="app"></div>
34
34
 
35
35
  <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" :class="geminiApiKey ? 'is-light' : 'is-warning'" @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>
36
+
37
+ <app-navbar
38
+ :is-locked="isLocked"
39
+ :running="running"
40
+ :has-notebook="!!notebook"
41
+ :last-run-index="lastRunIndex"
42
+ :cell-count="notebook ? notebook.cells.length : 0"
43
+ :has-api-key="!!geminiApiKey"
44
+ @lock="lockNotebook"
45
+ @refresh="reloadNotebook"
46
+ @interrupt="interruptKernel"
47
+ @regenerate-all="regenerateAllCode"
48
+ @reset-run-all="resetAndRunAllCells"
49
+ @open-info="showInfo = true"
50
+ @open-settings="showSettings = true"
51
+ />
111
52
 
112
53
  <div v-if="uiError" class="notification mb-0 mt-0 px-4 pl-2 pr-6 has-text-danger has-background-danger-light"
113
54
  style="width: 100%; border-radius: 0; align-items: center; justify-content: space-between;">
@@ -129,135 +70,61 @@
129
70
  <strong>Error:</strong> {{ error }}
130
71
  </div>
131
72
  <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>
135
- </div>
73
+
74
+ <cell-insertion-zone v-if="!isLocked"
75
+ @insert="(celltype) => insertCell(0, celltype)"
76
+ />
77
+
136
78
  <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>
144
79
 
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)" />
80
+ <notebook-cell
81
+ :cell="cell"
82
+ :is-active="activeIndex === index"
83
+ :needs-running="index > lastRunIndex"
84
+ :is-locked="isLocked"
85
+ :last-run-index="lastRunIndex"
86
+ :as-read="asRead"
87
+ :markdown-edit-key="markdownEditKey[index]"
88
+ :explanation-edit-key="explanationEditKey[index]"
89
+ @click="setActiveCell(index)"
90
+
91
+ @save-markdown="(content) => sendMarkdownToServer(content, index)"
92
+ @save-explanation="(content) => sendExplanationToServer(content, index)"
93
+ @save-code="(content) => sendCodeToServer(content, index)"
94
+ @save-and-run="(content) => saveExplanationAndRun(content, index)"
95
+
96
+ @run-cell="runCell(index)"
97
+ @generate-code="generateCode(index)"
98
+ @validate-code="validateCode(index)"
99
+ @dismiss-validation="dismissValidation(index)"
100
+
101
+ @delete="deleteCell(index)"
102
+ @move-up="moveCell(index, -1)"
103
+ @move-down="moveCell(index, 1)"
104
+ />
105
+
106
+ <cell-insertion-zone v-if="!isLocked"
107
+ @insert="(celltype) => insertCell(index + 1, celltype)"
108
+ />
160
109
 
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)" />
188
-
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
110
  </template>
195
111
 
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
112
  </div>
204
113
  </div>
205
114
  </div>
206
115
 
207
116
  <!-- 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>
229
-
117
+ <settings-modal
118
+ :is-active="showSettings"
119
+ :api-key="geminiApiKey"
120
+ @close="showSettings = false"
121
+ @save="(newKey) => {showSettings = false; saveSettings(newKey); }"
122
+ />
230
123
  <!-- 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>
254
- </div>
255
- </section>
256
- <footer class="modal-card-foot">
257
- <button class="button" @click="closeInfo">Close</button>
258
- </footer>
259
- </div>
260
- </div>
124
+ <info-modal
125
+ :is-active="showInfo"
126
+ @close="showInfo = false"
127
+ />
261
128
 
262
129
  </script>
263
130
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plainbook
3
- Version: 0.0.7
3
+ Version: 0.0.9
4
4
  Summary: Plain Language Notebooks
5
5
  Author-email: Luca de Alfaro <dealfaro@acm.org>
6
6
  License-Expression: BSD-3-Clause
@@ -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=DADOQR7vjoUTsoCJ4KbqdYiRMyBqQ4L6ZEK1MKj4oSk,23028
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=nG-hAHKlmy8W5eCcnHqktjvvaqlCHjk3F2O7ZhXV6Ig,15261
26
- plainbook-0.0.7.dist-info/licenses/LICENSE.md,sha256=JW6Hp9mBeCkRkCSeiT2KmIU3XA5mJorjcIlM2cOOxyU,1462
27
- plainbook-0.0.7.dist-info/METADATA,sha256=k6OFCYgwDiPSSCa0XBxzeUe2ewLnCHluQXadAsdnkU0,3023
28
- plainbook-0.0.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
29
- plainbook-0.0.7.dist-info/entry_points.txt,sha256=fktud-zRh9ZZ4rXv5w6kW-78u44lSYklXY1ttMTB9k8,50
30
- plainbook-0.0.7.dist-info/top_level.txt,sha256=Y3jOV2n79dczNw9tJ7acufz3y3ns2pWw8b06sIN4ltc,10
31
- plainbook-0.0.7.dist-info/RECORD,,
30
+ plainbook/views/index.html,sha256=Y_iA3eMd7X7BSkDgyPR3MghxvzOFnOZ8mmDizRb1iIM,5251
31
+ plainbook-0.0.9.dist-info/licenses/LICENSE.md,sha256=JW6Hp9mBeCkRkCSeiT2KmIU3XA5mJorjcIlM2cOOxyU,1462
32
+ plainbook-0.0.9.dist-info/METADATA,sha256=RbHMpf1Y4mJ6U4DKR6D0NknrfDUFrrtNnn-gEKAR8dY,3023
33
+ plainbook-0.0.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
34
+ plainbook-0.0.9.dist-info/entry_points.txt,sha256=fktud-zRh9ZZ4rXv5w6kW-78u44lSYklXY1ttMTB9k8,50
35
+ plainbook-0.0.9.dist-info/top_level.txt,sha256=Y3jOV2n79dczNw9tJ7acufz3y3ns2pWw8b06sIN4ltc,10
36
+ plainbook-0.0.9.dist-info/RECORD,,