cozy-search 2.2.30 → 2.3.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.
- package/README.md +44 -0
- package/dist/components/Assistant/AssistantSelection.js +1 -1
- package/dist/components/CozyAssistantRuntimeProvider.js +20 -9
- package/dist/components/KnowledgeBase/KnowledgeBaseChip.d.ts +2 -1
- package/dist/components/KnowledgeBase/KnowledgeBaseChip.js +2 -1
- package/dist/components/KnowledgeBase/KnowledgeBaseSection.js +1 -1
- package/dist/components/KnowledgeBase/conversationAssistant.d.ts +6 -0
- package/dist/components/KnowledgeBase/conversationAssistant.js +42 -0
- package/dist/components/KnowledgeBase/conversationAssistant.spec.d.ts +1 -0
- package/dist/components/KnowledgeBase/conversationAssistant.spec.js +80 -0
- package/dist/components/KnowledgeBase/knowledgeBase.d.ts +8 -0
- package/dist/components/KnowledgeBase/knowledgeBase.js +57 -11
- package/dist/components/KnowledgeBase/knowledgeBase.spec.js +92 -0
- package/dist/components/KnowledgeBase/provisioning.d.ts +24 -0
- package/dist/components/KnowledgeBase/provisioning.js +645 -0
- package/dist/components/KnowledgeBase/provisioning.spec.d.ts +1 -0
- package/dist/components/KnowledgeBase/provisioning.spec.js +489 -0
- package/dist/components/KnowledgeBase/ragIndexing.d.ts +21 -0
- package/dist/components/KnowledgeBase/ragIndexing.js +415 -0
- package/dist/components/KnowledgeBase/ragIndexing.spec.d.ts +1 -0
- package/dist/components/KnowledgeBase/ragIndexing.spec.js +268 -0
- package/dist/components/KnowledgeBase/useDefaultAssistantId.d.ts +1 -0
- package/dist/components/KnowledgeBase/useDefaultAssistantId.js +38 -0
- package/dist/components/KnowledgeBase/useDefaultAssistantId.spec.d.ts +1 -0
- package/dist/components/KnowledgeBase/useDefaultAssistantId.spec.js +69 -0
- package/dist/components/KnowledgeBase/useSelectedAssistantKnowledgeBase.d.ts +1 -0
- package/dist/components/KnowledgeBase/useSelectedAssistantKnowledgeBase.js +6 -2
- package/dist/components/TwakeKnowledges/TwakeKnowledgeSelector.js +2 -0
- package/dist/components/Views/CreateAssistantDialog.js +6 -12
- package/dist/components/Views/DeleteAssistantDialog.js +20 -12
- package/dist/components/Views/EditAssistantDialog.js +13 -7
- package/dist/components/queries.d.ts +8 -0
- package/dist/components/queries.js +21 -2
- package/dist/index.d.ts +2 -0
- package/dist/index.js +35 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -34,15 +34,59 @@ import 'cozy-search/dist/stylesheet.css'
|
|
|
34
34
|
"description": "Required by the cozy Assistant",
|
|
35
35
|
"type": "io.cozy.ai.chat.events",
|
|
36
36
|
"verbs": ["GET"]
|
|
37
|
+
},
|
|
38
|
+
"chatAssistants": {
|
|
39
|
+
"description": "Required by the cozy Assistant",
|
|
40
|
+
"type": "io.cozy.ai.chat.assistants",
|
|
41
|
+
"verbs": ["GET", "POST", "PUT", "DELETE"]
|
|
42
|
+
},
|
|
43
|
+
"accounts": {
|
|
44
|
+
"description": "Required by the cozy Assistant (provider accounts)",
|
|
45
|
+
"type": "io.cozy.accounts",
|
|
46
|
+
"verbs": ["GET", "POST", "PUT", "DELETE"]
|
|
37
47
|
}
|
|
38
48
|
```
|
|
39
49
|
|
|
50
|
+
The assistants and their provider accounts are created, edited and
|
|
51
|
+
deleted from the library's dialogs, so the host app declares the full set
|
|
52
|
+
of verbs (cozy-home and cozy-drive use `ALL`).
|
|
53
|
+
|
|
40
54
|
2. Add realtime queries for chat conversations in your tree :
|
|
41
55
|
|
|
42
56
|
```jsx
|
|
43
57
|
<RealTimeQueries doctype="io.cozy.ai.chat.conversations" />
|
|
44
58
|
```
|
|
45
59
|
|
|
60
|
+
### RAG indexing setup
|
|
61
|
+
|
|
62
|
+
Call `setupRagIndexing(client)` once per session at startup (before
|
|
63
|
+
`ensureProvisionedAssistants` if your app provisions assistants from the
|
|
64
|
+
`rag.assistants.autoprovision` flag). It makes sure the instance's two
|
|
65
|
+
`rag-index` triggers exist (one on `io.cozy.files`, one on
|
|
66
|
+
`io.cozy.ai.chat.assistants`) and gives the root folder to any assistant
|
|
67
|
+
that has no knowledge base folder yet — the stack's `rag-index` worker
|
|
68
|
+
reads the assistants to know what to index, cozy-search only has to keep
|
|
69
|
+
the triggers and the assistants' `knowledgeBase` in shape.
|
|
70
|
+
|
|
71
|
+
It needs the following permissions: `io.cozy.triggers` and `io.cozy.jobs`
|
|
72
|
+
(to create and launch the triggers), `io.cozy.ai.chat.assistants` and
|
|
73
|
+
`io.cozy.files` (to read and migrate the assistants). It is idempotent and
|
|
74
|
+
never throws: on a stack whose `rag-index` worker is still reserved, the
|
|
75
|
+
403 is logged and the app keeps working. `ensureProvisionedAssistants`
|
|
76
|
+
additionally creates one `io.cozy.accounts` document per provisioned
|
|
77
|
+
assistant, and the `dirName` folder in `io.cozy.files`.
|
|
78
|
+
|
|
79
|
+
An entry of `rag.assistants.autoprovision` can carry `"default": true`
|
|
80
|
+
(the first flagged entry wins if there are several). Every NEW
|
|
81
|
+
conversation then starts on that assistant once its document exists —
|
|
82
|
+
from the session after it was provisioned onward. Existing conversations
|
|
83
|
+
are unaffected: they keep their own assistant, or stay unscoped if they
|
|
84
|
+
had none.
|
|
85
|
+
|
|
86
|
+
```json
|
|
87
|
+
{ "name": "Mes documents", "dirName": "Documents", "default": true }
|
|
88
|
+
```
|
|
89
|
+
|
|
46
90
|
### On desktop
|
|
47
91
|
|
|
48
92
|
You can add the search bar like this :
|
|
@@ -165,7 +165,7 @@ var AssistantSelection = function AssistantSelection(_ref) {
|
|
|
165
165
|
assistant: _constants.DEFAULT_ASSISTANT,
|
|
166
166
|
onClose: handleClose,
|
|
167
167
|
onSelect: function onSelect() {
|
|
168
|
-
return setSelectedAssistantId(_constants.DEFAULT_ASSISTANT.
|
|
168
|
+
return setSelectedAssistantId(_constants.DEFAULT_ASSISTANT._id);
|
|
169
169
|
},
|
|
170
170
|
selectedAssistant: selectedAssistant,
|
|
171
171
|
disableActions: true
|
|
@@ -43,12 +43,14 @@ var _twakeI18n = require("twake-i18n");
|
|
|
43
43
|
|
|
44
44
|
var _AssistantProvider = require("./AssistantProvider");
|
|
45
45
|
|
|
46
|
+
var _conversationAssistant = require("./KnowledgeBase/conversationAssistant");
|
|
47
|
+
|
|
48
|
+
var _useDefaultAssistantId = require("./KnowledgeBase/useDefaultAssistantId");
|
|
49
|
+
|
|
46
50
|
var _CozyRealtimeChatAdapter = require("./adapters/CozyRealtimeChatAdapter");
|
|
47
51
|
|
|
48
52
|
var _StreamBridge = require("./adapters/StreamBridge");
|
|
49
53
|
|
|
50
|
-
var _constants = require("./constants");
|
|
51
|
-
|
|
52
54
|
var _helpers = require("./helpers");
|
|
53
55
|
|
|
54
56
|
var _queries = require("./queries");
|
|
@@ -80,7 +82,7 @@ var convertMessagesToThreadMessages = function convertMessagesToThreadMessages(m
|
|
|
80
82
|
};
|
|
81
83
|
|
|
82
84
|
var ConversationLoader = function ConversationLoader(_ref) {
|
|
83
|
-
var _conversation$
|
|
85
|
+
var _conversation$relatio, _conversation$relatio2, _conversation$relatio3;
|
|
84
86
|
|
|
85
87
|
var children = _ref.children,
|
|
86
88
|
conversationId = _ref.conversationId;
|
|
@@ -95,16 +97,25 @@ var ConversationLoader = function ConversationLoader(_ref) {
|
|
|
95
97
|
var queryResult = (0, _cozyClient.useQuery)(conversationQuery.definition, conversationQuery.options);
|
|
96
98
|
var conversation = queryResult.data;
|
|
97
99
|
var isLoading = (0, _cozyClient.isQueryLoading)(queryResult);
|
|
100
|
+
var boundId = conversation === null || conversation === void 0 ? void 0 : (_conversation$relatio = conversation.relationships) === null || _conversation$relatio === void 0 ? void 0 : (_conversation$relatio2 = _conversation$relatio.assistant) === null || _conversation$relatio2 === void 0 ? void 0 : (_conversation$relatio3 = _conversation$relatio2.data) === null || _conversation$relatio3 === void 0 ? void 0 : _conversation$relatio3._id;
|
|
101
|
+
var defaultId = (0, _useDefaultAssistantId.useDefaultAssistantId)();
|
|
98
102
|
var initialMessages = (0, _react2.useMemo)(function () {
|
|
99
103
|
return convertMessagesToThreadMessages(conversation === null || conversation === void 0 ? void 0 : conversation.messages, t);
|
|
100
|
-
}, [conversation === null || conversation === void 0 ? void 0 : conversation.messages, t]);
|
|
104
|
+
}, [conversation === null || conversation === void 0 ? void 0 : conversation.messages, t]); // Undefined until the conversation, and the default a new one starts
|
|
105
|
+
// on, are known: the runtime waits, or a message could go to the
|
|
106
|
+
// sentinel assistant before the configured default is selected.
|
|
107
|
+
|
|
108
|
+
var resolvedAssistantId = (0, _conversationAssistant.resolveConversationAssistantId)({
|
|
109
|
+
boundId: boundId,
|
|
110
|
+
conversation: conversation,
|
|
111
|
+
isLoading: isLoading,
|
|
112
|
+
defaultId: defaultId
|
|
113
|
+
});
|
|
101
114
|
(0, _react2.useEffect)(function () {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
setSelectedAssistantId((conversation === null || conversation === void 0 ? void 0 : (_conversation$relatio = conversation.relationships) === null || _conversation$relatio === void 0 ? void 0 : (_conversation$relatio2 = _conversation$relatio.assistant) === null || _conversation$relatio2 === void 0 ? void 0 : (_conversation$relatio3 = _conversation$relatio2.data) === null || _conversation$relatio3 === void 0 ? void 0 : _conversation$relatio3._id) || _constants.DEFAULT_ASSISTANT._id);
|
|
105
|
-
}, [conversation === null || conversation === void 0 ? void 0 : (_conversation$relatio4 = conversation.relationships) === null || _conversation$relatio4 === void 0 ? void 0 : (_conversation$relatio5 = _conversation$relatio4.assistant) === null || _conversation$relatio5 === void 0 ? void 0 : (_conversation$relatio6 = _conversation$relatio5.data) === null || _conversation$relatio6 === void 0 ? void 0 : _conversation$relatio6._id, setSelectedAssistantId]);
|
|
115
|
+
if (resolvedAssistantId) setSelectedAssistantId(resolvedAssistantId);
|
|
116
|
+
}, [resolvedAssistantId, setSelectedAssistantId]);
|
|
106
117
|
|
|
107
|
-
if (
|
|
118
|
+
if (resolvedAssistantId === undefined) {
|
|
108
119
|
return /*#__PURE__*/_react2.default.createElement("div", {
|
|
109
120
|
className: "u-flex u-flex-items-center u-flex-justify-center u-h-100 u-w-100"
|
|
110
121
|
}, /*#__PURE__*/_react2.default.createElement(_Spinner.default, {
|
|
@@ -7,9 +7,10 @@ export default KnowledgeBaseChip;
|
|
|
7
7
|
* open the folder in Drive (new tab) or change the knowledge base through
|
|
8
8
|
* the Drive folder picker (persisted on the assistant immediately).
|
|
9
9
|
*/
|
|
10
|
-
declare function KnowledgeBaseChip({ dirId, folder, isUnavailable, isLast, onChangeFolder }: {
|
|
10
|
+
declare function KnowledgeBaseChip({ dirId, folder, isRoot, isUnavailable, isLast, onChangeFolder }: {
|
|
11
11
|
dirId: any;
|
|
12
12
|
folder: any;
|
|
13
|
+
isRoot: any;
|
|
13
14
|
isUnavailable: any;
|
|
14
15
|
isLast: any;
|
|
15
16
|
onChangeFolder: any;
|
|
@@ -48,6 +48,7 @@ var KnowledgeBaseChip = function KnowledgeBaseChip(_ref) {
|
|
|
48
48
|
|
|
49
49
|
var dirId = _ref.dirId,
|
|
50
50
|
folder = _ref.folder,
|
|
51
|
+
isRoot = _ref.isRoot,
|
|
51
52
|
isUnavailable = _ref.isUnavailable,
|
|
52
53
|
isLast = _ref.isLast,
|
|
53
54
|
onChangeFolder = _ref.onChangeFolder;
|
|
@@ -83,7 +84,7 @@ var KnowledgeBaseChip = function KnowledgeBaseChip(_ref) {
|
|
|
83
84
|
subDomainType: client === null || client === void 0 ? void 0 : client.getInstanceOptions().subdomain,
|
|
84
85
|
hash: "/folder/".concat(dirId)
|
|
85
86
|
});
|
|
86
|
-
var label = isUnavailable ? t('assistant.knowledge_base.unavailable') : (_folder$name = folder === null || folder === void 0 ? void 0 : folder.name) !== null && _folder$name !== void 0 ? _folder$name : '…';
|
|
87
|
+
var label = isUnavailable ? t('assistant.knowledge_base.unavailable') : isRoot ? t('assistant.twake_knowledges.drive') : (_folder$name = folder === null || folder === void 0 ? void 0 : folder.name) !== null && _folder$name !== void 0 ? _folder$name : '…';
|
|
87
88
|
return /*#__PURE__*/_react.default.createElement(_react.default.Fragment, null, /*#__PURE__*/_react.default.createElement("div", {
|
|
88
89
|
ref: chipRef,
|
|
89
90
|
className: (0, _classnames.default)({
|
|
@@ -102,7 +102,7 @@ var KnowledgeBaseSection = function KnowledgeBaseSection(_ref) {
|
|
|
102
102
|
src: _tdrive.default,
|
|
103
103
|
width: 16
|
|
104
104
|
}),
|
|
105
|
-
label: isUnavailable ? t('assistant.knowledge_base.unavailable') : (_folder$name = folder === null || folder === void 0 ? void 0 : folder.name) !== null && _folder$name !== void 0 ? _folder$name : '…',
|
|
105
|
+
label: isUnavailable ? t('assistant.knowledge_base.unavailable') : (0, _knowledgeBase.isRootDirId)(dirId) ? t('assistant.twake_knowledges.drive') : (_folder$name = folder === null || folder === void 0 ? void 0 : folder.name) !== null && _folder$name !== void 0 ? _folder$name : '…',
|
|
106
106
|
deleteIcon: /*#__PURE__*/_react.default.createElement(_twakeIcons.Icon, {
|
|
107
107
|
icon: _twakeIcons.Cross,
|
|
108
108
|
size: 10 // inline width/height beats MUI v4's fixed deleteIcon class size
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.resolveConversationAssistantId = void 0;
|
|
7
|
+
|
|
8
|
+
var _constants = require("../constants");
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Which assistant a loaded conversation selects. Undefined means "leave the
|
|
12
|
+
* selection alone" (the conversation, or the default a new one starts on,
|
|
13
|
+
* is still loading).
|
|
14
|
+
* @param {object} params
|
|
15
|
+
* @param {string|undefined} params.boundId - The conversation's bound
|
|
16
|
+
* assistant id (`conversation?.relationships?.assistant?.data?._id`).
|
|
17
|
+
* @param {object|undefined} params.conversation - The conversation document,
|
|
18
|
+
* or undefined when it does not exist (yet).
|
|
19
|
+
* @param {boolean} params.isLoading - Whether the conversation query is
|
|
20
|
+
* still loading.
|
|
21
|
+
* @param {string|null|undefined} params.defaultId - The configured default
|
|
22
|
+
* assistant id, only when its document is known to exist; undefined while
|
|
23
|
+
* that is being checked.
|
|
24
|
+
* @returns {string|undefined}
|
|
25
|
+
*/
|
|
26
|
+
var resolveConversationAssistantId = function resolveConversationAssistantId(_ref) {
|
|
27
|
+
var boundId = _ref.boundId,
|
|
28
|
+
conversation = _ref.conversation,
|
|
29
|
+
isLoading = _ref.isLoading,
|
|
30
|
+
defaultId = _ref.defaultId;
|
|
31
|
+
if (boundId) return boundId;
|
|
32
|
+
if (isLoading) return undefined;
|
|
33
|
+
|
|
34
|
+
if (!conversation) {
|
|
35
|
+
if (defaultId === undefined) return undefined;
|
|
36
|
+
return defaultId || _constants.DEFAULT_ASSISTANT._id;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return _constants.DEFAULT_ASSISTANT._id;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
exports.resolveConversationAssistantId = resolveConversationAssistantId;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { resolveConversationAssistantId } from './conversationAssistant'
|
|
2
|
+
|
|
3
|
+
describe('resolveConversationAssistantId', () => {
|
|
4
|
+
it('selects the bound assistant, regardless of anything else', () => {
|
|
5
|
+
expect(
|
|
6
|
+
resolveConversationAssistantId({
|
|
7
|
+
boundId: 'bound',
|
|
8
|
+
conversation: { _id: 'c1' },
|
|
9
|
+
isLoading: false,
|
|
10
|
+
defaultId: 'docs'
|
|
11
|
+
})
|
|
12
|
+
).toBe('bound')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('leaves the selection alone while the conversation query is loading', () => {
|
|
16
|
+
expect(
|
|
17
|
+
resolveConversationAssistantId({
|
|
18
|
+
boundId: undefined,
|
|
19
|
+
conversation: undefined,
|
|
20
|
+
isLoading: true,
|
|
21
|
+
defaultId: 'docs'
|
|
22
|
+
})
|
|
23
|
+
).toBeUndefined()
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('leaves the selection alone while the default of a new conversation is being checked', () => {
|
|
27
|
+
expect(
|
|
28
|
+
resolveConversationAssistantId({
|
|
29
|
+
boundId: undefined,
|
|
30
|
+
conversation: undefined,
|
|
31
|
+
isLoading: false,
|
|
32
|
+
defaultId: undefined
|
|
33
|
+
})
|
|
34
|
+
).toBeUndefined()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('does not wait for the default on an existing conversation', () => {
|
|
38
|
+
expect(
|
|
39
|
+
resolveConversationAssistantId({
|
|
40
|
+
boundId: undefined,
|
|
41
|
+
conversation: { _id: 'c1' },
|
|
42
|
+
isLoading: false,
|
|
43
|
+
defaultId: undefined
|
|
44
|
+
})
|
|
45
|
+
).toBe('default_ai_assistant')
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
it('selects the default for a new conversation when one is configured', () => {
|
|
49
|
+
expect(
|
|
50
|
+
resolveConversationAssistantId({
|
|
51
|
+
boundId: undefined,
|
|
52
|
+
conversation: undefined,
|
|
53
|
+
isLoading: false,
|
|
54
|
+
defaultId: 'docs'
|
|
55
|
+
})
|
|
56
|
+
).toBe('docs')
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
it('selects the sentinel for a new conversation without a configured default', () => {
|
|
60
|
+
expect(
|
|
61
|
+
resolveConversationAssistantId({
|
|
62
|
+
boundId: undefined,
|
|
63
|
+
conversation: undefined,
|
|
64
|
+
isLoading: false,
|
|
65
|
+
defaultId: null
|
|
66
|
+
})
|
|
67
|
+
).toBe('default_ai_assistant')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('selects the sentinel for an existing conversation without an assistant, even with a default configured', () => {
|
|
71
|
+
expect(
|
|
72
|
+
resolveConversationAssistantId({
|
|
73
|
+
boundId: undefined,
|
|
74
|
+
conversation: { _id: 'c1' },
|
|
75
|
+
isLoading: false,
|
|
76
|
+
defaultId: 'docs'
|
|
77
|
+
})
|
|
78
|
+
).toBe('default_ai_assistant')
|
|
79
|
+
})
|
|
80
|
+
})
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/** The root folder of the instance: the assistant covers the whole Drive. */
|
|
2
|
+
export const ROOT_DIR_ID: "io.cozy.files.root-dir";
|
|
3
|
+
export function isRootDirId(dirId: any): boolean;
|
|
4
|
+
export function makeRootKnowledgeBaseEntry(): {
|
|
5
|
+
doctype: string;
|
|
6
|
+
dirId: string;
|
|
7
|
+
};
|
|
8
|
+
export function withRootFolderIfMissing(knowledgeBase?: any[]): any[];
|
|
1
9
|
export function makeKnowledgeBaseEntry(pickedFolder: any): {
|
|
2
10
|
doctype: string;
|
|
3
11
|
dirId: any;
|
|
@@ -5,7 +5,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", {
|
|
6
6
|
value: true
|
|
7
7
|
});
|
|
8
|
-
exports.withoutKnowledgeBaseDoctype = exports.withKnowledgeBaseEntry = exports.saveKnowledgeBase = exports.makeKnowledgeBaseEntry = exports.makeEmailKnowledgeBaseEntry = exports.hasEmailKnowledgeBase = exports.getKnowledgeBaseDirId = void 0;
|
|
8
|
+
exports.withoutKnowledgeBaseDoctype = exports.withRootFolderIfMissing = exports.withKnowledgeBaseEntry = exports.saveKnowledgeBase = exports.makeRootKnowledgeBaseEntry = exports.makeKnowledgeBaseEntry = exports.makeEmailKnowledgeBaseEntry = exports.isRootDirId = exports.hasEmailKnowledgeBase = exports.getKnowledgeBaseDirId = exports.ROOT_DIR_ID = void 0;
|
|
9
9
|
|
|
10
10
|
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
|
|
11
11
|
|
|
@@ -15,15 +15,54 @@ var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/
|
|
|
15
15
|
|
|
16
16
|
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
17
17
|
|
|
18
|
-
var _cozyClient = require("cozy-client");
|
|
19
|
-
|
|
20
18
|
var _queries = require("../queries");
|
|
21
19
|
|
|
22
20
|
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
23
21
|
|
|
24
22
|
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
25
23
|
|
|
26
|
-
|
|
24
|
+
/** The root folder of the instance: the assistant covers the whole Drive. */
|
|
25
|
+
var ROOT_DIR_ID = 'io.cozy.files.root-dir';
|
|
26
|
+
exports.ROOT_DIR_ID = ROOT_DIR_ID;
|
|
27
|
+
|
|
28
|
+
var isRootDirId = function isRootDirId(dirId) {
|
|
29
|
+
return dirId === ROOT_DIR_ID;
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
exports.isRootDirId = isRootDirId;
|
|
33
|
+
|
|
34
|
+
var makeRootKnowledgeBaseEntry = function makeRootKnowledgeBaseEntry() {
|
|
35
|
+
return {
|
|
36
|
+
doctype: _queries.FILES_DOCTYPE,
|
|
37
|
+
dirId: ROOT_DIR_ID
|
|
38
|
+
};
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
exports.makeRootKnowledgeBaseEntry = makeRootKnowledgeBaseEntry;
|
|
42
|
+
|
|
43
|
+
var isFolderEntry = function isFolderEntry(entry) {
|
|
44
|
+
return entry.doctype === _queries.FILES_DOCTYPE && !!entry.dirId;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
var hasFolderEntry = function hasFolderEntry(knowledgeBase) {
|
|
48
|
+
return (knowledgeBase || []).some(isFolderEntry);
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* An assistant always has a knowledge base folder: without one, it covers
|
|
52
|
+
* the whole Drive. Adds the root entry when no folder entry exists,
|
|
53
|
+
* replacing a files entry without dirId (getKnowledgeBaseDirId would find
|
|
54
|
+
* that one first); returns the very same array otherwise.
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
var withRootFolderIfMissing = function withRootFolderIfMissing() {
|
|
59
|
+
var knowledgeBase = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
60
|
+
return hasFolderEntry(knowledgeBase) ? knowledgeBase : [].concat((0, _toConsumableArray2.default)(knowledgeBase.filter(function (entry) {
|
|
61
|
+
return entry.doctype !== _queries.FILES_DOCTYPE;
|
|
62
|
+
})), [makeRootKnowledgeBaseEntry()]);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
exports.withRootFolderIfMissing = withRootFolderIfMissing;
|
|
27
66
|
|
|
28
67
|
var makeKnowledgeBaseEntry = function makeKnowledgeBaseEntry(pickedFolder) {
|
|
29
68
|
return {
|
|
@@ -90,6 +129,10 @@ var getKnowledgeBaseDirId = function getKnowledgeBaseDirId(assistant) {
|
|
|
90
129
|
* applied to the freshly fetched doc's knowledgeBase. Computing the update
|
|
91
130
|
* from the fresh doc rather than a value the caller may have cached avoids
|
|
92
131
|
* dropping concurrent changes (stale-read race).
|
|
132
|
+
*
|
|
133
|
+
* The saved knowledge base always has a folder entry (the root when none
|
|
134
|
+
* was chosen): the stack's rag-index worker reads the assistants to know
|
|
135
|
+
* what to index, there is nothing else to do here.
|
|
93
136
|
*/
|
|
94
137
|
|
|
95
138
|
|
|
@@ -97,25 +140,28 @@ exports.getKnowledgeBaseDirId = getKnowledgeBaseDirId;
|
|
|
97
140
|
|
|
98
141
|
var saveKnowledgeBase = /*#__PURE__*/function () {
|
|
99
142
|
var _ref = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(client, assistantId, knowledgeBaseOrUpdater) {
|
|
100
|
-
var _yield$client$query, assistant, knowledgeBase;
|
|
143
|
+
var _buildAssistantByIdQu, definition, options, _yield$client$query, assistant, knowledgeBase;
|
|
101
144
|
|
|
102
145
|
return _regenerator.default.wrap(function _callee$(_context) {
|
|
103
146
|
while (1) {
|
|
104
147
|
switch (_context.prev = _context.next) {
|
|
105
148
|
case 0:
|
|
106
|
-
|
|
107
|
-
|
|
149
|
+
_buildAssistantByIdQu = (0, _queries.buildAssistantByIdQuery)(assistantId), definition = _buildAssistantByIdQu.definition, options = _buildAssistantByIdQu.options;
|
|
150
|
+
_context.next = 3;
|
|
151
|
+
return client.query(definition(), {
|
|
152
|
+
as: options.as
|
|
153
|
+
});
|
|
108
154
|
|
|
109
|
-
case
|
|
155
|
+
case 3:
|
|
110
156
|
_yield$client$query = _context.sent;
|
|
111
157
|
assistant = _yield$client$query.data;
|
|
112
158
|
knowledgeBase = typeof knowledgeBaseOrUpdater === 'function' ? knowledgeBaseOrUpdater(assistant === null || assistant === void 0 ? void 0 : assistant.knowledgeBase) : knowledgeBaseOrUpdater;
|
|
113
|
-
_context.next =
|
|
159
|
+
_context.next = 8;
|
|
114
160
|
return client.save(_objectSpread(_objectSpread({}, assistant), {}, {
|
|
115
|
-
knowledgeBase: knowledgeBase
|
|
161
|
+
knowledgeBase: withRootFolderIfMissing(knowledgeBase)
|
|
116
162
|
}));
|
|
117
163
|
|
|
118
|
-
case
|
|
164
|
+
case 8:
|
|
119
165
|
case "end":
|
|
120
166
|
return _context.stop();
|
|
121
167
|
}
|
|
@@ -5,6 +5,9 @@ import {
|
|
|
5
5
|
withKnowledgeBaseEntry,
|
|
6
6
|
withoutKnowledgeBaseDoctype,
|
|
7
7
|
getKnowledgeBaseDirId,
|
|
8
|
+
ROOT_DIR_ID,
|
|
9
|
+
isRootDirId,
|
|
10
|
+
withRootFolderIfMissing,
|
|
8
11
|
saveKnowledgeBase
|
|
9
12
|
} from './knowledgeBase'
|
|
10
13
|
|
|
@@ -125,6 +128,43 @@ describe('getKnowledgeBaseDirId', () => {
|
|
|
125
128
|
})
|
|
126
129
|
})
|
|
127
130
|
|
|
131
|
+
describe('withRootFolderIfMissing', () => {
|
|
132
|
+
it('appends the root entry when there is no files entry', () => {
|
|
133
|
+
expect(withRootFolderIfMissing([{ doctype: 'io.cozy.email' }])).toEqual([
|
|
134
|
+
{ doctype: 'io.cozy.email' },
|
|
135
|
+
{ doctype: 'io.cozy.files', dirId: ROOT_DIR_ID }
|
|
136
|
+
])
|
|
137
|
+
expect(withRootFolderIfMissing()).toEqual([
|
|
138
|
+
{ doctype: 'io.cozy.files', dirId: ROOT_DIR_ID }
|
|
139
|
+
])
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('returns the same array when a files entry exists', () => {
|
|
143
|
+
const kb = [{ doctype: 'io.cozy.files', dirId: 'folder-1' }]
|
|
144
|
+
expect(withRootFolderIfMissing(kb)).toBe(kb)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('replaces a files entry without dirId, keeping the other entries', () => {
|
|
148
|
+
expect(
|
|
149
|
+
withRootFolderIfMissing([
|
|
150
|
+
{ doctype: 'io.cozy.email' },
|
|
151
|
+
{ doctype: 'io.cozy.files' }
|
|
152
|
+
])
|
|
153
|
+
).toEqual([
|
|
154
|
+
{ doctype: 'io.cozy.email' },
|
|
155
|
+
{ doctype: 'io.cozy.files', dirId: ROOT_DIR_ID }
|
|
156
|
+
])
|
|
157
|
+
})
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
describe('isRootDirId', () => {
|
|
161
|
+
it('recognizes the root folder id only', () => {
|
|
162
|
+
expect(isRootDirId(ROOT_DIR_ID)).toBe(true)
|
|
163
|
+
expect(isRootDirId('folder-1')).toBe(false)
|
|
164
|
+
expect(isRootDirId(null)).toBe(false)
|
|
165
|
+
})
|
|
166
|
+
})
|
|
167
|
+
|
|
128
168
|
describe('saveKnowledgeBase', () => {
|
|
129
169
|
it('refetches the assistant and saves it with the new knowledgeBase', async () => {
|
|
130
170
|
const assistantDoc = {
|
|
@@ -174,4 +214,56 @@ describe('saveKnowledgeBase', () => {
|
|
|
174
214
|
]
|
|
175
215
|
})
|
|
176
216
|
})
|
|
217
|
+
|
|
218
|
+
it('saves the root entry when the knowledge base has no folder', async () => {
|
|
219
|
+
const assistantDoc = {
|
|
220
|
+
_id: 'assistant-1',
|
|
221
|
+
_type: 'io.cozy.ai.chat.assistants'
|
|
222
|
+
}
|
|
223
|
+
const client = {
|
|
224
|
+
query: jest.fn().mockResolvedValue({ data: assistantDoc }),
|
|
225
|
+
save: jest.fn().mockResolvedValue({ data: assistantDoc })
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
await saveKnowledgeBase(client, 'assistant-1', [
|
|
229
|
+
{ doctype: 'io.cozy.email' }
|
|
230
|
+
])
|
|
231
|
+
|
|
232
|
+
expect(client.save).toHaveBeenCalledWith({
|
|
233
|
+
...assistantDoc,
|
|
234
|
+
knowledgeBase: [
|
|
235
|
+
{ doctype: 'io.cozy.email' },
|
|
236
|
+
{ doctype: 'io.cozy.files', dirId: ROOT_DIR_ID }
|
|
237
|
+
]
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
it('applies the root invariant to the updater result too', async () => {
|
|
242
|
+
const assistantDoc = {
|
|
243
|
+
_id: 'assistant-1',
|
|
244
|
+
_type: 'io.cozy.ai.chat.assistants',
|
|
245
|
+
knowledgeBase: [{ doctype: 'io.cozy.files', dirId: 'folder-1' }]
|
|
246
|
+
}
|
|
247
|
+
const client = {
|
|
248
|
+
query: jest.fn().mockResolvedValue({ data: assistantDoc }),
|
|
249
|
+
save: jest.fn().mockResolvedValue({ data: assistantDoc })
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
await saveKnowledgeBase(client, 'assistant-1', () => [])
|
|
253
|
+
|
|
254
|
+
expect(client.save).toHaveBeenCalledWith({
|
|
255
|
+
...assistantDoc,
|
|
256
|
+
knowledgeBase: [{ doctype: 'io.cozy.files', dirId: ROOT_DIR_ID }]
|
|
257
|
+
})
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
it('propagates a save failure', async () => {
|
|
261
|
+
const client = {
|
|
262
|
+
query: jest.fn().mockResolvedValue({ data: { _id: 'assistant-1' } }),
|
|
263
|
+
save: jest.fn().mockRejectedValue(new Error('boom'))
|
|
264
|
+
}
|
|
265
|
+
await expect(saveKnowledgeBase(client, 'assistant-1', [])).rejects.toThrow(
|
|
266
|
+
'boom'
|
|
267
|
+
)
|
|
268
|
+
})
|
|
177
269
|
})
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export const AUTOPROVISION_FLAG: "rag.assistants.autoprovision";
|
|
2
|
+
export function assistantIdFromName(name: any): string;
|
|
3
|
+
export function getDefaultProvisionedAssistantId(): string | null;
|
|
4
|
+
export function isMagicFolderId(dirId: any): boolean;
|
|
5
|
+
export function resolveProvisionedFolder(client: any, { dirId, dirName }: {
|
|
6
|
+
dirId: any;
|
|
7
|
+
dirName: any;
|
|
8
|
+
}): Promise<any>;
|
|
9
|
+
export function createOpenragAccount(client: any): Promise<any>;
|
|
10
|
+
export function ensureProvisionedAssistants(client: import("cozy-client").CozyClient, configs: Array<{
|
|
11
|
+
name: string;
|
|
12
|
+
dirName?: string;
|
|
13
|
+
dirId?: string;
|
|
14
|
+
prompt?: string;
|
|
15
|
+
icon?: string | null;
|
|
16
|
+
default?: boolean;
|
|
17
|
+
}>): Promise<{
|
|
18
|
+
created: string[];
|
|
19
|
+
ensured: string[];
|
|
20
|
+
skipped: {
|
|
21
|
+
id: string;
|
|
22
|
+
reason: string;
|
|
23
|
+
}[];
|
|
24
|
+
}>;
|