@peers-app/peers-ui 0.11.2 → 0.12.1

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.
Files changed (41) hide show
  1. package/dist/components/chat-overlay.js +2 -1
  2. package/dist/components/inverse-lazy-list.d.ts +25 -0
  3. package/dist/components/inverse-lazy-list.js +103 -0
  4. package/dist/components/left-bar.d.ts +3 -1
  5. package/dist/components/left-bar.js +97 -9
  6. package/dist/components/main-content-container.js +3 -1
  7. package/dist/components/markdown-editor/markdown-plugin.d.ts +0 -2
  8. package/dist/components/markdown-editor/markdown-plugin.js +4 -32
  9. package/dist/components/tabs.d.ts +1 -0
  10. package/dist/components/tabs.js +6 -5
  11. package/dist/index.d.ts +3 -0
  12. package/dist/index.js +6 -0
  13. package/dist/screens/settings/voice-settings-agent.d.ts +2 -0
  14. package/dist/screens/settings/voice-settings-agent.js +76 -0
  15. package/dist/screens/settings/voice-settings-api-keys.d.ts +13 -0
  16. package/dist/screens/settings/voice-settings-api-keys.js +35 -0
  17. package/dist/screens/settings/voice-settings-output.d.ts +16 -0
  18. package/dist/screens/settings/voice-settings-output.js +45 -0
  19. package/dist/screens/settings/voice-settings-providers.d.ts +9 -0
  20. package/dist/screens/settings/voice-settings-providers.js +23 -0
  21. package/dist/screens/settings/voice-settings-types.d.ts +33 -0
  22. package/dist/screens/settings/voice-settings-types.js +55 -0
  23. package/dist/screens/settings/voice-settings-wake-word.d.ts +9 -0
  24. package/dist/screens/settings/voice-settings-wake-word.js +39 -0
  25. package/dist/screens/settings/voice-settings.d.ts +0 -19
  26. package/dist/screens/settings/voice-settings.js +28 -133
  27. package/package.json +8 -9
  28. package/src/components/chat-overlay.tsx +3 -1
  29. package/src/components/inverse-lazy-list.tsx +106 -0
  30. package/src/components/left-bar.tsx +77 -6
  31. package/src/components/main-content-container.tsx +4 -1
  32. package/src/components/markdown-editor/markdown-plugin.tsx +3 -33
  33. package/src/components/tabs.tsx +8 -5
  34. package/src/index.tsx +4 -3
  35. package/src/screens/settings/voice-settings-agent.tsx +72 -0
  36. package/src/screens/settings/voice-settings-api-keys.tsx +89 -0
  37. package/src/screens/settings/voice-settings-output.tsx +125 -0
  38. package/src/screens/settings/voice-settings-providers.tsx +45 -0
  39. package/src/screens/settings/voice-settings-types.ts +91 -0
  40. package/src/screens/settings/voice-settings-wake-word.tsx +78 -0
  41. package/src/screens/settings/voice-settings.tsx +57 -327
@@ -65,6 +65,7 @@ function getChatOverlayThreadPvar() {
65
65
  }
66
66
  return chatOverlayThreadPvar;
67
67
  }
68
+ const voiceHubActive = (0, peers_sdk_1.deviceVar)('voiceHub:active', { defaultValue: false });
68
69
  // Voice settings pvar (shared with voice-settings.tsx and voice-service.ts)
69
70
  const voiceSettingsPvar = (0, peers_sdk_1.userVar)('voiceSettings', { defaultValue: DEFAULT_VOICE_SETTINGS });
70
71
  const DEFAULT_POSITION = { x: 20, y: 20 };
@@ -190,7 +191,7 @@ const ChatOverlay = ({ position = 'bottom-right', }) => {
190
191
  setTranscription('');
191
192
  setError(null);
192
193
  }
193
- if (event.data.state === 'recording') {
194
+ if (event.data.state === 'recording' && !voiceHubActive()) {
194
195
  setIsExpanded(true);
195
196
  }
196
197
  }),
