smart-home-engine 0.21.0 → 0.22.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.
@@ -1,4 +1,4 @@
1
- import{m as O}from"./monaco-langs-BW2J83t5.js";import{t as I}from"./index-Ba5-h6tq.js";/*!-----------------------------------------------------------------------------
1
+ import{m as O}from"./monaco-langs-BW2J83t5.js";import{t as I}from"./index-BZSPXz4N.js";/*!-----------------------------------------------------------------------------
2
2
  * Copyright (c) Microsoft Corporation. All rights reserved.
3
3
  * Version: 0.52.2(404545bded1df6ffa41ea0af4e8ddb219018c6c1)
4
4
  * Released under the MIT license
@@ -155,10 +155,10 @@
155
155
  }
156
156
  })();
157
157
  </script>
158
- <script type="module" crossorigin src="/assets/index-Ba5-h6tq.js"></script>
158
+ <script type="module" crossorigin src="/assets/index-BZSPXz4N.js"></script>
159
159
  <link rel="modulepreload" crossorigin href="/assets/monaco-langs-BW2J83t5.js">
160
160
  <link rel="stylesheet" crossorigin href="/assets/monaco-langs-DyX1CsEw.css">
161
- <link rel="stylesheet" crossorigin href="/assets/index-c8FUuDqy.css">
161
+ <link rel="stylesheet" crossorigin href="/assets/index-BPk8Jr3B.css">
162
162
  </head>
163
163
  <body>
164
164
  <div id="app"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "smart-home-engine",
3
- "version": "0.21.0",
3
+ "version": "0.22.0",
4
4
  "description": "Node.js based script runner for use in MQTT based Smart Home environments",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -26,7 +26,7 @@
26
26
  "service/"
27
27
  ],
28
28
  "author": "Sebastian 'hobbyquaker' Raff <hobbyquaker@gmail.com>",
29
- "license": "MIT",
29
+ "license": "GPL-3.0-or-later",
30
30
  "dependencies": {
31
31
  "@elastic/elasticsearch": "^9.4.2",
32
32
  "@influxdata/influxdb-client": "^1.35.0",
package/src/web/ai-api.js CHANGED
@@ -18,9 +18,11 @@
18
18
 
19
19
  const express = require('express');
20
20
  const fs = require('fs');
21
+ const path = require('path');
21
22
 
22
23
  const { buildSystemPrompt } = require('./ai-context');
23
24
  const { TOOL_DEFINITIONS, TOOL_DEFINITIONS_ANTHROPIC, executeTool } = require('./ai-tools');
25
+ const { STORAGE_ROOT } = require('../lib/storage');
24
26
 
25
27
  const router = express.Router();
26
28
  let _store = null;
@@ -521,4 +523,69 @@ router.post('/chat/stream', async (req, res) => {
521
523
  }
522
524
  });
523
525
 
526
+ // ---------------------------------------------------------------------------
527
+ // Conversation persistence — GET/PUT/DELETE /she/ai/conversations[/:id]
528
+ // ---------------------------------------------------------------------------
529
+
530
+ const AI_DIR = path.join(STORAGE_ROOT, 'ai');
531
+
532
+ function ensureAiDir() {
533
+ fs.mkdirSync(AI_DIR, { recursive: true });
534
+ }
535
+
536
+ function convPath(id) {
537
+ // Sanitize: only allow alphanumerics, hyphens, underscores
538
+ if (!/^[a-z0-9_-]{1,64}$/i.test(id)) return null;
539
+ return path.join(AI_DIR, `${id}.json`);
540
+ }
541
+
542
+ // GET /she/ai/conversations — list conversations sorted by updatedAt desc
543
+ router.get('/conversations', (req, res) => {
544
+ ensureAiDir();
545
+ let list = [];
546
+ try {
547
+ const files = fs.readdirSync(AI_DIR).filter(f => f.endsWith('.json'));
548
+ list = files.map(f => {
549
+ try {
550
+ const data = JSON.parse(fs.readFileSync(path.join(AI_DIR, f), 'utf8'));
551
+ return { id: data.id, title: data.title || data.id, updatedAt: data.updatedAt || 0 };
552
+ } catch { return null; }
553
+ }).filter(Boolean);
554
+ list.sort((a, b) => b.updatedAt - a.updatedAt);
555
+ } catch { /* empty dir */ }
556
+ res.json(list);
557
+ });
558
+
559
+ // GET /she/ai/conversations/:id
560
+ router.get('/conversations/:id', (req, res) => {
561
+ const p = convPath(req.params.id);
562
+ if (!p) return res.status(400).json({ error: 'invalid id' });
563
+ try {
564
+ const data = JSON.parse(fs.readFileSync(p, 'utf8'));
565
+ res.json(data);
566
+ } catch {
567
+ res.status(404).json({ error: 'not found' });
568
+ }
569
+ });
570
+
571
+ // PUT /she/ai/conversations/:id — { title, messages }
572
+ router.put('/conversations/:id', (req, res) => {
573
+ const p = convPath(req.params.id);
574
+ if (!p) return res.status(400).json({ error: 'invalid id' });
575
+ const { title, messages } = req.body || {};
576
+ if (!Array.isArray(messages)) return res.status(400).json({ error: 'messages must be an array' });
577
+ ensureAiDir();
578
+ const data = { id: req.params.id, title: String(title || req.params.id).slice(0, 200), updatedAt: Date.now(), messages };
579
+ fs.writeFileSync(p, JSON.stringify(data), 'utf8');
580
+ res.json({ ok: true });
581
+ });
582
+
583
+ // DELETE /she/ai/conversations/:id
584
+ router.delete('/conversations/:id', (req, res) => {
585
+ const p = convPath(req.params.id);
586
+ if (!p) return res.status(400).json({ error: 'invalid id' });
587
+ try { fs.unlinkSync(p); } catch { /* already gone */ }
588
+ res.json({ ok: true });
589
+ });
590
+
524
591
  module.exports = { router, init };
@@ -114,7 +114,7 @@ router.use('/views', (req, res) => {
114
114
  // PUT /she/db/views/<id> — create/update view
115
115
  if (method === 'PUT') {
116
116
  if (!id || isResult) return res.status(400).json({ error: 'id required' });
117
- const { filter, map, reduce, mqttpub, retain } = req.body || {};
117
+ const { filter, map, reduce, mqttpub, retain, description } = req.body || {};
118
118
  if (typeof map !== 'string' || !map.trim()) return res.status(400).json({ error: '"map" function string is required' });
119
119
  const payload = {
120
120
  filter: filter || undefined,
@@ -122,6 +122,7 @@ router.use('/views', (req, res) => {
122
122
  reduce: reduce || undefined,
123
123
  ...(mqttpub ? { mqttpub: true } : {}),
124
124
  ...(retain ? { retain: true } : {}),
125
+ ...(description ? { description: String(description).slice(0, 500) } : {}),
125
126
  };
126
127
  core.query(id, payload);
127
128
  return res.json({ ok: true });