@tangleai/assistant 0.21.1 → 0.24.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/CHANGELOG.md +42 -0
- package/package.json +8 -8
- package/src/actions.d.ts +36 -12
- package/src/actions.js +141 -142
- package/src/component.d.ts +8 -8
- package/src/component.js +95 -76
- package/src/controller.d.ts +1 -6
- package/src/controller.js +282 -286
- package/src/index.d.ts +4 -4
- package/src/index.js +4 -4
- package/src/settings.d.ts +2 -2
- package/src/settings.js +8 -7
- package/src/state.d.ts +16 -16
- package/src/state.js +34 -35
- package/src/viewmodel.d.ts +3 -3
- package/src/viewmodel.js +71 -73
- package/src/views.d.ts +21 -21
- package/src/views.js +194 -196
package/src/settings.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
//@ts-check
|
|
2
1
|
import { PROVIDERS } from '@tangleai/models/providers';
|
|
3
2
|
export const PROVIDER_OPTIONS = Object.entries(PROVIDERS)
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
.map(([value, preset]) => ({ value, label: preset.label, local: preset.local }));
|
|
6
4
|
export function isConfigured(s) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
if (s.model.trim() === '')
|
|
6
|
+
return false;
|
|
7
|
+
if (s.provider === 'custom')
|
|
8
|
+
return s.baseUrl.trim() !== '';
|
|
9
|
+
if (s.provider === 'openrouter')
|
|
10
|
+
return s.apiKey.trim() !== '';
|
|
11
|
+
return true; // local runtimes (Ollama, LM Studio) need no key
|
|
11
12
|
}
|
package/src/state.d.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
export declare const DEFAULT_AI_SETTINGS: {
|
|
2
|
+
provider: string;
|
|
3
|
+
baseUrl: string;
|
|
4
|
+
model: string;
|
|
5
|
+
apiKey: string;
|
|
6
|
+
};
|
|
7
|
+
/** @param [aiSettings] @param [aiChat] */
|
|
8
|
+
export declare function createAssistantState(aiSettings?: any, aiChat?: any): {
|
|
3
9
|
open: boolean;
|
|
4
10
|
settingsOpen: boolean;
|
|
5
11
|
probe: {
|
|
6
12
|
status: string;
|
|
7
|
-
detail:
|
|
8
|
-
models:
|
|
13
|
+
detail: null;
|
|
14
|
+
models: never[];
|
|
9
15
|
};
|
|
10
16
|
reasoningChars: number;
|
|
11
17
|
settings: any;
|
|
@@ -13,20 +19,14 @@ export function createAssistantState(aiSettings?: any, aiChat?: any): {
|
|
|
13
19
|
draft: string;
|
|
14
20
|
pending: string;
|
|
15
21
|
status: string;
|
|
16
|
-
activity:
|
|
17
|
-
error:
|
|
18
|
-
goal:
|
|
22
|
+
activity: null;
|
|
23
|
+
error: null;
|
|
24
|
+
goal: null;
|
|
19
25
|
goalDraft: string;
|
|
20
26
|
memories: number;
|
|
21
|
-
persistence:
|
|
22
|
-
retention:
|
|
27
|
+
persistence: null;
|
|
28
|
+
retention: null;
|
|
23
29
|
archived: number;
|
|
24
30
|
remembering: boolean;
|
|
25
|
-
remembered:
|
|
31
|
+
remembered: null;
|
|
26
32
|
};
|
|
27
|
-
export namespace DEFAULT_AI_SETTINGS {
|
|
28
|
-
let provider: string;
|
|
29
|
-
let baseUrl: string;
|
|
30
|
-
let model: string;
|
|
31
|
-
let apiKey: string;
|
|
32
|
-
}
|
package/src/state.js
CHANGED
|
@@ -1,40 +1,39 @@
|
|
|
1
|
-
//@ts-check
|
|
2
1
|
export const DEFAULT_AI_SETTINGS = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
2
|
+
provider: 'openrouter', // 'openrouter' | 'ollama' | 'lmstudio' | 'custom'
|
|
3
|
+
baseUrl: '', // required for 'custom'; overrides the preset otherwise
|
|
4
|
+
model: '', // e.g. 'qwen/qwen3-4b' or a local model name
|
|
5
|
+
apiKey: '', // bring your own; local runtimes need none
|
|
7
6
|
};
|
|
8
|
-
/** @param
|
|
7
|
+
/** @param [aiSettings] @param [aiChat] */
|
|
9
8
|
export function createAssistantState(aiSettings = null, aiChat = null) {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
9
|
+
return {
|
|
10
|
+
open: false,
|
|
11
|
+
settingsOpen: false,
|
|
12
|
+
// the settings "Test connection" probe: idle | busy | ok | fail,
|
|
13
|
+
// a human detail line, and the model ids a successful probe found
|
|
14
|
+
probe: { status: 'idle', detail: null, models: [] },
|
|
15
|
+
// reasoning characters streamed this turn (thinking models emit
|
|
16
|
+
// reasoning before - or instead of - visible content)
|
|
17
|
+
reasoningChars: 0,
|
|
18
|
+
settings: { ...DEFAULT_AI_SETTINGS, ...(aiSettings ?? {}) },
|
|
19
|
+
// visible transcript: { role, content }; restored from local
|
|
20
|
+
// storage so a page reload keeps the conversation
|
|
21
|
+
messages: Array.isArray(aiChat?.messages) ? structuredClone(aiChat.messages) : [],
|
|
22
|
+
draft: '', // composer text
|
|
23
|
+
pending: '', // the assistant reply currently streaming
|
|
24
|
+
status: 'idle', // 'idle' | 'streaming' | 'error'
|
|
25
|
+
activity: null, // the tool the model is currently calling
|
|
26
|
+
error: null,
|
|
27
|
+
// the @tangleai/context ledger, as the panel shows it. Read from the
|
|
28
|
+
// ledger (not mirrored into it): the objective is durable state and
|
|
29
|
+
// this slice is a view of it, so a reload shows what storage holds
|
|
30
|
+
// rather than what this tab last typed.
|
|
31
|
+
goal: null, // { objective, progress: [ { at, note, evidence } ] }
|
|
32
|
+
goalDraft: '', // the objective composer
|
|
33
|
+
memories: 0, // evidenced facts carried into every turn
|
|
34
|
+
persistence: null, retention: null,
|
|
35
|
+
archived: 0, // dropped rounds sitting in slots, recallable
|
|
36
|
+
remembering: false, // a refinement is in flight
|
|
37
|
+
remembered: null, // what the last refinement did, in one line
|
|
39
38
|
};
|
|
40
39
|
}
|
package/src/viewmodel.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
/** @param
|
|
2
|
-
export function assistantView(ai: any, renderMarkdown: (source: string) => any): {
|
|
1
|
+
/** @param ai @param renderMarkdown */
|
|
2
|
+
export declare function assistantView(ai: any, renderMarkdown: (source: string) => any): {
|
|
3
3
|
open: any;
|
|
4
4
|
status: any;
|
|
5
5
|
activity: any;
|
|
@@ -42,7 +42,7 @@ export function assistantView(ai: any, renderMarkdown: (source: string) => any):
|
|
|
42
42
|
evidence: any;
|
|
43
43
|
}[];
|
|
44
44
|
more: number;
|
|
45
|
-
};
|
|
45
|
+
} | null;
|
|
46
46
|
goalDraft: any;
|
|
47
47
|
memories: any;
|
|
48
48
|
memoryLabel: string;
|
package/src/viewmodel.js
CHANGED
|
@@ -1,77 +1,75 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
/** @param {any} ai @param {(source: string) => any} renderMarkdown */
|
|
1
|
+
import { PROVIDER_OPTIONS, isConfigured } from "./settings.js";
|
|
2
|
+
/** @param ai @param renderMarkdown */
|
|
4
3
|
export function assistantView(ai, renderMarkdown) {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
4
|
+
const s = ai.settings;
|
|
5
|
+
const configured = isConfigured(s);
|
|
6
|
+
return {
|
|
7
|
+
open: ai.open,
|
|
8
|
+
status: ai.status,
|
|
9
|
+
activity: ai.activity,
|
|
10
|
+
error: ai.error,
|
|
11
|
+
draft: ai.draft,
|
|
12
|
+
configured,
|
|
13
|
+
// the settings form shows until the assistant can actually run, or
|
|
14
|
+
// whenever the user opens it explicitly
|
|
15
|
+
showSettings: ai.settingsOpen || !configured,
|
|
16
|
+
settings: {
|
|
17
|
+
provider: s.provider,
|
|
18
|
+
baseUrl: s.baseUrl,
|
|
19
|
+
model: s.model,
|
|
20
|
+
apiKey: s.apiKey,
|
|
21
|
+
needsKey: s.provider === 'openrouter' || s.provider === 'custom',
|
|
22
|
+
probe: {
|
|
23
|
+
status: ai.probe.status,
|
|
24
|
+
detail: ai.probe.detail,
|
|
25
|
+
busy: ai.probe.status === 'busy',
|
|
26
|
+
ok: ai.probe.status === 'ok',
|
|
27
|
+
fail: ai.probe.status === 'fail',
|
|
28
|
+
models: ai.probe.models.map((id) => ({ id })),
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
providers: PROVIDER_OPTIONS.map((p) => ({ ...p, selected: p.value === s.provider })),
|
|
32
|
+
empty: ai.messages.length === 0,
|
|
33
|
+
messages: ai.messages.map((m) => (m.role === 'user'
|
|
34
|
+
? { role: 'user', text: m.content }
|
|
35
|
+
: { role: 'assistant', article: renderMarkdown(m.content) })),
|
|
36
|
+
// the reply currently streaming in (plain text: it changes per token)
|
|
37
|
+
streaming: ai.status === 'streaming',
|
|
38
|
+
pending: ai.pending,
|
|
39
|
+
// the quiet-phase label: reasoning models think before they speak,
|
|
40
|
+
// and watching the thinking grow beats a blind spinner
|
|
41
|
+
thinkingLabel: ai.reasoningChars > 0
|
|
42
|
+
? `Thinking… (${ai.reasoningChars} characters of reasoning)`
|
|
43
|
+
: 'Thinking…',
|
|
44
|
+
// the ledger: a persistent objective, what has been recorded against
|
|
45
|
+
// it, and what a compacted session can still reach. Every number here
|
|
46
|
+
// is derived from the ledger read, so the panel cannot claim a
|
|
47
|
+
// memory the store does not hold.
|
|
48
|
+
goal: ai.goal === null ? null : {
|
|
49
|
+
objective: ai.goal.objective,
|
|
50
|
+
entries: ai.goal.progress.length + (ai.goal.checkpoint?.sources.length ?? 0),
|
|
51
|
+
checkpointLabel: ai.goal.checkpoint ? `${ai.goal.checkpoint.sources.length} earlier progress entries retained with their evidence.` : '',
|
|
52
|
+
// newest first: the last thing that happened is the thing a reader
|
|
53
|
+
// wants, and the whole log would push the conversation off screen
|
|
54
|
+
progress: [...ai.goal.progress].reverse().slice(0, PROGRESS_SHOWN)
|
|
55
|
+
.map((entry) => ({ note: entry.note, evidence: entry.evidence })),
|
|
56
|
+
more: Math.max(0, ai.goal.progress.length - PROGRESS_SHOWN),
|
|
57
|
+
},
|
|
58
|
+
goalDraft: ai.goalDraft,
|
|
59
|
+
memories: ai.memories,
|
|
60
|
+
memoryLabel: `${ai.memories} remembered fact${ai.memories === 1 ? '' : 's'}`,
|
|
61
|
+
persistenceLabel: ai.persistence?.error ? `Storage failed: ${ai.persistence.error}`
|
|
62
|
+
: ai.persistence?.concurrency === 'single-writer' ? 'Use one tab at a time to update remembered state.' : '',
|
|
63
|
+
retentionLabel: ai.retention?.evicted?.length
|
|
64
|
+
? `${ai.retention.evicted.length} earlier archive address(es) evicted under the storage budget.` : '',
|
|
65
|
+
archived: ai.archived,
|
|
66
|
+
// said in full sentences, because "12" beside a chat is not
|
|
67
|
+
// information: a compacted session has to LOOK recoverable
|
|
68
|
+
archivedLabel: `${ai.archived} earlier round${ai.archived === 1 ? '' : 's'} archived —`
|
|
69
|
+
+ ' the assistant can fetch any of them back with recall.',
|
|
70
|
+
remembering: ai.remembering,
|
|
71
|
+
remembered: ai.remembered,
|
|
72
|
+
};
|
|
74
73
|
}
|
|
75
|
-
|
|
76
74
|
/** Progress entries the panel shows before it says "and N more". */
|
|
77
75
|
const PROGRESS_SHOWN = 3;
|
package/src/views.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* text because it changes per token.
|
|
8
8
|
*/
|
|
9
9
|
/** One instance owns its datalist identifier and host copy. */
|
|
10
|
-
export function createAssistantRules(modelListId: any): ({
|
|
10
|
+
export declare function createAssistantRules(modelListId: any): ({
|
|
11
11
|
match: string;
|
|
12
12
|
mode: string;
|
|
13
13
|
body: (string | {
|
|
@@ -149,6 +149,26 @@ export function createAssistantRules(modelListId: any): ({
|
|
|
149
149
|
} | {
|
|
150
150
|
$if: (string | {
|
|
151
151
|
$if: (string | (string | {
|
|
152
|
+
class: string;
|
|
153
|
+
on: {
|
|
154
|
+
submit: {
|
|
155
|
+
action: string;
|
|
156
|
+
preventDefault: boolean;
|
|
157
|
+
};
|
|
158
|
+
};
|
|
159
|
+
} | (string | {
|
|
160
|
+
type: string;
|
|
161
|
+
class: string;
|
|
162
|
+
spellcheck: string;
|
|
163
|
+
placeholder: string;
|
|
164
|
+
value: string;
|
|
165
|
+
on: {
|
|
166
|
+
input: string;
|
|
167
|
+
};
|
|
168
|
+
})[] | (string | {
|
|
169
|
+
type: string;
|
|
170
|
+
class: string;
|
|
171
|
+
})[])[] | (string | {
|
|
152
172
|
class: string;
|
|
153
173
|
$if?: undefined;
|
|
154
174
|
} | (string | {
|
|
@@ -196,26 +216,6 @@ export function createAssistantRules(modelListId: any): ({
|
|
|
196
216
|
class: string;
|
|
197
217
|
})[])[];
|
|
198
218
|
class?: undefined;
|
|
199
|
-
})[])[] | (string | {
|
|
200
|
-
class: string;
|
|
201
|
-
on: {
|
|
202
|
-
submit: {
|
|
203
|
-
action: string;
|
|
204
|
-
preventDefault: boolean;
|
|
205
|
-
};
|
|
206
|
-
};
|
|
207
|
-
} | (string | {
|
|
208
|
-
type: string;
|
|
209
|
-
class: string;
|
|
210
|
-
spellcheck: string;
|
|
211
|
-
placeholder: string;
|
|
212
|
-
value: string;
|
|
213
|
-
on: {
|
|
214
|
-
input: string;
|
|
215
|
-
};
|
|
216
|
-
})[] | (string | {
|
|
217
|
-
type: string;
|
|
218
|
-
class: string;
|
|
219
219
|
})[])[])[];
|
|
220
220
|
})[];
|
|
221
221
|
class?: undefined;
|