@@ -0,0 +1,25 @@
1
+ import React from 'react';
2
+ interface IInverseLazyListProps<T> {
3
+ /** Unique id for the scroll container — required to avoid id collisions */
4
+ scrollableId: string;
5
+ /** Called to fetch the next older batch. Receives the current newest-first items array. */
6
+ loadMore: (existingItems: T[]) => Promise<T[]>;
7
+ /** Render all loaded items. Receives the newest-first array. */
8
+ renderItems: (items: T[]) => React.ReactNode;
9
+ /** Optional ref that will be set to a function to prepend a single live item */
10
+ prependRef?: React.MutableRefObject<((item: T) => void) | null>;
11
+ /** Custom loading indicator */
12
+ loadingIndicator?: React.ReactNode;
13
+ /** Change this value to reset the list and reload from scratch */
14
+ resetTrigger?: any;
15
+ /** Style for the outer scroll container */
16
+ style?: React.CSSProperties;
17
+ }
18
+ /**
19
+ * Infinite scroll list for chat-style history (newest at bottom, load older on scroll up).
20
+ * Uses react-infinite-scroll-component with inverse=true and column-reverse layout.
21
+ *
22
+ * Items state is managed internally. Use prependRef to push live inserts from outside.
23
+ */
24
+ export declare function InverseLazyList<T>(props: IInverseLazyListProps<T>): React.JSX.Element;
25
+ export {};
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.InverseLazyList = InverseLazyList;
40
+ const react_1 = __importStar(require("react"));
41
+ const react_infinite_scroll_component_1 = __importDefault(require("react-infinite-scroll-component"));
42
+ const PRELOAD_THRESHOLD = 20;
43
+ /**
44
+ * Infinite scroll list for chat-style history (newest at bottom, load older on scroll up).
45
+ * Uses react-infinite-scroll-component with inverse=true and column-reverse layout.
46
+ *
47
+ * Items state is managed internally. Use prependRef to push live inserts from outside.
48
+ */
49
+ function InverseLazyList(props) {
50
+ const [items, setItems] = (0, react_1.useState)([]);
51
+ const [allLoaded, setAllLoaded] = (0, react_1.useState)(false);
52
+ const isLoadingRef = (0, react_1.useRef)(false);
53
+ const itemsRef = (0, react_1.useRef)([]);
54
+ // Keep ref in sync so loadMore always sees the latest items
55
+ itemsRef.current = items;
56
+ // Expose prepend function via ref so callers can push live updates
57
+ (0, react_1.useEffect)(() => {
58
+ if (props.prependRef) {
59
+ props.prependRef.current = (item) => setItems(prev => [item, ...prev]);
60
+ }
61
+ return () => {
62
+ if (props.prependRef)
63
+ props.prependRef.current = null;
64
+ };
65
+ }, [props.prependRef]);
66
+ async function loadMore() {
67
+ if (isLoadingRef.current || allLoaded)
68
+ return;
69
+ isLoadingRef.current = true;
70
+ try {
71
+ const moreItems = await props.loadMore(itemsRef.current);
72
+ if (!moreItems.length) {
73
+ setAllLoaded(true);
74
+ }
75
+ else {
76
+ setItems(prev => [...prev, ...moreItems]);
77
+ }
78
+ }
79
+ finally {
80
+ isLoadingRef.current = false;
81
+ }
82
+ }
83
+ // Auto-preload when items are sparse
84
+ (0, react_1.useEffect)(() => {
85
+ if (items.length < PRELOAD_THRESHOLD && !allLoaded && !isLoadingRef.current) {
86
+ loadMore();
87
+ }
88
+ }, [items.length, allLoaded]);
89
+ // Reset when resetTrigger changes
90
+ (0, react_1.useEffect)(() => {
91
+ setItems([]);
92
+ setAllLoaded(false);
93
+ isLoadingRef.current = false;
94
+ }, [props.resetTrigger]);
95
+ return (react_1.default.createElement("div", { id: props.scrollableId, style: {
96
+ overflow: 'auto',
97
+ display: 'flex',
98
+ flexDirection: 'column-reverse',
99
+ ...props.style,
100
+ } },
101
+ react_1.default.createElement(react_infinite_scroll_component_1.default, { dataLength: items.length, next: loadMore, style: { display: 'flex', flexDirection: 'column-reverse', overflow: 'hidden' }, inverse: true, hasMore: !allLoaded, loader: props.loadingIndicator ?? (react_1.default.createElement("div", { className: "d-flex justify-content-center py-2" },
102
+ react_1.default.createElement("div", { className: "spinner-border spinner-border-sm text-secondary", role: "status" }))), scrollableTarget: props.scrollableId }, props.renderItems(items))));
103
+ }
@@ -1,3 +1,5 @@
1
1
  import React from 'react';
