@startsimpli/integrations 0.1.5 → 0.2.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/package.json +2 -2
- package/src/__tests__/corpus-copy.test.tsx +196 -0
- package/src/__tests__/corpus-extract.test.ts +537 -0
- package/src/__tests__/corpus-storage.test.ts +301 -0
- package/src/__tests__/corpus-tracker.test.tsx +250 -0
- package/src/components/Markdown.tsx +193 -0
- package/src/components/index.ts +2 -0
- package/src/components/pages/AskPage.tsx +33 -8
- package/src/components/pages/LibraryPage.tsx +10 -2
- package/src/components/pages/TrackerPage.tsx +437 -0
- package/src/corpus/client.ts +206 -18
- package/src/corpus/commitments.ts +416 -0
- package/src/corpus/entities.ts +288 -5
- package/src/corpus/extract.ts +438 -0
- package/src/corpus/fixtures.ts +602 -186
- package/src/corpus/index.ts +57 -1
- package/src/corpus/llm.ts +134 -2
- package/src/corpus/text.ts +283 -0
- package/src/corpus/types.ts +256 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@startsimpli/integrations",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Provider-agnostic types, API clients, hooks, and UI primitives for email + calendar integrations across StartSimpli apps.",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"react-dom": "^19.0.0",
|
|
49
49
|
"typescript": "^6.0.0",
|
|
50
50
|
"vitest": "^3.0.0",
|
|
51
|
-
"@startsimpli/llm": "0.4.
|
|
51
|
+
"@startsimpli/llm": "0.4.15"
|
|
52
52
|
},
|
|
53
53
|
"scripts": {
|
|
54
54
|
"type-check": "tsc --noEmit",
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the corpus surfaces SAY, as opposed to what they fetch.
|
|
3
|
+
*
|
|
4
|
+
* Three defects the pre-demo rehearsal (bd startsim-768w.38.10) put on a
|
|
5
|
+
* screenshot, all of them the kind that survives a green test suite because no
|
|
6
|
+
* assertion ever read the sentence:
|
|
7
|
+
*
|
|
8
|
+
* • Markdown rendered literally — `## Decisions` and `**$4,800**` as text
|
|
9
|
+
* (bd startsim-768w.38.10, MINOR);
|
|
10
|
+
* • `Answered by a none stub ((provider default))` — not English, and
|
|
11
|
+
* `(provider default)` where the model's name belongs
|
|
12
|
+
* (bd startsim-768w.38.21);
|
|
13
|
+
* • `1 more record were searched`.
|
|
14
|
+
*/
|
|
15
|
+
import { describe, expect, it } from 'vitest';
|
|
16
|
+
import { render, screen, within } from '@testing-library/react';
|
|
17
|
+
import userEvent from '@testing-library/user-event';
|
|
18
|
+
|
|
19
|
+
import { Markdown, stripMarkdown } from '../components/Markdown';
|
|
20
|
+
import { AskPage } from '../components/pages/AskPage';
|
|
21
|
+
import { LibraryPage } from '../components/pages/LibraryPage';
|
|
22
|
+
import { configuredModel } from '../corpus/llm';
|
|
23
|
+
import { createFixtureCorpusApi, FIXTURE_DOCUMENTS, FIXTURE_ORG } from '../corpus/fixtures';
|
|
24
|
+
import type { AskResult, CorpusAskApi, SourceDocument } from '../corpus/types';
|
|
25
|
+
|
|
26
|
+
const SCOPE = { orgId: FIXTURE_ORG };
|
|
27
|
+
|
|
28
|
+
const MARKDOWN_BODY = [
|
|
29
|
+
'## Decisions',
|
|
30
|
+
'- Globex pricing agreed at **$4,800 per month**, 20 seats included.',
|
|
31
|
+
'- Additional seats are `$95` each per month.',
|
|
32
|
+
'',
|
|
33
|
+
'See the [rate card](https://docs.example/rate-card) for the rest.',
|
|
34
|
+
].join('\n');
|
|
35
|
+
|
|
36
|
+
function resultWith(overrides: Partial<AskResult>): AskResult {
|
|
37
|
+
const document = FIXTURE_DOCUMENTS[0];
|
|
38
|
+
return {
|
|
39
|
+
status: 'answered',
|
|
40
|
+
answer: MARKDOWN_BODY,
|
|
41
|
+
citations: [{ documentId: document.id, document, quote: '**SAML SSO has not shipped**' }],
|
|
42
|
+
considered: [{ document, score: 1, matchedTerms: ['globex'], snippet: '' }],
|
|
43
|
+
unresolvedCitations: [],
|
|
44
|
+
droppedOutOfScope: 0,
|
|
45
|
+
provider: { provider: 'openai', model: 'gpt-4o-mini', live: true },
|
|
46
|
+
...overrides,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function askApi(result: AskResult): CorpusAskApi {
|
|
51
|
+
return { ask: async () => result };
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function ask(result: AskResult) {
|
|
55
|
+
const user = userEvent.setup();
|
|
56
|
+
render(<AskPage api={askApi(result)} scope={SCOPE} />);
|
|
57
|
+
await user.type(screen.getByRole('textbox'), 'What did we agree?');
|
|
58
|
+
await user.click(screen.getByRole('button', { name: 'Ask' }));
|
|
59
|
+
return screen.findByRole('region', { name: 'Answer' });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
describe('Markdown is rendered, not printed', () => {
|
|
65
|
+
it('turns a heading into a heading rather than two hashes', () => {
|
|
66
|
+
render(<Markdown text={MARKDOWN_BODY} />);
|
|
67
|
+
expect(screen.getByText('Decisions')).toBeInTheDocument();
|
|
68
|
+
expect(screen.queryByText(/##/)).not.toBeInTheDocument();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('emphasises an amount instead of wrapping it in asterisks', () => {
|
|
72
|
+
render(<Markdown text={MARKDOWN_BODY} />);
|
|
73
|
+
const strong = screen.getByText('$4,800 per month');
|
|
74
|
+
expect(strong.tagName).toBe('STRONG');
|
|
75
|
+
expect(screen.queryByText(/\*\*/)).not.toBeInTheDocument();
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it('makes a list a list, and a link a link', () => {
|
|
79
|
+
render(<Markdown text={MARKDOWN_BODY} />);
|
|
80
|
+
expect(screen.getAllByRole('listitem')).toHaveLength(2);
|
|
81
|
+
expect(screen.getByRole('link', { name: 'rate card' })).toHaveAttribute(
|
|
82
|
+
'href',
|
|
83
|
+
'https://docs.example/rate-card',
|
|
84
|
+
);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('keeps the label but drops the link for a scheme it does not trust', () => {
|
|
88
|
+
render(<Markdown text={'[click me](javascript:doSomething)'} />);
|
|
89
|
+
expect(screen.getByText('click me')).toBeInTheDocument();
|
|
90
|
+
expect(screen.queryByRole('link')).not.toBeInTheDocument();
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('leaves prose containing a lone asterisk exactly as written', () => {
|
|
94
|
+
// A false positive here silently edits somebody's contract.
|
|
95
|
+
render(<Markdown text={'Fees are 2.5% * the invoice total.'} />);
|
|
96
|
+
expect(screen.getByText('Fees are 2.5% * the invoice total.')).toBeInTheDocument();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('flattens to plain text when a string is what is needed', () => {
|
|
100
|
+
expect(stripMarkdown('## Blocker\n- **SAML SSO** has not shipped')).toBe(
|
|
101
|
+
'Blocker\nSAML SSO has not shipped',
|
|
102
|
+
);
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
describe('LibraryPage renders a document’s own Markdown', () => {
|
|
107
|
+
it('shows the summary as prose, not as source', async () => {
|
|
108
|
+
const user = userEvent.setup();
|
|
109
|
+
const document: SourceDocument = { ...FIXTURE_DOCUMENTS[0], body: MARKDOWN_BODY };
|
|
110
|
+
const api = createFixtureCorpusApi({ scope: SCOPE, documents: [document], moneyEvents: [] });
|
|
111
|
+
render(<LibraryPage api={api} scope={SCOPE} />);
|
|
112
|
+
|
|
113
|
+
await user.click(await screen.findByText(document.title));
|
|
114
|
+
const detail = await screen.findByRole('region', { name: 'Record detail' });
|
|
115
|
+
expect(within(detail).getByText('Decisions')).toBeInTheDocument();
|
|
116
|
+
expect(within(detail).getByText('$4,800 per month').tagName).toBe('STRONG');
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
describe('AskPage says who answered, in English', () => {
|
|
121
|
+
it('names the model when one answered', async () => {
|
|
122
|
+
const panel = await ask(resultWith({}));
|
|
123
|
+
expect(within(panel).getByText('Answered by openai · gpt-4o-mini')).toBeInTheDocument();
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it('says no model is configured rather than "a none stub ((provider default))"', async () => {
|
|
127
|
+
const panel = await ask(
|
|
128
|
+
resultWith({ provider: { provider: 'none', model: '', live: false } }),
|
|
129
|
+
);
|
|
130
|
+
expect(within(panel).getByText(/No model is configured for this workspace/)).toBeInTheDocument();
|
|
131
|
+
// The honesty is kept: it still says nothing live ran.
|
|
132
|
+
expect(within(panel).getByText(/no live model was called/)).toBeInTheDocument();
|
|
133
|
+
expect(within(panel).queryByText(/\(\(/)).not.toBeInTheDocument();
|
|
134
|
+
expect(within(panel).queryByText(/none stub/)).not.toBeInTheDocument();
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('still calls a stub a stub', async () => {
|
|
138
|
+
const panel = await ask(
|
|
139
|
+
resultWith({ provider: { provider: 'fixture', model: 'fixture-echo', live: false } }),
|
|
140
|
+
);
|
|
141
|
+
expect(
|
|
142
|
+
within(panel).getByText('Answered by a fixture stub (fixture-echo) — no live model was called'),
|
|
143
|
+
).toBeInTheDocument();
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('renders the answer’s Markdown and flattens the citation quote', async () => {
|
|
147
|
+
const panel = await ask(resultWith({}));
|
|
148
|
+
expect(within(panel).getByText('Decisions')).toBeInTheDocument();
|
|
149
|
+
expect(within(panel).getByText('“SAML SSO has not shipped”')).toBeInTheDocument();
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('agrees in number about how many records went uncited', async () => {
|
|
153
|
+
const document = FIXTURE_DOCUMENTS[1];
|
|
154
|
+
const panel = await ask(
|
|
155
|
+
resultWith({
|
|
156
|
+
considered: [
|
|
157
|
+
{ document: FIXTURE_DOCUMENTS[0], score: 1, matchedTerms: [], snippet: '' },
|
|
158
|
+
{ document, score: 0.5, matchedTerms: [], snippet: '' },
|
|
159
|
+
],
|
|
160
|
+
}),
|
|
161
|
+
);
|
|
162
|
+
expect(within(panel).getByText('1 more record was searched but not cited')).toBeInTheDocument();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
it('uses the plural when more than one was passed over', async () => {
|
|
166
|
+
const panel = await ask(
|
|
167
|
+
resultWith({
|
|
168
|
+
considered: [
|
|
169
|
+
{ document: FIXTURE_DOCUMENTS[0], score: 1, matchedTerms: [], snippet: '' },
|
|
170
|
+
{ document: FIXTURE_DOCUMENTS[1], score: 0.5, matchedTerms: [], snippet: '' },
|
|
171
|
+
{ document: FIXTURE_DOCUMENTS[2], score: 0.4, matchedTerms: [], snippet: '' },
|
|
172
|
+
],
|
|
173
|
+
}),
|
|
174
|
+
);
|
|
175
|
+
expect(within(panel).getByText('2 more records were searched but not cited')).toBeInTheDocument();
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe('the model named before the first call is the CONFIGURED one', () => {
|
|
180
|
+
it('reads the provider’s own variable', () => {
|
|
181
|
+
expect(configuredModel('openai', { OPENAI_MODEL: 'gpt-4o-mini' })).toBe('gpt-4o-mini');
|
|
182
|
+
expect(configuredModel('anthropic', { ANTHROPIC_MODEL: 'claude-x' })).toBe('claude-x');
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it('prefers the specific variable over the generic one', () => {
|
|
186
|
+
expect(configuredModel('openai', { OPENAI_MODEL: 'gpt-4o-mini', LLM_MODEL: 'other' })).toBe(
|
|
187
|
+
'gpt-4o-mini',
|
|
188
|
+
);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('falls back to LLM_MODEL, and to nothing at all', () => {
|
|
192
|
+
expect(configuredModel('openai', { LLM_MODEL: 'gpt-5' })).toBe('gpt-5');
|
|
193
|
+
expect(configuredModel('openai', {})).toBe('');
|
|
194
|
+
expect(configuredModel('none', { OPENAI_MODEL: 'gpt-4o-mini' })).toBe('');
|
|
195
|
+
});
|
|
196
|
+
});
|