@truedat/ai 8.5.6 → 8.5.8
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/package.json +3 -3
- package/src/components/ConceptSuggestions.js +128 -0
- package/src/components/SuggestLinkButton.js +61 -0
- package/src/components/__tests__/ConceptSuggestions.spec.js +268 -0
- package/src/components/__tests__/SuggestLinkButton.spec.js +40 -0
- package/src/components/actions/Actions.js +12 -21
- package/src/components/actions/__tests__/__snapshots__/Action.spec.js.snap +5 -1
- package/src/components/actions/__tests__/__snapshots__/ActionActions.spec.js.snap +5 -1
- package/src/components/actions/__tests__/__snapshots__/ActionEdit.spec.js.snap +1 -1
- package/src/components/actions/__tests__/__snapshots__/ActionForm.spec.js.snap +2 -2
- package/src/components/actions/__tests__/__snapshots__/ActionNew.spec.js.snap +1 -1
- package/src/components/actions/__tests__/__snapshots__/Actions.spec.js.snap +10 -22
- package/src/components/assistant/__tests__/AssistantConversations.spec.js +15 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@truedat/ai",
|
|
3
|
-
"version": "8.5.
|
|
3
|
+
"version": "8.5.8",
|
|
4
4
|
"description": "Truedat Web Artificial Intelligence package",
|
|
5
5
|
"sideEffects": false,
|
|
6
6
|
"module": "src/index.js",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"@testing-library/jest-dom": "^6.6.3",
|
|
51
51
|
"@testing-library/react": "^16.3.0",
|
|
52
52
|
"@testing-library/user-event": "^14.6.1",
|
|
53
|
-
"@truedat/test": "8.5.
|
|
53
|
+
"@truedat/test": "8.5.8",
|
|
54
54
|
"identity-obj-proxy": "^3.0.0",
|
|
55
55
|
"jest": "^29.7.0",
|
|
56
56
|
"redux-saga-test-plan": "^4.0.6"
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"semantic-ui-react": "^3.0.0-beta.2",
|
|
81
81
|
"swr": "^2.3.3"
|
|
82
82
|
},
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "61ad9443b3d822d30dcfa977f5fad3494b3ed5b4"
|
|
84
84
|
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { useEffect, useState, useCallback } from "react";
|
|
2
|
+
import { useParams, useLocation } from "react-router";
|
|
3
|
+
import PropTypes from "prop-types";
|
|
4
|
+
import { FormattedMessage } from "react-intl";
|
|
5
|
+
import { Icon, Loader, Message } from "semantic-ui-react";
|
|
6
|
+
import { ConceptSelectorTable } from "@truedat/bg/concepts/relations/components/ConceptSelector";
|
|
7
|
+
import useAssistantSocket from "./assistant/hooks/useAssistantSocket";
|
|
8
|
+
import ThinkingOutLoud from "./ThinkingOutLoud";
|
|
9
|
+
import { useAgentLayerRun } from "../hooks/useAgentLayerRun";
|
|
10
|
+
import {
|
|
11
|
+
reasonColumnDefinition,
|
|
12
|
+
similarityColumnDefinition,
|
|
13
|
+
} from "./structureSuggestionColumns";
|
|
14
|
+
|
|
15
|
+
const extraColumns = [similarityColumnDefinition, reasonColumnDefinition];
|
|
16
|
+
|
|
17
|
+
export const ConceptSuggestions = ({ selectedConcept, handleConceptSelected }) => {
|
|
18
|
+
const { id } = useParams();
|
|
19
|
+
const { state } = useLocation();
|
|
20
|
+
const prompt = state?.prompt;
|
|
21
|
+
|
|
22
|
+
const { trigger: fetchSuggestions, data: runData } = useAgentLayerRun();
|
|
23
|
+
|
|
24
|
+
const [logItems, setLogItems] = useState([]);
|
|
25
|
+
const [concepts, setConcepts] = useState(null);
|
|
26
|
+
const [hasError, setHasError] = useState(false);
|
|
27
|
+
|
|
28
|
+
const agentStateId = runData?.data?.data?.agent_state_id
|
|
29
|
+
? String(runData.data.data.agent_state_id)
|
|
30
|
+
: null;
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
if (id) {
|
|
34
|
+
fetchSuggestions({
|
|
35
|
+
workflow: "suggest_structure_concept_links",
|
|
36
|
+
params: { structure_id: id, prompt },
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}, [id, prompt]);
|
|
40
|
+
|
|
41
|
+
const handleServerMessage = useCallback((event, payload) => {
|
|
42
|
+
const priority = payload?.priority ?? null;
|
|
43
|
+
const source = payload?.source ?? null;
|
|
44
|
+
|
|
45
|
+
if (event === "stream" && Number(priority) === 0) {
|
|
46
|
+
const content = payload?.content;
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
const raw = typeof content === "string" ? JSON.parse(content) : content;
|
|
50
|
+
parsed = Array.isArray(raw) ? raw : (raw?.result ?? []);
|
|
51
|
+
} catch {
|
|
52
|
+
parsed = [];
|
|
53
|
+
}
|
|
54
|
+
setConcepts(parsed);
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (event === "error" && Number(priority) !== 2) {
|
|
59
|
+
setHasError(true);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (event === "log" || (event === "error" && Number(priority) === 2)) {
|
|
64
|
+
const content =
|
|
65
|
+
event === "log"
|
|
66
|
+
? (() => {
|
|
67
|
+
const log = payload?.content;
|
|
68
|
+
return log && typeof log === "object"
|
|
69
|
+
? log.message ?? log.level ?? JSON.stringify(log)
|
|
70
|
+
: String(log ?? "");
|
|
71
|
+
})()
|
|
72
|
+
: (() => {
|
|
73
|
+
const err = payload?.error;
|
|
74
|
+
return err && typeof err === "object"
|
|
75
|
+
? err.message ?? err.reason ?? JSON.stringify(err)
|
|
76
|
+
: String(err ?? "");
|
|
77
|
+
})();
|
|
78
|
+
|
|
79
|
+
setLogItems((prev) => [
|
|
80
|
+
...prev,
|
|
81
|
+
{ eventType: event, content: content || " ", priority, source },
|
|
82
|
+
]);
|
|
83
|
+
}
|
|
84
|
+
}, []);
|
|
85
|
+
|
|
86
|
+
useAssistantSocket(!agentStateId, {
|
|
87
|
+
conversationId: agentStateId,
|
|
88
|
+
onServerMessage: handleServerMessage,
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
if (hasError) {
|
|
92
|
+
return (
|
|
93
|
+
<Message icon negative>
|
|
94
|
+
<Icon name="warning circle" />
|
|
95
|
+
<Message.Content>
|
|
96
|
+
<Message.Header>
|
|
97
|
+
<FormattedMessage id="concepts.suggestions.error.header" />
|
|
98
|
+
</Message.Header>
|
|
99
|
+
<FormattedMessage id="concepts.suggestions.error.body" />
|
|
100
|
+
</Message.Content>
|
|
101
|
+
</Message>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (concepts !== null) {
|
|
106
|
+
return (
|
|
107
|
+
<ConceptSelectorTable
|
|
108
|
+
concepts={concepts}
|
|
109
|
+
selectedConcept={selectedConcept}
|
|
110
|
+
handleConceptSelected={handleConceptSelected}
|
|
111
|
+
extraColumns={extraColumns}
|
|
112
|
+
/>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (logItems.length === 0) {
|
|
117
|
+
return <Loader active inline="centered" />;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return <ThinkingOutLoud items={logItems} active={true} />;
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
ConceptSuggestions.propTypes = {
|
|
124
|
+
selectedConcept: PropTypes.object,
|
|
125
|
+
handleConceptSelected: PropTypes.func,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
export default ConceptSuggestions;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { useState } from "react";
|
|
2
|
+
import PropTypes from "prop-types";
|
|
3
|
+
import { Button, Form, Popup } from "semantic-ui-react";
|
|
4
|
+
import { FormattedMessage, useIntl } from "react-intl";
|
|
5
|
+
|
|
6
|
+
export const SuggestLinkButton = ({ onSubmit, floated }) => {
|
|
7
|
+
const { formatMessage } = useIntl();
|
|
8
|
+
const [open, setOpen] = useState(false);
|
|
9
|
+
const [prompt, setPrompt] = useState("");
|
|
10
|
+
|
|
11
|
+
const handleSubmit = () => {
|
|
12
|
+
setOpen(false);
|
|
13
|
+
onSubmit(prompt);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<Popup
|
|
18
|
+
on="click"
|
|
19
|
+
open={open}
|
|
20
|
+
onOpen={() => setOpen(true)}
|
|
21
|
+
onClose={() => setOpen(false)}
|
|
22
|
+
content={
|
|
23
|
+
<Form style={{ minWidth: "300px" }}>
|
|
24
|
+
<Form.Field>
|
|
25
|
+
<label>{formatMessage({ id: "links.suggest.prompt.label" })}</label>
|
|
26
|
+
<Form.Input
|
|
27
|
+
value={prompt}
|
|
28
|
+
onChange={(_, { value }) => setPrompt(value)}
|
|
29
|
+
placeholder={formatMessage({
|
|
30
|
+
id: "links.suggest.prompt.placeholder",
|
|
31
|
+
})}
|
|
32
|
+
/>
|
|
33
|
+
</Form.Field>
|
|
34
|
+
<Button
|
|
35
|
+
primary
|
|
36
|
+
content={formatMessage({ id: "links.suggest.submit" })}
|
|
37
|
+
onClick={handleSubmit}
|
|
38
|
+
/>
|
|
39
|
+
</Form>
|
|
40
|
+
}
|
|
41
|
+
position="bottom center"
|
|
42
|
+
trigger={
|
|
43
|
+
<Button
|
|
44
|
+
className="td-icon-text-control"
|
|
45
|
+
secondary
|
|
46
|
+
active={open}
|
|
47
|
+
floated={floated}
|
|
48
|
+
icon="lightbulb outline"
|
|
49
|
+
content={<FormattedMessage id="links.actions.suggest" />}
|
|
50
|
+
/>
|
|
51
|
+
}
|
|
52
|
+
/>
|
|
53
|
+
);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
SuggestLinkButton.propTypes = {
|
|
57
|
+
onSubmit: PropTypes.func.isRequired,
|
|
58
|
+
floated: PropTypes.string,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export default SuggestLinkButton;
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
import { act, waitFor } from "@testing-library/react";
|
|
2
|
+
import { useParams, useLocation } from "react-router";
|
|
3
|
+
import { render } from "@truedat/test/render";
|
|
4
|
+
import { ConceptSelectorTable } from "@truedat/bg/concepts/relations/components/ConceptSelector";
|
|
5
|
+
import useAssistantSocket from "../assistant/hooks/useAssistantSocket";
|
|
6
|
+
import ThinkingOutLoud from "../ThinkingOutLoud";
|
|
7
|
+
import { useAgentLayerRun } from "../../hooks/useAgentLayerRun";
|
|
8
|
+
import ConceptSuggestions from "../ConceptSuggestions";
|
|
9
|
+
|
|
10
|
+
jest.mock("react-router", () => ({
|
|
11
|
+
...jest.requireActual("react-router"),
|
|
12
|
+
useParams: jest.fn(),
|
|
13
|
+
useLocation: jest.fn(),
|
|
14
|
+
}));
|
|
15
|
+
|
|
16
|
+
jest.mock("@truedat/bg/concepts/relations/components/ConceptSelector", () => ({
|
|
17
|
+
ConceptSelectorTable: jest.fn(() => <div>MockConceptSelectorTable</div>),
|
|
18
|
+
}));
|
|
19
|
+
|
|
20
|
+
jest.mock("../ThinkingOutLoud", () => ({
|
|
21
|
+
__esModule: true,
|
|
22
|
+
default: jest.fn(() => <div>MockThinkingOutLoud</div>),
|
|
23
|
+
getSourceLabel: jest.fn((s) => s),
|
|
24
|
+
}));
|
|
25
|
+
|
|
26
|
+
jest.mock("../assistant/hooks/useAssistantSocket", () => jest.fn());
|
|
27
|
+
|
|
28
|
+
jest.mock("../../hooks/useAgentLayerRun", () => ({
|
|
29
|
+
useAgentLayerRun: jest.fn(),
|
|
30
|
+
}));
|
|
31
|
+
|
|
32
|
+
describe("ConceptSuggestions", () => {
|
|
33
|
+
const mockFetchSuggestions = jest.fn();
|
|
34
|
+
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
jest.clearAllMocks();
|
|
37
|
+
|
|
38
|
+
useParams.mockReturnValue({ id: "42" });
|
|
39
|
+
useLocation.mockReturnValue({ state: { prompt: "find concepts" } });
|
|
40
|
+
useAssistantSocket.mockImplementation(() => {});
|
|
41
|
+
|
|
42
|
+
useAgentLayerRun.mockReturnValue({
|
|
43
|
+
trigger: mockFetchSuggestions,
|
|
44
|
+
data: null,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("calls fetchSuggestions on mount with structure_id and prompt", async () => {
|
|
49
|
+
render(
|
|
50
|
+
<ConceptSuggestions
|
|
51
|
+
selectedConcept={null}
|
|
52
|
+
handleConceptSelected={jest.fn()}
|
|
53
|
+
/>
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
await waitFor(() =>
|
|
57
|
+
expect(mockFetchSuggestions).toHaveBeenCalledWith({
|
|
58
|
+
workflow: "suggest_structure_concept_links",
|
|
59
|
+
params: { structure_id: "42", prompt: "find concepts" },
|
|
60
|
+
})
|
|
61
|
+
);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("calls fetchSuggestions without prompt when prompt is missing", async () => {
|
|
65
|
+
useLocation.mockReturnValue({ state: null });
|
|
66
|
+
|
|
67
|
+
render(
|
|
68
|
+
<ConceptSuggestions
|
|
69
|
+
selectedConcept={null}
|
|
70
|
+
handleConceptSelected={jest.fn()}
|
|
71
|
+
/>
|
|
72
|
+
);
|
|
73
|
+
|
|
74
|
+
await waitFor(() =>
|
|
75
|
+
expect(mockFetchSuggestions).toHaveBeenCalledWith({
|
|
76
|
+
workflow: "suggest_structure_concept_links",
|
|
77
|
+
params: { structure_id: "42", prompt: undefined },
|
|
78
|
+
})
|
|
79
|
+
);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("shows spinner while waiting for first log item", () => {
|
|
83
|
+
useAgentLayerRun.mockReturnValue({
|
|
84
|
+
trigger: mockFetchSuggestions,
|
|
85
|
+
data: { data: { data: { agent_state_id: "state-1" } } },
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const rendered = render(
|
|
89
|
+
<ConceptSuggestions
|
|
90
|
+
selectedConcept={null}
|
|
91
|
+
handleConceptSelected={jest.fn()}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
expect(rendered.queryByText(/mockthinkingoutloud/i)).not.toBeInTheDocument();
|
|
96
|
+
expect(rendered.queryByText(/mockconceptselectortable/i)).not.toBeInTheDocument();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("shows ThinkingOutLoud once log items arrive", async () => {
|
|
100
|
+
let capturedOnServerMessage;
|
|
101
|
+
|
|
102
|
+
useAssistantSocket.mockImplementation((_, { onServerMessage }) => {
|
|
103
|
+
capturedOnServerMessage = onServerMessage;
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
useAgentLayerRun.mockReturnValue({
|
|
107
|
+
trigger: mockFetchSuggestions,
|
|
108
|
+
data: { data: { data: { agent_state_id: "state-1" } } },
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const rendered = render(
|
|
112
|
+
<ConceptSuggestions
|
|
113
|
+
selectedConcept={null}
|
|
114
|
+
handleConceptSelected={jest.fn()}
|
|
115
|
+
/>
|
|
116
|
+
);
|
|
117
|
+
|
|
118
|
+
act(() => {
|
|
119
|
+
capturedOnServerMessage("log", {
|
|
120
|
+
priority: 1,
|
|
121
|
+
source: "agent_x",
|
|
122
|
+
content: "Processing...",
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
await waitFor(() =>
|
|
127
|
+
expect(rendered.queryByText(/mockthinkingoutloud/i)).toBeInTheDocument()
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("disables socket when agent_state_id is not yet available", () => {
|
|
132
|
+
render(
|
|
133
|
+
<ConceptSuggestions
|
|
134
|
+
selectedConcept={null}
|
|
135
|
+
handleConceptSelected={jest.fn()}
|
|
136
|
+
/>
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
expect(useAssistantSocket).toHaveBeenCalledWith(
|
|
140
|
+
true,
|
|
141
|
+
expect.objectContaining({ conversationId: null })
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it("connects socket with agent_state_id when available", () => {
|
|
146
|
+
useAgentLayerRun.mockReturnValue({
|
|
147
|
+
trigger: mockFetchSuggestions,
|
|
148
|
+
data: { data: { data: { agent_state_id: "state-1" } } },
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
render(
|
|
152
|
+
<ConceptSuggestions
|
|
153
|
+
selectedConcept={null}
|
|
154
|
+
handleConceptSelected={jest.fn()}
|
|
155
|
+
/>
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
expect(useAssistantSocket).toHaveBeenCalledWith(
|
|
159
|
+
false,
|
|
160
|
+
expect.objectContaining({ conversationId: "state-1" })
|
|
161
|
+
);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
it("shows ConceptSelectorTable when stream priority-0 arrives", async () => {
|
|
165
|
+
const concepts = [{ id: "c1" }, { id: "c2" }];
|
|
166
|
+
let capturedOnServerMessage;
|
|
167
|
+
|
|
168
|
+
useAssistantSocket.mockImplementation((_, { onServerMessage }) => {
|
|
169
|
+
capturedOnServerMessage = onServerMessage;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
useAgentLayerRun.mockReturnValue({
|
|
173
|
+
trigger: mockFetchSuggestions,
|
|
174
|
+
data: { data: { data: { agent_state_id: "state-1" } } },
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
const rendered = render(
|
|
178
|
+
<ConceptSuggestions
|
|
179
|
+
selectedConcept={null}
|
|
180
|
+
handleConceptSelected={jest.fn()}
|
|
181
|
+
/>
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
act(() => {
|
|
185
|
+
capturedOnServerMessage("stream", {
|
|
186
|
+
priority: 0,
|
|
187
|
+
content: JSON.stringify({ result: concepts }),
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
await waitFor(() =>
|
|
192
|
+
expect(rendered.queryByText(/mockconceptselectortable/i)).toBeInTheDocument()
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
expect(rendered.queryByText(/mockthinkingoutloud/i)).not.toBeInTheDocument();
|
|
196
|
+
|
|
197
|
+
const props = ConceptSelectorTable.mock.calls.at(-1)[0];
|
|
198
|
+
expect(props.concepts).toEqual(concepts);
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it("shows error message when error priority-0 arrives", async () => {
|
|
202
|
+
let capturedOnServerMessage;
|
|
203
|
+
|
|
204
|
+
useAssistantSocket.mockImplementation((_, { onServerMessage }) => {
|
|
205
|
+
capturedOnServerMessage = onServerMessage;
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
useAgentLayerRun.mockReturnValue({
|
|
209
|
+
trigger: mockFetchSuggestions,
|
|
210
|
+
data: { data: { data: { agent_state_id: "state-1" } } },
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
const rendered = render(
|
|
214
|
+
<ConceptSuggestions
|
|
215
|
+
selectedConcept={null}
|
|
216
|
+
handleConceptSelected={jest.fn()}
|
|
217
|
+
/>
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
act(() => {
|
|
221
|
+
capturedOnServerMessage("error", {
|
|
222
|
+
priority: 0,
|
|
223
|
+
error: { message: "Something failed" },
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
await waitFor(() =>
|
|
228
|
+
expect(rendered.queryByText(/mockthinkingoutloud/i)).not.toBeInTheDocument()
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
expect(rendered.queryByText(/mockconceptselectortable/i)).not.toBeInTheDocument();
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
it("adds log events to ThinkingOutLoud items", async () => {
|
|
235
|
+
let capturedOnServerMessage;
|
|
236
|
+
|
|
237
|
+
useAssistantSocket.mockImplementation((_, { onServerMessage }) => {
|
|
238
|
+
capturedOnServerMessage = onServerMessage;
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
useAgentLayerRun.mockReturnValue({
|
|
242
|
+
trigger: mockFetchSuggestions,
|
|
243
|
+
data: { data: { data: { agent_state_id: "state-1" } } },
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
render(
|
|
247
|
+
<ConceptSuggestions
|
|
248
|
+
selectedConcept={null}
|
|
249
|
+
handleConceptSelected={jest.fn()}
|
|
250
|
+
/>
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
act(() => {
|
|
254
|
+
capturedOnServerMessage("log", {
|
|
255
|
+
priority: 1,
|
|
256
|
+
source: "agent_x",
|
|
257
|
+
content: "Processing...",
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
await waitFor(() => {
|
|
262
|
+
const lastCall = ThinkingOutLoud.mock.calls.at(-1)[0];
|
|
263
|
+
expect(lastCall.items).toHaveLength(1);
|
|
264
|
+
expect(lastCall.items[0].content).toBe("Processing...");
|
|
265
|
+
expect(lastCall.items[0].eventType).toBe("log");
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { fireEvent, screen, waitFor } from "@testing-library/react";
|
|
2
|
+
import { render } from "@truedat/test/render";
|
|
3
|
+
import { SuggestLinkButton } from "../SuggestLinkButton";
|
|
4
|
+
|
|
5
|
+
jest.mock("react-intl", () => ({
|
|
6
|
+
...jest.requireActual("react-intl"),
|
|
7
|
+
useIntl: () => ({ formatMessage: ({ id }) => id }),
|
|
8
|
+
FormattedMessage: ({ id }) => id,
|
|
9
|
+
}));
|
|
10
|
+
|
|
11
|
+
describe("SuggestLinkButton", () => {
|
|
12
|
+
it("renders the trigger button", () => {
|
|
13
|
+
render(<SuggestLinkButton onSubmit={jest.fn()} />);
|
|
14
|
+
expect(screen.getByText("links.actions.suggest")).toBeInTheDocument();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("calls onSubmit with the prompt value when submitted", async () => {
|
|
18
|
+
const onSubmit = jest.fn();
|
|
19
|
+
render(<SuggestLinkButton onSubmit={onSubmit} />);
|
|
20
|
+
|
|
21
|
+
fireEvent.click(screen.getByText("links.actions.suggest"));
|
|
22
|
+
|
|
23
|
+
const input = screen.getByPlaceholderText("links.suggest.prompt.placeholder");
|
|
24
|
+
fireEvent.change(input, { target: { value: "my prompt" } });
|
|
25
|
+
|
|
26
|
+
fireEvent.click(screen.getByText("links.suggest.submit"));
|
|
27
|
+
|
|
28
|
+
await waitFor(() => expect(onSubmit).toHaveBeenCalledWith("my prompt"));
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("calls onSubmit with empty string when no prompt entered", async () => {
|
|
32
|
+
const onSubmit = jest.fn();
|
|
33
|
+
render(<SuggestLinkButton onSubmit={onSubmit} />);
|
|
34
|
+
|
|
35
|
+
fireEvent.click(screen.getByText("links.actions.suggest"));
|
|
36
|
+
fireEvent.click(screen.getByText("links.suggest.submit"));
|
|
37
|
+
|
|
38
|
+
await waitFor(() => expect(onSubmit).toHaveBeenCalledWith(""));
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -1,11 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
Header,
|
|
3
|
-
Icon,
|
|
4
|
-
Segment,
|
|
5
|
-
Grid,
|
|
6
|
-
Container,
|
|
7
|
-
Button,
|
|
8
|
-
} from "semantic-ui-react";
|
|
1
|
+
import { Header, Icon, Segment, Button } from "semantic-ui-react";
|
|
9
2
|
import { FormattedMessage, useIntl } from "react-intl";
|
|
10
3
|
import { Link } from "react-router";
|
|
11
4
|
import { ACTION_NEW } from "@truedat/core/routes";
|
|
@@ -32,19 +25,17 @@ export function ActionsContent() {
|
|
|
32
25
|
</Header.Subheader>
|
|
33
26
|
</Header.Content>
|
|
34
27
|
</Header>
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
</Grid.Column>
|
|
47
|
-
</Grid>
|
|
28
|
+
|
|
29
|
+
<Button
|
|
30
|
+
primary
|
|
31
|
+
floated="right"
|
|
32
|
+
as={Link}
|
|
33
|
+
to={ACTION_NEW}
|
|
34
|
+
content={<FormattedMessage id="ai.actions.actions.create" />}
|
|
35
|
+
/>
|
|
36
|
+
|
|
37
|
+
<Segment clearing basic />
|
|
38
|
+
|
|
48
39
|
<ActionsTable />
|
|
49
40
|
</Segment>
|
|
50
41
|
);
|
|
@@ -71,13 +71,17 @@ exports[`<Action /> matches the latest snapshot 1`] = `
|
|
|
71
71
|
aria-hidden="true"
|
|
72
72
|
class="ellipsis vertical icon"
|
|
73
73
|
/>
|
|
74
|
+
<i
|
|
75
|
+
aria-hidden="true"
|
|
76
|
+
class="dropdown icon"
|
|
77
|
+
/>
|
|
74
78
|
<div
|
|
75
79
|
class="left menu transition"
|
|
76
80
|
>
|
|
77
81
|
<a
|
|
78
82
|
aria-checked="false"
|
|
79
83
|
aria-selected="true"
|
|
80
|
-
class="selected item"
|
|
84
|
+
class="selected item td-icon-text-control"
|
|
81
85
|
data-discover="true"
|
|
82
86
|
href="/tasks/3/edit"
|
|
83
87
|
role="option"
|
|
@@ -12,13 +12,17 @@ exports[`<ActionActions /> matches the latest snapshot 1`] = `
|
|
|
12
12
|
aria-hidden="true"
|
|
13
13
|
class="ellipsis vertical icon"
|
|
14
14
|
/>
|
|
15
|
+
<i
|
|
16
|
+
aria-hidden="true"
|
|
17
|
+
class="dropdown icon"
|
|
18
|
+
/>
|
|
15
19
|
<div
|
|
16
20
|
class="left menu transition"
|
|
17
21
|
>
|
|
18
22
|
<a
|
|
19
23
|
aria-checked="false"
|
|
20
24
|
aria-selected="true"
|
|
21
|
-
class="selected item"
|
|
25
|
+
class="selected item td-icon-text-control"
|
|
22
26
|
data-discover="true"
|
|
23
27
|
href="/tasks/3/edit"
|
|
24
28
|
role="option"
|
|
@@ -235,7 +235,7 @@ exports[`<ActionEdit /> matches the latest snapshot 1`] = `
|
|
|
235
235
|
class="ai-action-edit-form-actions actions"
|
|
236
236
|
>
|
|
237
237
|
<a
|
|
238
|
-
class="ui secondary button"
|
|
238
|
+
class="ui secondary button td-icon-text-control"
|
|
239
239
|
data-discover="true"
|
|
240
240
|
href="/"
|
|
241
241
|
role="button"
|
|
@@ -191,7 +191,7 @@ exports[`<ActionForm /> matches the latest snapshot when is creating 1`] = `
|
|
|
191
191
|
class="ai-action-edit-form-actions actions"
|
|
192
192
|
>
|
|
193
193
|
<a
|
|
194
|
-
class="ui secondary button"
|
|
194
|
+
class="ui secondary button td-icon-text-control"
|
|
195
195
|
data-discover="true"
|
|
196
196
|
href="/"
|
|
197
197
|
role="button"
|
|
@@ -386,7 +386,7 @@ exports[`<ActionForm /> matches the latest snapshot when is editing 1`] = `
|
|
|
386
386
|
class="ai-action-edit-form-actions actions"
|
|
387
387
|
>
|
|
388
388
|
<a
|
|
389
|
-
class="ui secondary button"
|
|
389
|
+
class="ui secondary button td-icon-text-control"
|
|
390
390
|
data-discover="true"
|
|
391
391
|
href="/"
|
|
392
392
|
role="button"
|
|
@@ -250,7 +250,7 @@ exports[`<ActionNew /> matches the latest snapshot 1`] = `
|
|
|
250
250
|
class="ai-action-edit-form-actions actions"
|
|
251
251
|
>
|
|
252
252
|
<a
|
|
253
|
-
class="ui secondary button"
|
|
253
|
+
class="ui secondary button td-icon-text-control"
|
|
254
254
|
data-discover="true"
|
|
255
255
|
href="/"
|
|
256
256
|
role="button"
|
|
@@ -23,29 +23,17 @@ exports[`<Actions /> matches the latest snapshot 1`] = `
|
|
|
23
23
|
</div>
|
|
24
24
|
</div>
|
|
25
25
|
</h2>
|
|
26
|
-
<
|
|
27
|
-
class="ui
|
|
26
|
+
<a
|
|
27
|
+
class="ui primary right floated button"
|
|
28
|
+
data-discover="true"
|
|
29
|
+
href="/tasks/new"
|
|
30
|
+
role="button"
|
|
28
31
|
>
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
>
|
|
35
|
-
<div
|
|
36
|
-
class="ui right aligned container"
|
|
37
|
-
>
|
|
38
|
-
<a
|
|
39
|
-
class="ui primary button"
|
|
40
|
-
data-discover="true"
|
|
41
|
-
href="/tasks/new"
|
|
42
|
-
role="button"
|
|
43
|
-
>
|
|
44
|
-
ai.actions.actions.create
|
|
45
|
-
</a>
|
|
46
|
-
</div>
|
|
47
|
-
</div>
|
|
48
|
-
</div>
|
|
32
|
+
ai.actions.actions.create
|
|
33
|
+
</a>
|
|
34
|
+
<div
|
|
35
|
+
class="ui basic clearing segment"
|
|
36
|
+
/>
|
|
49
37
|
<h4
|
|
50
38
|
class="ui header"
|
|
51
39
|
>
|
|
@@ -33,6 +33,21 @@ const defaultProps = {
|
|
|
33
33
|
};
|
|
34
34
|
|
|
35
35
|
describe("<AssistantConversations />", () => {
|
|
36
|
+
beforeAll(() => {
|
|
37
|
+
jest.spyOn(Date.prototype, "toLocaleString").mockImplementation(
|
|
38
|
+
function (_, options) {
|
|
39
|
+
return new Intl.DateTimeFormat("en", {
|
|
40
|
+
...options,
|
|
41
|
+
timeZone: "UTC",
|
|
42
|
+
}).format(this);
|
|
43
|
+
}
|
|
44
|
+
);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
afterAll(() => {
|
|
48
|
+
jest.restoreAllMocks();
|
|
49
|
+
});
|
|
50
|
+
|
|
36
51
|
beforeEach(() => {
|
|
37
52
|
jest.clearAllMocks();
|
|
38
53
|
});
|