2
2
  export declare const LeftBar: () => React.JSX.Element;
3
- export declare const LeftBarContent: () => React.JSX.Element;
3
+ export declare const LeftBarContent: ({ width }?: {
4
+ width?: number;
5
+ }) => React.JSX.Element;
@@ -1,32 +1,120 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
5
35
  Object.defineProperty(exports, "__esModule", { value: true });
6
36
  exports.LeftBarContent = exports.LeftBar = void 0;
7
37
  const peers_sdk_1 = require("@peers-app/peers-sdk");
8
- const react_1 = __importDefault(require("react"));
38
+ const react_1 = __importStar(require("react"));
9
39
  const globals_1 = require("../globals");
10
40
  const hooks_1 = require("../hooks");
11
41
  const off_canvas_1 = require("./off-canvas");
12
42
  // import { useObservable } from 'peers-ui';
13
43
  const offCanvasEffects = { hide: () => { } };
44
+ const LEFT_BAR_WIDTH_KEY = 'leftBarWidth';
45
+ const DEFAULT_LEFT_BAR_WIDTH = 300;
46
+ function getStoredLeftBarWidth() {
47
+ const stored = localStorage.getItem(LEFT_BAR_WIDTH_KEY);
48
+ return stored ? parseInt(stored, 10) : DEFAULT_LEFT_BAR_WIDTH;
49
+ }
50
+ function setStoredLeftBarWidth(width) {
51
+ localStorage.setItem(LEFT_BAR_WIDTH_KEY, String(width));
52
+ document.documentElement.style.setProperty('--left-bar-width', `${width}px`);
53
+ }
54
+ // Set initial CSS variable on load
55
+ setStoredLeftBarWidth(getStoredLeftBarWidth());
14
56
  const LeftBar = () => {
15
57
  const [_isDesktop] = (0, hooks_1.useObservable)(globals_1.isDesktop);
58
+ const [width, setWidth] = (0, react_1.useState)(() => getStoredLeftBarWidth());
59
+ const isDragging = (0, react_1.useRef)(false);
60
+ const startX = (0, react_1.useRef)(0);
61
+ const startWidth = (0, react_1.useRef)(0);
62
+ const onMouseMove = (0, react_1.useCallback)((e) => {
63
+ if (!isDragging.current)
64
+ return;
65
+ const delta = e.clientX - startX.current;
66
+ const newWidth = Math.max(150, Math.min(600, startWidth.current + delta));
67
+ setWidth(newWidth);
68
+ setStoredLeftBarWidth(newWidth);
69
+ }, []);
70
+ const onMouseUp = (0, react_1.useCallback)(() => {
71
+ isDragging.current = false;
72
+ document.removeEventListener('mousemove', onMouseMove);
73
+ document.removeEventListener('mouseup', onMouseUp);
74
+ document.body.style.cursor = '';
75
+ document.body.style.userSelect = '';
76
+ }, [onMouseMove]);
77
+ const onMouseDown = (0, react_1.useCallback)((e) => {
78
+ e.preventDefault();
79
+ isDragging.current = true;
80
+ startX.current = e.clientX;
81
+ startWidth.current = width;
82
+ document.addEventListener('mousemove', onMouseMove);
83
+ document.addEventListener('mouseup', onMouseUp);
84
+ document.body.style.cursor = 'col-resize';
85
+ document.body.style.userSelect = 'none';
86
+ }, [width, onMouseMove, onMouseUp]);
87
+ (0, react_1.useEffect)(() => {
88
+ return () => {
89
+ document.removeEventListener('mousemove', onMouseMove);
90
+ document.removeEventListener('mouseup', onMouseUp);
91
+ };
92
+ }, [onMouseMove, onMouseUp]);
16
93
  // const [usersAndGroups, setUsersAndGroups] = useState<(IUser | IGroup)[]>([]);
17
94
  if (_isDesktop) {
18
- return (react_1.default.createElement("div", { className: 'left-bar-desktop', style: { position: 'fixed', top: 0, left: 0 } },
19
- react_1.default.createElement(exports.LeftBarContent, null)));
95
+ return (react_1.default.createElement("div", { className: 'left-bar-desktop', style: { position: 'fixed', top: 0, left: 0, width, display: 'flex', flexDirection: 'row' } },
96
+ react_1.default.createElement(exports.LeftBarContent, { width: width }),
97
+ react_1.default.createElement("div", { onMouseDown: onMouseDown, title: "Drag to resize", style: {
98
+ position: 'absolute',
99
+ top: 0,
100
+ right: '-3px',
101
+ width: '6px',
102
+ height: '100%',
103
+ cursor: 'col-resize',
104
+ zIndex: 200,
105
+ backgroundColor: 'transparent',
106
+ }, onMouseEnter: e => { e.target.style.backgroundColor = 'rgba(128,128,128,0.4)'; }, onMouseLeave: e => { e.target.style.backgroundColor = 'transparent'; } })));
20
107
  }
21
108
  else {
22
109
  return (react_1.default.createElement(off_canvas_1.OffCanvas, { id: "leftBar", position: "left", keepOpen: _isDesktop, effects: offCanvasEffects },
23
- react_1.default.createElement(exports.LeftBarContent, null)));
110
+ react_1.default.createElement(exports.LeftBarContent, { width: DEFAULT_LEFT_BAR_WIDTH })));
24
111
  }
