zyn-ai 1.3.2 → 1.3.3

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/src/tui/app.mjs CHANGED
@@ -71,6 +71,8 @@ class UIStore extends EventEmitter {
71
71
  this.spinner = null;
72
72
  this.processing = false;
73
73
  this.confirmRequest = null;
74
+ this.lastUserMessage = '';
75
+ this.inputDraft = '';
74
76
  this.turnCount = 0;
75
77
  this.messageQueue = [];
76
78
  this.pendingExit = false;
@@ -129,6 +131,13 @@ class UIStore extends EventEmitter {
129
131
  this.addItem({ type: 'event', kind, title, detail: detail || '' });
130
132
  }
131
133
 
134
+ setInputDraft(text) {
135
+ const nextDraft = String(text || '');
136
+ if (nextDraft === this.inputDraft) return;
137
+ this.inputDraft = nextDraft;
138
+ this._emit();
139
+ }
140
+
132
141
  requestConfirm(title, detail) {
133
142
  return new Promise(resolve => {
134
143
  this.confirmRequest = { title, detail, resolve };
@@ -163,6 +172,11 @@ function stripAnsi(str) {
163
172
  return str.replace(/\x1b\[[0-9;]*m/g, '');
164
173
  }
165
174
 
175
+ function shortTextPreview(str, maxLen) {
176
+ const s = String(str || '').replace(/\n/g, ' ').trim();
177
+ return s.length > maxLen ? s.slice(0, maxLen - 3) + '...' : s;
178
+ }
179
+
166
180
  function formatElapsed(ms) {
167
181
  const s = Math.floor(ms / 1000);
168
182
  if (s < 60) return s + 's';
@@ -527,7 +541,7 @@ function QueuedMessage({ text }) {
527
541
  );
528
542
  }
529
543
 
530
- function ConfirmBar({ title, detail }) {
544
+ function ConfirmBar({ title, detail, lastMessage, draft }) {
531
545
  const detailLines = (detail || '').split('\n').filter(l => l.trim()).slice(0, 10);
532
546
 
533
547
  return h(Box, { flexDirection: 'column', paddingLeft: 3, marginTop: 1 },
@@ -549,6 +563,18 @@ function ConfirmBar({ title, detail }) {
549
563
  ),
550
564
  )
551
565
  : null,
566
+ lastMessage
567
+ ? h(Box, { marginTop: 0, paddingLeft: 2 },
568
+ h(Text, { color: T.textGhost }, uiText('Your message: ', 'Tu mensaje: ')),
569
+ h(Text, { color: T.textDim, wrap: 'wrap' }, shortTextPreview(lastMessage, 80)),
570
+ )
571
+ : null,
572
+ draft
573
+ ? h(Box, { marginTop: 0, paddingLeft: 2 },
574
+ h(Text, { color: T.textGhost }, uiText('Saved draft: ', 'Borrador guardado: ')),
575
+ h(Text, { color: T.textDim, wrap: 'wrap' }, shortTextPreview(draft, 80)),
576
+ )
577
+ : null,
552
578
  h(Box, { marginTop: 0, paddingLeft: 2, gap: 2 },
553
579
  h(Text, { color: T.green, bold: true }, '[y]'),
554
580
  h(Text, { color: T.textMuted }, uiText('allow', 'permitir')),
@@ -609,15 +635,29 @@ function StaticItem({ item, width }) {
609
635
  }
610
636
  }
611
637
 
612
- function InputBar({ onSubmit, processing, width = 100 }) {
613
- const [value, setValue] = useState('');
614
- const [cursor, setCursor] = useState(0);
638
+ function InputBar({ onSubmit, processing, width = 100, draft = '', onDraftChange }) {
639
+ const [value, setValue] = useState(draft || '');
640
+ const [cursor, setCursor] = useState((draft || '').length);
615
641
  const [histIdx, setHistIdx] = useState(-1);
616
642
  const [suggestIdx, setSuggestIdx] = useState(0);
617
643
  const historyRef = useRef([]);
618
644
  const savedRef = useRef('');
619
645
  const lastPasteMetaRef = useRef(null);
620
646
 
647
+ useEffect(() => {
648
+ if (draft !== value) {
649
+ setValue(draft || '');
650
+ setCursor((draft || '').length);
651
+ setHistIdx(-1);
652
+ setSuggestIdx(0);
653
+ }
654
+ }, [draft]);
655
+
656
+ const updateValue = useCallback((next) => {
657
+ setValue(next);
658
+ if (onDraftChange) onDraftChange(next);
659
+ }, [onDraftChange]);
660
+
621
661
  const showSuggestions = value.startsWith('/') && !value.includes(' ') && value.length > 0;
622
662
  const suggestions = showSuggestions
623
663
  ? SLASH_COMMANDS.filter(c => ('/' + c.name).startsWith(value.toLowerCase()))
@@ -634,7 +674,7 @@ function InputBar({ onSubmit, processing, width = 100 }) {
634
674
  }
635
675
  historyRef.current.unshift(text);
636
676
  if (historyRef.current.length > 100) historyRef.current.pop();
637
- setValue('');
677
+ updateValue('');
638
678
  setCursor(0);
639
679
  setHistIdx(-1);
640
680
  setSuggestIdx(0);
@@ -647,7 +687,7 @@ function InputBar({ onSubmit, processing, width = 100 }) {
647
687
  const cmd = suggestions[suggestIdx] || suggestions[0];
648
688
  if (cmd) {
649
689
  const completed = `/${cmd.name} `;
650
- setValue(completed);
690
+ updateValue(completed);
651
691
  setCursor(completed.length);
652
692
  setSuggestIdx(0);
653
693
  }
@@ -671,7 +711,7 @@ function InputBar({ onSubmit, processing, width = 100 }) {
671
711
  if (histIdx === -1) savedRef.current = value;
672
712
  const next = Math.min(histIdx + 1, hist.length - 1);
673
713
  setHistIdx(next);
674
- setValue(hist[next]);
714
+ updateValue(hist[next]);
675
715
  setCursor(hist[next].length);
676
716
  return;
677
717
  }
@@ -679,13 +719,13 @@ function InputBar({ onSubmit, processing, width = 100 }) {
679
719
  if (key.downArrow) {
680
720
  if (histIdx <= 0) {
681
721
  setHistIdx(-1);
682
- setValue(savedRef.current);
722
+ updateValue(savedRef.current);
683
723
  setCursor(savedRef.current.length);
684
724
  return;
685
725
  }
686
726
  const next = histIdx - 1;
687
727
  setHistIdx(next);
688
- setValue(historyRef.current[next]);
728
+ updateValue(historyRef.current[next]);
689
729
  setCursor(historyRef.current[next].length);
690
730
  return;
691
731
  }
@@ -705,7 +745,7 @@ function InputBar({ onSubmit, processing, width = 100 }) {
705
745
 
706
746
  if (key.ctrl && input === 'u') {
707
747
  const after = value.slice(cursor);
708
- setValue(after);
748
+ updateValue(after);
709
749
  setCursor(0);
710
750
  return;
711
751
  }
@@ -714,14 +754,14 @@ function InputBar({ onSubmit, processing, width = 100 }) {
714
754
  const before = value.slice(0, cursor);
715
755
  const after = value.slice(cursor);
716
756
  const trimmed = before.replace(/\S+\s*$/, '');
717
- setValue(trimmed + after);
757
+ updateValue(trimmed + after);
718
758
  setCursor(trimmed.length);
719
759
  return;
720
760
  }
721
761
 
722
762
  if (key.backspace || key.delete) {
723
763
  if (cursor === 0) return;
724
- setValue(v => v.slice(0, cursor - 1) + v.slice(cursor));
764
+ updateValue(value.slice(0, cursor - 1) + value.slice(cursor));
725
765
  setCursor(c => Math.max(0, c - 1));
726
766
  setSuggestIdx(0);
727
767
  return;
@@ -730,7 +770,7 @@ function InputBar({ onSubmit, processing, width = 100 }) {
730
770
  if (input && !key.ctrl && !key.meta) {
731
771
  const normalizedInput = input.replace(/\r\n/g, '\n');
732
772
  const safeInput = normalizedInput.includes('\n') ? normalizedInput.replace(/\n/g, ' ') : normalizedInput;
733
- setValue(v => v.slice(0, cursor) + safeInput + v.slice(cursor));
773
+ updateValue(value.slice(0, cursor) + safeInput + value.slice(cursor));
734
774
  setCursor(c => c + safeInput.length);
735
775
  setSuggestIdx(0);
736
776
  }
@@ -790,7 +830,7 @@ function InputBar({ onSubmit, processing, width = 100 }) {
790
830
  h(Text, {
791
831
  color: selected ? T.accent : T.textMuted,
792
832
  }, `/${cmd.name}`),
793
- h(Text, { color: T.textGhost }, ` ${cmd.desc}`),
833
+ h(Text, { color: T.textGhost }, ` ${getTuiLang() === 'es' ? (cmd.descEs || cmd.desc) : cmd.desc}`),
794
834
  );
795
835
  }),
796
836
  hasMore && windowStart + maxVisible < suggestions.length
@@ -877,13 +917,13 @@ function App({ store, state, onSubmit }) {
877
917
 
878
918
  if (showConfirm) {
879
919
  dynamicArea.push(
880
- h(ConfirmBar, { key: 'confirm', title: store.confirmRequest.title, detail: store.confirmRequest.detail })
920
+ h(ConfirmBar, { key: 'confirm', title: store.confirmRequest.title, detail: store.confirmRequest.detail, lastMessage: store.lastUserMessage, draft: store.inputDraft })
881
921
  );
882
922
  }
883
923
 
884
924
  if (showInput) {
885
925
  dynamicArea.push(
886
- h(InputBar, { key: 'input', onSubmit: handleInput, processing: store.processing, width })
926
+ h(InputBar, { key: 'input', onSubmit: handleInput, processing: store.processing, width, draft: store.inputDraft, onDraftChange: (text) => store.setInputDraft(text) })
887
927
  );
888
928
  }
889
929
 
@@ -954,6 +994,7 @@ export async function startTUI(options = {}) {
954
994
  }
955
995
 
956
996
  if (input.startsWith('/')) {
997
+ const commandName = input.split(' ')[0].slice(1).toLowerCase();
957
998
  const lines = [];
958
999
  const origLog = console.log;
959
1000
  const origError = console.error;
@@ -972,12 +1013,21 @@ export async function startTUI(options = {}) {
972
1013
  printSessions: printMod.printSessions,
973
1014
  printStatus: printMod.printStatus,
974
1015
  };
975
- const handled = await handleLocalCommand(input, state, deps);
976
- if (handled && lines.length > 0) {
977
- const clean = lines.filter(l => l.trim()).join('\n');
978
- if (clean) store.addItem({ type: 'system', text: clean });
1016
+
1017
+ if (commandName === 'persona') {
1018
+ const handled = await handleLocalCommand(input, state, deps);
1019
+ if (handled && lines.length > 0) {
1020
+ const clean = lines.filter(l => l.trim()).join('\n');
1021
+ if (clean) store.addItem({ type: 'system', text: clean });
1022
+ }
1023
+ } else {
1024
+ const handled = await handleLocalCommand(input, state, deps);
1025
+ if (handled && lines.length > 0) {
1026
+ const clean = lines.filter(l => l.trim()).join('\n');
1027
+ if (clean) store.addItem({ type: 'system', text: clean });
1028
+ }
1029
+ if (!handled) store.addEvent('warn', uiText('command not recognized', 'comando no reconocido'), input);
979
1030
  }
980
- if (!handled) store.addEvent('warn', uiText('command not recognized', 'comando no reconocido'), input);
981
1031
  } catch (err) {
982
1032
  store.addEvent('error', uiText('error', 'error'), err.message);
983
1033
  } finally {
@@ -989,6 +1039,7 @@ export async function startTUI(options = {}) {
989
1039
 
990
1040
  store.addItem({ type: 'divider' });
991
1041
  store.addItem({ type: 'user', text: input });
1042
+ store.lastUserMessage = input;
992
1043
 
993
1044
  const origError = console.error;
994
1045
  console.error = () => {};
package/src/web/server.js CHANGED
@@ -11,7 +11,8 @@ const { runWebAgent } = require('./webAgent');
11
11
  const { MODELS, DEFAULT_MODEL_KEY, listProvidersFromModels, DEFAULT_LANGUAGE } = require('../config');
12
12
 
13
13
  const app = express();
14
- const PORT = process.env.PORT || 3000;
14
+ const HOST = process.env.HOST || process.env.ZYN_WEB_HOST || '127.0.0.1';
15
+ const PORT = Number(process.env.PORT || process.env.ZYN_WEB_PORT || 3000);
15
16
 
16
17
  // Evitar crashes silenciosos
17
18
  process.on('uncaughtException', (err) => {
@@ -31,7 +32,7 @@ app.use((req, res, next) => {
31
32
  next();
32
33
  });
33
34
  app.use(express.static(path.join(__dirname, 'public')));
34
- // Persistir secreto de sesión en disco
35
+ // Persistir secreto de sesion en disco
35
36
  const SECRET_FILE = path.join(__dirname, 'data', '.session-secret');
36
37
  let sessionSecret;
37
38
  try {
@@ -42,9 +43,13 @@ try {
42
43
  fs.writeFileSync(SECRET_FILE, sessionSecret);
43
44
  }
44
45
 
46
+ // Asegurar que el directorio de sesiones existe
47
+ const SESSION_DIR = path.join(__dirname, 'data', 'sessions');
48
+ fs.mkdirSync(SESSION_DIR, { recursive: true });
49
+
45
50
  app.use(session({
46
51
  store: new FileStore({
47
- path: path.join(__dirname, 'data', 'sessions'),
52
+ path: SESSION_DIR,
48
53
  ttl: 30 * 24 * 60 * 60,
49
54
  retries: 0,
50
55
  logFn: () => {},
@@ -294,6 +299,6 @@ app.get('/{*splat}', (req, res) => {
294
299
  res.sendFile(path.join(__dirname, 'public', 'index.html'));
295
300
  });
296
301
 
297
- app.listen(PORT, () => {
298
- console.log(`\n Zyn Web http://localhost:${PORT}\n`);
302
+ app.listen(PORT, HOST, () => {
303
+ console.log(`\n \u25cf Zyn Web \u2192 http://${HOST}:${PORT}\n`);
299
304
  });