25
112
  };
26
113
  exports.LeftBar = LeftBar;
27
- const LeftBarContent = () => {
114
+ const LeftBarContent = ({ width } = {}) => {
28
115
  const [contentPath] = (0, hooks_1.useObservable)(globals_1.mainContentPath);
29
116
  const [_isDesktop] = (0, hooks_1.useObservable)(globals_1.isDesktop);
117
+ const barWidth = width ?? DEFAULT_LEFT_BAR_WIDTH;
30
118
  const packagesWithNavItems = (0, hooks_1.usePromise)(async () => {
31
119
  const packages = await (0, peers_sdk_1.Packages)().list();
32
120
  return packages.filter(pkg => pkg.appNavs?.length);
@@ -38,7 +126,7 @@ const LeftBarContent = () => {
38
126
  ];
39
127
  const knowledgeSublinkActive = contentPath.startsWith('knowledge-') || knowledgeSublinks.find(ks => contentPath.startsWith(ks.path || ks.text.toLowerCase()));
40
128
  const containerClassName = `d-flex flex-column flex-shrink-0 p-2 text-white ` + (_isDesktop ? 'bg-dark-subtle' : 'bg-dark');
41
- const content = (react_1.default.createElement("div", { className: containerClassName, style: { width: "300px", height: "calc(100vh - 25px)" } },
129
+ const content = (react_1.default.createElement("div", { className: containerClassName, style: { width: barWidth, height: "calc(100vh - 25px)" } },
42
130
  react_1.default.createElement("div", { className: "clearfix" },
43
131
  react_1.default.createElement("div", { className: "dropdown", style: { display: 'inline-block', width: '200px' } },
44
132
  react_1.default.createElement("a", { id: "dropdownUser1", "data-bs-toggle": "dropdown", "aria-expanded": "false", className: "d-flex align-items-center text-white text-decoration-none", href: "#", tabIndex: -1 },
@@ -43,6 +43,8 @@ const globals_1 = require("../globals");
43
43
  const thread_view_1 = require("./messages/thread-view");
44
44
  const router_1 = require("./router");
45
45
  const hooks_1 = require("../hooks");
46
+ const LEFT_BAR_WIDTH_KEY = 'leftBarWidth';
47
+ const DEFAULT_LEFT_BAR_WIDTH = 300;
46
48
  const MainContentContainer = () => {
47
49
  const splitPositionGlobalName = 'mainContentContainerSplitPos';
48
50
  let [splitPos, setSplitPos] = (0, react_1.useState)(localStorage.getItem(splitPositionGlobalName) || '800');
@@ -83,7 +85,7 @@ const MainContentContainer = () => {
83
85
  react_1.default.createElement("div", { className: 'thread-view', style: {
84
86
  height: `calc(100vh - 25px)`,
85
87
  overflowY: 'scroll',
86
- width: `calc(100vw - ${splitPos}px - 300px)`,
88
+ width: `calc(100vw - ${splitPos}px - ${parseInt(localStorage.getItem(LEFT_BAR_WIDTH_KEY) || String(DEFAULT_LEFT_BAR_WIDTH), 10)}px)`,
87
89
  } },
88
90
  react_1.default.createElement(thread_view_1.ThreadContainer, null))));
89
91
  };
@@ -1,6 +1,4 @@
1
- import { ElementTransformer } from "@lexical/markdown";
2
1
  import { Observable } from "@peers-app/peers-sdk";
3
- export declare const PRESERVE_NEWLINES: ElementTransformer;
4
2
  export declare const customMarkdownTransformers: import("@lexical/markdown").Transformer[];
5
3
  interface IProps {
6
4
  markdownObs: Observable<string>;
@@ -1,12 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.customMarkdownTransformers = exports.PRESERVE_NEWLINES = void 0;
3
+ exports.customMarkdownTransformers = void 0;
4
4
  exports.MarkdownPlugin = MarkdownPlugin;
5
5
  const list_1 = require("@lexical/list");
6
6
  const markdown_1 = require("@lexical/markdown");
7
7
  const LexicalComposerContext_1 = require("@lexical/react/LexicalComposerContext");
8
8
  const peers_sdk_1 = require("@peers-app/peers-sdk");
9
- const lexical_1 = require("lexical");
10
9
  const react_1 = require("react");
11
10
  const mention_node_1 = require("./mention-node");
12
11
  // Amount of spaces that define indentation level
@@ -118,27 +117,7 @@ const MENTION_TRANSFORMER = {
118
117
  textNode.replace(node);
119
118
  },
120
119
  };
121
- exports.PRESERVE_NEWLINES = {
122
- type: 'element',
123
- dependencies: [lexical_1.ParagraphNode],
124
- export: (node) => {
125
- if ((0, lexical_1.$isParagraphNode)(node)) {
126
- const content = node.getTextContent();
127
- if (!content.trim()) {
128
- return '\n \n';
129
- }
130
- }
131
- return null;
132
- },
133
- regExp: /^ $/, // a line with only a space
134
- replace: (textNode, nodes, _, isImport) => {
135
- if (isImport && nodes.length === 1) {
136
- textNode.append((0, lexical_1.$createTextNode)(''));
137
- }
138
- },
139
- };
140
120
  exports.customMarkdownTransformers = [
141
- exports.PRESERVE_NEWLINES,
142
121
  MENTION_TRANSFORMER,
143
122
  CHECK_LIST_TRANSFORMER, // This needs to be first or checklists will be treated as unordered lists
144
123
  ...markdown_1.TRANSFORMERS.filter(transformer => {
@@ -162,9 +141,7 @@ function MarkdownPlugin(props) {
162
141
  }
163
142
  markdown = newMarkdown;
164
143
  editor.update(() => {
165
- const shouldPreserveNewLines = false;
166
- const shouldMergeAdjacentLines = false;
167
- (0, markdown_1.$convertFromMarkdownString)(markdown, exports.customMarkdownTransformers, undefined, shouldPreserveNewLines, shouldMergeAdjacentLines);
144
+ (0, markdown_1.$convertFromMarkdownString)(markdown, exports.customMarkdownTransformers, undefined, true, false);
168
145
  });
169
146
  }
170
147
  setMarkdown();
@@ -172,13 +149,8 @@ function MarkdownPlugin(props) {
172
149
  setMarkdown();
173
150
  });
174
151
  const sub2Dispose = editor.registerUpdateListener(({ editorState }) => {
175
- editor.update(() => {
176
- const shouldPreserveNewLines = false;
177
- let newMarkdown = (0, markdown_1.$convertToMarkdownString)(exports.customMarkdownTransformers, undefined, shouldPreserveNewLines);
178
- newMarkdown = newMarkdown.replace(/\n\n\n \n\n\n/gm, '\n\n \n\n');
179
- if (newMarkdown === "\n \n") {
180
- newMarkdown = '';
181
- }
152
+ editorState.read(() => {
153
+ const newMarkdown = (0, markdown_1.$convertToMarkdownString)(exports.customMarkdownTransformers, undefined, true);
182
154
  if (newMarkdown !== markdown) {
183
155
  markdown = newMarkdown;
184
156
  markdownObs(markdown);
@@ -6,6 +6,7 @@ interface ITabsProps {
6
6
  active?: true;
7
7
  }[];
8
8
  contentClassName?: string;
9
+ activeTabName?: string;
9
10
  }
10
11
  export declare const Tabs: (props: ITabsProps) => React.JSX.Element;
11
12
  export declare const ScreenTabBody: (props: React.PropsWithChildren) => React.JSX.Element;
@@ -44,22 +44,23 @@ const Tabs = (props) => {
44
44
  activeTabName = activeTabName.toLowerCase();
45
45
  const _activeTab = tabs.find(tab => tab.name.toLowerCase() === activeTabName) || tabs[0];
46
46
  lastActive = _activeTab.name;
47
- // TODO setup query persistence on tabs
48
- // setTimeout(() => queryParam('tab', lastActive.toLowerCase()));
49
47
  return _activeTab;
50
48
  });
49
+ // Allow parent to control the active tab imperatively
50
+ const resolvedActiveTab = props.activeTabName
51
+ ? (tabs.find(tab => tab.name.toLowerCase() === props.activeTabName.toLowerCase()) ?? activeTab)
52
+ : activeTab;
51
53
  function setActiveTab(tab) {
52
54
  _setActiveTab(tab);
53
- // queryParam('tab', tab.name.toLowerCase());
54
55
  lastActive = tab.name;
55
56
  }
56
57
  return (react_1.default.createElement(react_1.default.Fragment, null,
57
58
  react_1.default.createElement("ul", { className: "nav nav-tabs" }, tabs.map((tab, iTab) => {
58
59
  return (react_1.default.createElement("li", { key: tab.name + iTab, className: "nav-item" },
59
- react_1.default.createElement("a", { href: "#", className: "nav-link " + (activeTab.name === tab.name ? 'active' : ''), onClick: evt => (evt.preventDefault(), setActiveTab(tab)) }, tab.name)));
60
+ react_1.default.createElement("a", { href: "#", className: "nav-link " + (resolvedActiveTab.name === tab.name ? 'active' : ''), onClick: evt => (evt.preventDefault(), setActiveTab(tab)) }, tab.name)));
60
61
  })),
61
62
  react_1.default.createElement("div", { className: "tab-content " + (props.contentClassName ?? 'mt-3') }, tabs.map((tab, iTab) => {
62
- return (react_1.default.createElement("div", { key: tab.name + iTab, hidden: activeTab.name !== tab.name }, tab.content));
63
+ return (react_1.default.createElement("div", { key: tab.name + iTab, hidden: resolvedActiveTab.name !== tab.name }, tab.content));
63
64
  }))));
64
65
  };
65
66
  exports.Tabs = Tabs;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,10 @@
1
1
  export * from "./screens/events/cron";
2
2
  export * from "./tabs-layout/tabs-layout";
3
+ export { activeTabId, activeTabs, TabState } from "./tabs-layout/tabs-state";
3
4
  export * from "./components/voice-indicator";
4
5
  export * from "./components/chat-overlay";
5
6
  export * from "./components/sortable-list";
6
7
  export * from "./components/tabs";
8
+ export * from "./components/inverse-lazy-list";
7
9
  export * from "./components/markdown-editor/editor-inline";
10
+ export * from "./components/markdown-editor/editor";
package/dist/index.js CHANGED
@@ -14,10 +14,16 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.activeTabs = exports.activeTabId = void 0;
17
18
  __exportStar(require("./screens/events/cron"), exports);
18
19
  __exportStar(require("./tabs-layout/tabs-layout"), exports);
20
+ var tabs_state_1 = require("./tabs-layout/tabs-state");
21
+ Object.defineProperty(exports, "activeTabId", { enumerable: true, get: function () { return tabs_state_1.activeTabId; } });
22
+ Object.defineProperty(exports, "activeTabs", { enumerable: true, get: function () { return tabs_state_1.activeTabs; } });
19
23
  __exportStar(require("./components/voice-indicator"), exports);
20
24
  __exportStar(require("./components/chat-overlay"), exports);
21
25
  __exportStar(require("./components/sortable-list"), exports);
22
26
  __exportStar(require("./components/tabs"), exports);
27
+ __exportStar(require("./components/inverse-lazy-list"), exports);
23
28
  __exportStar(require("./components/markdown-editor/editor-inline"), exports);
29
+ __exportStar(require("./components/markdown-editor/editor"), exports);
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const VoiceSettingsAgent: React.FC;
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.VoiceSettingsAgent = void 0;
37
+ const react_1 = __importStar(require("react"));
38
+ const peers_sdk_1 = require("@peers-app/peers-sdk");
39
+ const VOICE_HUB_AGENT_ASSISTANT_ID = '00mh0wlipkdbeaw8vhagent01';
40
+ // Must match the pvar names in peers-core/src/data/voice-agent-config.ts and
41
+ // peers-electron/src/server/voice/voice-agent-config.ts — pvar instances are
42
+ // singletons keyed by name+scope so all three share the same underlying value.
43
+ const voiceAgentAssistantIdPvar = (0, peers_sdk_1.userVar)('voice:agentAssistantId', { defaultValue: VOICE_HUB_AGENT_ASSISTANT_ID });
44
+ const VoiceSettingsAgent = () => {
45
+ const [assistants, setAssistants] = (0, react_1.useState)([]);
46
+ const [selectedId, setSelectedId] = (0, react_1.useState)(VOICE_HUB_AGENT_ASSISTANT_ID);
47
+ const [saving, setSaving] = (0, react_1.useState)(false);
48
+ const [saved, setSaved] = (0, react_1.useState)(false);
49
+ (0, react_1.useEffect)(() => {
50
+ const load = async () => {
51
+ await voiceAgentAssistantIdPvar.loadingPromise;
52
+ setSelectedId(voiceAgentAssistantIdPvar() ?? VOICE_HUB_AGENT_ASSISTANT_ID);
53
+ const all = await (0, peers_sdk_1.Assistants)().list({});
54
+ setAssistants(all);
55
+ };
56
+ load();
57
+ }, []);
58
+ const handleSave = (0, react_1.useCallback)(async () => {
59
+ setSaving(true);
60
+ setSaved(false);
61
+ await voiceAgentAssistantIdPvar.loadingPromise;
62
+ voiceAgentAssistantIdPvar(selectedId || undefined);
63
+ setSaving(false);
64
+ setSaved(true);
65
+ setTimeout(() => setSaved(false), 2000);
66
+ }, [selectedId]);
67
+ return (react_1.default.createElement("div", null,
68
+ react_1.default.createElement("h6", null, "Voice Agent"),
69
+ react_1.default.createElement("p", { className: "text-muted small mb-2" }, "When you give a voice command that requires action (e.g. \"add milk to the shopping list\"), it is dispatched to this assistant as a background task."),
70
+ react_1.default.createElement("div", { className: "d-flex align-items-center gap-2" },
71
+ react_1.default.createElement("select", { className: "form-select form-select-sm", value: selectedId, onChange: e => setSelectedId(e.target.value), disabled: saving }, assistants.map(a => (react_1.default.createElement("option", { key: a.assistantId, value: a.assistantId },
72
+ a.name,
73
+ a.assistantId === VOICE_HUB_AGENT_ASSISTANT_ID ? ' (default)' : '')))),
74
+ react_1.default.createElement("button", { className: "btn btn-sm btn-primary", onClick: handleSave, disabled: saving, style: { whiteSpace: 'nowrap' } }, saving ? 'Saving…' : saved ? 'Saved!' : 'Save'))));
75
+ };
76
+ exports.VoiceSettingsAgent = VoiceSettingsAgent;
@@ -0,0 +1,13 @@
1
+ import React from 'react';
2
+ import { VoiceSettings, PicovoiceKeyError } from './voice-settings-types';
3
+ interface ApiKeysProps {
4
+ settings: VoiceSettings;
5
+ picovoiceKey: string;
6
+ openaiKey: string;
7
+ keyError: PicovoiceKeyError | null;
8
+ onPicovoiceKeyChange: (value: string) => void;
9
+ onOpenaiKeyChange: (value: string) => void;
10
+ onDismissKeyError: () => void;
11
+ }
12
+ export declare const VoiceSettingsApiKeys: React.FC<ApiKeysProps>;
13
+ export {};
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.VoiceSettingsApiKeys = void 0;
7
+ const react_1 = __importDefault(require("react"));
8
+ const tooltip_1 = require("../../components/tooltip");
9
+ const voice_settings_types_1 = require("./voice-settings-types");
10
+ const VoiceSettingsApiKeys = ({ settings, picovoiceKey, openaiKey, keyError, onPicovoiceKeyChange, onOpenaiKeyChange, onDismissKeyError, }) => (react_1.default.createElement(react_1.default.Fragment, null,
11
+ react_1.default.createElement("div", { className: "mb-4" },
12
+ react_1.default.createElement("label", { className: "form-label" },
13
+ "Picovoice Access Key",
14
+ react_1.default.createElement(tooltip_1.Tooltip, { markdownContent: "Required for wake word detection. Get a free key at [console.picovoice.ai](https://console.picovoice.ai)" }),
15
+ settings.picovoiceAccessKeySet && (react_1.default.createElement("span", { className: "badge bg-success ms-2" }, "Set"))),
16
+ react_1.default.createElement("input", { type: "password", className: "form-control", value: picovoiceKey, onChange: (e) => onPicovoiceKeyChange(e.target.value), placeholder: settings.picovoiceAccessKeySet ? '••••••••••••••••' : 'Enter your Picovoice access key' }),
17
+ react_1.default.createElement("small", { className: "text-muted" },
18
+ react_1.default.createElement("a", { href: "https://console.picovoice.ai", target: "_blank", rel: "noopener noreferrer" }, "Get a free access key"),
19
+ settings.picovoiceAccessKeySet && ' (leave blank to keep existing key)')),
20
+ keyError && (react_1.default.createElement("div", { className: "alert alert-warning alert-dismissible mb-4", role: "alert" },
21
+ react_1.default.createElement("div", { className: "d-flex align-items-start gap-2" },
22
+ react_1.default.createElement("i", { className: "bi bi-exclamation-triangle-fill flex-shrink-0 mt-1" }),
23
+ react_1.default.createElement("div", null,
24
+ react_1.default.createElement("strong", null, voice_settings_types_1.PICOVOICE_KEY_ERROR_MESSAGES[keyError].heading),
25
+ react_1.default.createElement("div", { className: "mt-1" }, voice_settings_types_1.PICOVOICE_KEY_ERROR_MESSAGES[keyError].body),
26
+ react_1.default.createElement("a", { href: "https://console.picovoice.ai", target: "_blank", rel: "noopener noreferrer", className: "alert-link mt-1 d-inline-block" }, "Open console.picovoice.ai"))),
27
+ react_1.default.createElement("button", { type: "button", className: "btn-close", onClick: onDismissKeyError }))),
28
+ react_1.default.createElement("div", { className: "mb-3" },
29
+ react_1.default.createElement("label", { className: "form-label" },
30
+ "OpenAI API Key",
31
+ react_1.default.createElement(tooltip_1.Tooltip, { markdownContent: "Required for cloud STT (Whisper) and cloud TTS. Leave empty to use local/browser alternatives." }),
32
+ settings.openaiApiKeySet && (react_1.default.createElement("span", { className: "badge bg-success ms-2" }, "Set"))),
33
+ react_1.default.createElement("input", { type: "password", className: "form-control", value: openaiKey, onChange: (e) => onOpenaiKeyChange(e.target.value), placeholder: settings.openaiApiKeySet ? '••••••••••••••••' : 'sk-...' }),
34
+ settings.openaiApiKeySet && (react_1.default.createElement("small", { className: "text-muted" }, "Leave blank to keep existing key")))));
35
+ exports.VoiceSettingsApiKeys = VoiceSettingsApiKeys;
@@ -0,0 +1,16 @@
1
+ import React from 'react';
2
+ import { VoiceSettings } from './voice-settings-types';
3
+ interface OutputProps {
4
+ settings: VoiceSettings;
5
+ localSettings: VoiceSettings;
6
+ saving: boolean;
7
+ testText: string;
8
+ hasUnsavedChanges: boolean;
9
+ audioDevices: string[];
10
+ onChange: (patch: Partial<VoiceSettings>) => void;
11
+ onTestTextChange: (text: string) => void;
12
+ onTestTTS: () => void;
13
+ onTestRecording: () => void;
14
+ }
15
+ export declare const VoiceSettingsOutput: React.FC<OutputProps>;
16
+ export {};