@lapis-notes/ai 0.1.0
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 +6 -0
- package/LICENSE.md +8 -0
- package/README.md +20 -0
- package/dist/ai-plugin.d.ts +88 -0
- package/dist/ai-plugin.js +742 -0
- package/dist/bootstrap/build.d.ts +15 -0
- package/dist/bootstrap/build.js +28 -0
- package/dist/bootstrap/folder-instructions.d.ts +4 -0
- package/dist/bootstrap/folder-instructions.js +49 -0
- package/dist/bootstrap/format.d.ts +2 -0
- package/dist/bootstrap/format.js +46 -0
- package/dist/bootstrap/instructions.d.ts +1 -0
- package/dist/bootstrap/instructions.js +8 -0
- package/dist/bootstrap/types.d.ts +36 -0
- package/dist/bootstrap/types.js +3 -0
- package/dist/catalog/ai-catalog-panel.svelte +742 -0
- package/dist/catalog/ai-catalog-panel.svelte.d.ts +13 -0
- package/dist/catalog/ai-catalog-view-type.d.ts +1 -0
- package/dist/catalog/ai-catalog-view-type.js +1 -0
- package/dist/catalog/ai-catalog-view.d.ts +22 -0
- package/dist/catalog/ai-catalog-view.js +48 -0
- package/dist/catalog/filter.d.ts +3 -0
- package/dist/catalog/filter.js +24 -0
- package/dist/catalog/inventory.d.ts +21 -0
- package/dist/catalog/inventory.js +255 -0
- package/dist/catalog/tree.d.ts +12 -0
- package/dist/catalog/tree.js +60 -0
- package/dist/catalog/types.d.ts +44 -0
- package/dist/catalog/types.js +1 -0
- package/dist/chat/ai-approval-card.svelte +54 -0
- package/dist/chat/ai-approval-card.svelte.d.ts +9 -0
- package/dist/chat/ai-chat-panel.svelte +1288 -0
- package/dist/chat/ai-chat-panel.svelte.d.ts +50 -0
- package/dist/chat/ai-inventory-result.css +55 -0
- package/dist/chat/ai-inventory-result.svelte +63 -0
- package/dist/chat/ai-inventory-result.svelte.d.ts +5 -0
- package/dist/chat/ai-question-card.svelte +132 -0
- package/dist/chat/ai-question-card.svelte.d.ts +9 -0
- package/dist/chat/ai-view-bootstrap.d.ts +13 -0
- package/dist/chat/ai-view-bootstrap.js +86 -0
- package/dist/chat/ai-view-panel.svelte +89 -0
- package/dist/chat/ai-view-panel.svelte.d.ts +13 -0
- package/dist/chat/ai-view-type.d.ts +1 -0
- package/dist/chat/ai-view-type.js +1 -0
- package/dist/chat/ai-view.d.ts +60 -0
- package/dist/chat/ai-view.js +121 -0
- package/dist/chat/chat-controller.svelte.d.ts +82 -0
- package/dist/chat/chat-controller.svelte.js +1926 -0
- package/dist/chat/chat-items.d.ts +88 -0
- package/dist/chat/chat-items.js +9 -0
- package/dist/chat/chat-mentions.d.ts +15 -0
- package/dist/chat/chat-mentions.js +77 -0
- package/dist/chat/chat-result-views.d.ts +5 -0
- package/dist/chat/chat-result-views.js +15 -0
- package/dist/chat/chat-session.d.ts +28 -0
- package/dist/chat/chat-session.js +50 -0
- package/dist/chat/chat-status.d.ts +1 -0
- package/dist/chat/chat-status.js +3 -0
- package/dist/chat/chat-time.d.ts +18 -0
- package/dist/chat/chat-time.js +90 -0
- package/dist/chat/chat-tool-display.d.ts +17 -0
- package/dist/chat/chat-tool-display.js +213 -0
- package/dist/chat/chat-tool-identity.d.ts +4 -0
- package/dist/chat/chat-tool-identity.js +42 -0
- package/dist/chat/chat-trace.d.ts +6 -0
- package/dist/chat/chat-trace.js +256 -0
- package/dist/chat/live-runtime-unavailable.d.ts +1 -0
- package/dist/chat/live-runtime-unavailable.js +1 -0
- package/dist/commands/agent.d.ts +8 -0
- package/dist/commands/agent.js +38 -0
- package/dist/commands/catalog.d.ts +18 -0
- package/dist/commands/catalog.js +115 -0
- package/dist/commands/discovery.d.ts +36 -0
- package/dist/commands/discovery.js +151 -0
- package/dist/commands/groups.d.ts +16 -0
- package/dist/commands/groups.js +121 -0
- package/dist/commands/inspect.d.ts +18 -0
- package/dist/commands/inspect.js +29 -0
- package/dist/commands/markdown.d.ts +21 -0
- package/dist/commands/markdown.js +117 -0
- package/dist/commands/parser.d.ts +4 -0
- package/dist/commands/parser.js +24 -0
- package/dist/commands/reserved.d.ts +4 -0
- package/dist/commands/reserved.js +107 -0
- package/dist/commands/router.d.ts +17 -0
- package/dist/commands/router.js +170 -0
- package/dist/commands/seed.d.ts +4 -0
- package/dist/commands/seed.js +8 -0
- package/dist/commands/types.d.ts +54 -0
- package/dist/commands/types.js +1 -0
- package/dist/commands/user-agents-node.d.ts +3 -0
- package/dist/commands/user-agents-node.js +52 -0
- package/dist/commands/user-agents.d.ts +22 -0
- package/dist/commands/user-agents.js +40 -0
- package/dist/conversations/approval-grants.d.ts +20 -0
- package/dist/conversations/approval-grants.js +75 -0
- package/dist/conversations/binding-state.d.ts +6 -0
- package/dist/conversations/binding-state.js +35 -0
- package/dist/conversations/context-handoff.d.ts +25 -0
- package/dist/conversations/context-handoff.js +207 -0
- package/dist/conversations/conversation-index-coordinator.d.ts +18 -0
- package/dist/conversations/conversation-index-coordinator.js +81 -0
- package/dist/conversations/conversation-index.d.ts +16 -0
- package/dist/conversations/conversation-index.js +125 -0
- package/dist/conversations/conversation-locator.d.ts +2 -0
- package/dist/conversations/conversation-locator.js +20 -0
- package/dist/conversations/conversation-palette.d.ts +18 -0
- package/dist/conversations/conversation-palette.js +29 -0
- package/dist/conversations/conversation-repository.d.ts +39 -0
- package/dist/conversations/conversation-repository.js +213 -0
- package/dist/conversations/handoff-summary.d.ts +48 -0
- package/dist/conversations/handoff-summary.js +269 -0
- package/dist/conversations/hashes.d.ts +5 -0
- package/dist/conversations/hashes.js +40 -0
- package/dist/conversations/history-creation-scope.d.ts +12 -0
- package/dist/conversations/history-creation-scope.js +44 -0
- package/dist/conversations/jsonl.d.ts +10 -0
- package/dist/conversations/jsonl.js +41 -0
- package/dist/conversations/memory-transcript-store.d.ts +15 -0
- package/dist/conversations/memory-transcript-store.js +119 -0
- package/dist/conversations/paths.d.ts +14 -0
- package/dist/conversations/paths.js +92 -0
- package/dist/conversations/redaction.d.ts +13 -0
- package/dist/conversations/redaction.js +94 -0
- package/dist/conversations/reveal-scope.d.ts +3 -0
- package/dist/conversations/reveal-scope.js +8 -0
- package/dist/conversations/scope-resolver.d.ts +11 -0
- package/dist/conversations/scope-resolver.js +18 -0
- package/dist/conversations/scope-tree.d.ts +10 -0
- package/dist/conversations/scope-tree.js +71 -0
- package/dist/conversations/transcript-projection.d.ts +9 -0
- package/dist/conversations/transcript-projection.js +409 -0
- package/dist/conversations/transcript-store.d.ts +24 -0
- package/dist/conversations/transcript-store.js +27 -0
- package/dist/conversations/types.d.ts +224 -0
- package/dist/conversations/types.js +12 -0
- package/dist/conversations/validation.d.ts +4 -0
- package/dist/conversations/validation.js +383 -0
- package/dist/conversations/vault-transcript-store.d.ts +22 -0
- package/dist/conversations/vault-transcript-store.js +219 -0
- package/dist/core/event-queue.d.ts +6 -0
- package/dist/core/event-queue.js +39 -0
- package/dist/core/types.d.ts +241 -0
- package/dist/core/types.js +50 -0
- package/dist/history/ai-history-panel.svelte +937 -0
- package/dist/history/ai-history-panel.svelte.d.ts +17 -0
- package/dist/history/ai-history-view-type.d.ts +1 -0
- package/dist/history/ai-history-view-type.js +1 -0
- package/dist/history/ai-history-view.d.ts +26 -0
- package/dist/history/ai-history-view.js +51 -0
- package/dist/history/conversation-history-tree.d.ts +15 -0
- package/dist/history/conversation-history-tree.js +71 -0
- package/dist/host/agent-workspace.d.ts +6 -0
- package/dist/host/agent-workspace.js +16 -0
- package/dist/host/create-host-runtimes.d.ts +2 -0
- package/dist/host/create-host-runtimes.js +14 -0
- package/dist/host/desktop-process-host.d.ts +6 -0
- package/dist/host/desktop-process-host.js +74 -0
- package/dist/host/host-runtime-availability.d.ts +1 -0
- package/dist/host/host-runtime-availability.js +3 -0
- package/dist/host/process-host.d.ts +31 -0
- package/dist/host/process-host.js +6 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.js +49 -0
- package/dist/jsonl/ai-jsonl-preview.d.ts +27 -0
- package/dist/jsonl/ai-jsonl-preview.js +68 -0
- package/dist/jsonl/ai-jsonl-view-type.d.ts +1 -0
- package/dist/jsonl/ai-jsonl-view-type.js +1 -0
- package/dist/jsonl/ai-jsonl-view.d.ts +16 -0
- package/dist/jsonl/ai-jsonl-view.js +57 -0
- package/dist/jsonl/ai-jsonl-view.svelte +260 -0
- package/dist/jsonl/ai-jsonl-view.svelte.d.ts +9 -0
- package/dist/memory/candidate-extractor.d.ts +25 -0
- package/dist/memory/candidate-extractor.js +191 -0
- package/dist/memory/forget-confirmation.d.ts +3 -0
- package/dist/memory/forget-confirmation.js +45 -0
- package/dist/memory/hashes.d.ts +5 -0
- package/dist/memory/hashes.js +37 -0
- package/dist/memory/memory-ingestion-coordinator.d.ts +19 -0
- package/dist/memory/memory-ingestion-coordinator.js +108 -0
- package/dist/memory/memory-record-store.d.ts +62 -0
- package/dist/memory/memory-record-store.js +369 -0
- package/dist/memory/memory-scheduler.d.ts +21 -0
- package/dist/memory/memory-scheduler.js +143 -0
- package/dist/memory/memory-tools.d.ts +61 -0
- package/dist/memory/memory-tools.js +99 -0
- package/dist/memory/native-memory-service.d.ts +72 -0
- package/dist/memory/native-memory-service.js +1256 -0
- package/dist/memory/paths.d.ts +13 -0
- package/dist/memory/paths.js +60 -0
- package/dist/memory/runtime-consolidation-provider.d.ts +18 -0
- package/dist/memory/runtime-consolidation-provider.js +154 -0
- package/dist/memory/types.d.ts +139 -0
- package/dist/memory/types.js +1 -0
- package/dist/providers/acp-model-provider.d.ts +11 -0
- package/dist/providers/acp-model-provider.js +99 -0
- package/dist/providers/codex-model-catalog.d.ts +7 -0
- package/dist/providers/codex-model-catalog.js +179 -0
- package/dist/providers/codex-model-provider.d.ts +12 -0
- package/dist/providers/codex-model-provider.js +42 -0
- package/dist/providers/model-provider.d.ts +26 -0
- package/dist/providers/model-provider.js +26 -0
- package/dist/public-components.d.ts +3 -0
- package/dist/public-components.js +3 -0
- package/dist/registry/runtime-registry.d.ts +11 -0
- package/dist/registry/runtime-registry.js +71 -0
- package/dist/registry/select-runtime.d.ts +9 -0
- package/dist/registry/select-runtime.js +31 -0
- package/dist/runtime-adapters.d.ts +16 -0
- package/dist/runtime-adapters.js +9 -0
- package/dist/runtimes/acp/acp-event-mapper.d.ts +56 -0
- package/dist/runtimes/acp/acp-event-mapper.js +237 -0
- package/dist/runtimes/acp/acp-runtime.d.ts +46 -0
- package/dist/runtimes/acp/acp-runtime.js +165 -0
- package/dist/runtimes/acp/desktop-acp-backend.d.ts +16 -0
- package/dist/runtimes/acp/desktop-acp-backend.js +243 -0
- package/dist/runtimes/codex/app-server-protocol.d.ts +28 -0
- package/dist/runtimes/codex/app-server-protocol.js +274 -0
- package/dist/runtimes/codex/codex-runtime.d.ts +24 -0
- package/dist/runtimes/codex/codex-runtime.js +318 -0
- package/dist/runtimes/fake/fake-runtime.d.ts +51 -0
- package/dist/runtimes/fake/fake-runtime.js +257 -0
- package/dist/sessions/plugin-data.d.ts +8 -0
- package/dist/sessions/plugin-data.js +29 -0
- package/dist/sessions/session-store.d.ts +47 -0
- package/dist/sessions/session-store.js +117 -0
- package/dist/settings/acp-agents.d.ts +8 -0
- package/dist/settings/acp-agents.js +11 -0
- package/dist/settings/ai-settings-tab.d.ts +8 -0
- package/dist/settings/ai-settings-tab.js +249 -0
- package/dist/settings/ai-settings.d.ts +43 -0
- package/dist/settings/ai-settings.js +147 -0
- package/dist/settings/app-tool-setting-rows.d.ts +16 -0
- package/dist/settings/app-tool-setting-rows.js +30 -0
- package/dist/settings/register-ai-settings.d.ts +3 -0
- package/dist/settings/register-ai-settings.js +393 -0
- package/dist/skills/activation.d.ts +2 -0
- package/dist/skills/activation.js +20 -0
- package/dist/skills/bundled/lapis-notes.d.ts +3 -0
- package/dist/skills/bundled/lapis-notes.js +52 -0
- package/dist/skills/bundled/research.d.ts +6 -0
- package/dist/skills/bundled/research.js +29 -0
- package/dist/skills/discovery.d.ts +21 -0
- package/dist/skills/discovery.js +227 -0
- package/dist/skills/manifest.d.ts +3 -0
- package/dist/skills/manifest.js +39 -0
- package/dist/skills/parser.d.ts +12 -0
- package/dist/skills/parser.js +124 -0
- package/dist/skills/registry.d.ts +21 -0
- package/dist/skills/registry.js +89 -0
- package/dist/skills/seed.d.ts +5 -0
- package/dist/skills/seed.js +18 -0
- package/dist/skills/skill-tools.d.ts +8 -0
- package/dist/skills/skill-tools.js +113 -0
- package/dist/skills/types.d.ts +67 -0
- package/dist/skills/types.js +1 -0
- package/dist/styles.css +514 -0
- package/dist/tools/app-tool-host.d.ts +47 -0
- package/dist/tools/app-tool-host.js +419 -0
- package/dist/tools/desktop-app-tool-bridge.d.ts +23 -0
- package/dist/tools/desktop-app-tool-bridge.js +223 -0
- package/dist/tools/mcp-server-registry.d.ts +7 -0
- package/dist/tools/mcp-server-registry.js +30 -0
- package/manifest.json +34 -0
- package/package.json +83 -0
- package/styles.css +1 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { App, WorkspaceLeaf } from "@lapis-notes/api";
|
|
2
|
+
import type { ComposerSearchSource } from "@lapismd/design-core/ai/chat";
|
|
3
|
+
import type { AgentRequest, AgentRuntime, ModelRef, McpServerContribution } from "../core/types";
|
|
4
|
+
import type { AgentSessionStore } from "../sessions/session-store";
|
|
5
|
+
import type { ConversationRepository, CreateConversationInput } from "../conversations/conversation-repository";
|
|
6
|
+
import type { ConversationLocation } from "../conversations/types";
|
|
7
|
+
import { type AiPluginSettings } from "../settings/ai-settings";
|
|
8
|
+
import type { AppToolBridgeCoordinator } from "../tools/desktop-app-tool-bridge";
|
|
9
|
+
import type { SkillRegistry, SkillSnapshotStore } from "../skills/registry";
|
|
10
|
+
import type { SkillDiscoveryContext } from "../skills/types";
|
|
11
|
+
import type { SlashCommandRouter } from "../commands/router";
|
|
12
|
+
import type { AppToolHost } from "../tools/app-tool-host";
|
|
13
|
+
import type { AutomaticMemoryRecall } from "../memory/types";
|
|
14
|
+
import type { HandoffSummaryCoordinator } from "../conversations/handoff-summary";
|
|
15
|
+
type $$ComponentProps = {
|
|
16
|
+
app?: App;
|
|
17
|
+
runtime: AgentRuntime;
|
|
18
|
+
selectRuntime?: (request: AgentRequest) => Promise<AgentRuntime>;
|
|
19
|
+
unavailableReason?: string | null;
|
|
20
|
+
initializing?: boolean;
|
|
21
|
+
workspace?: string;
|
|
22
|
+
mcpServers?: McpServerContribution[];
|
|
23
|
+
appToolBridge?: AppToolBridgeCoordinator;
|
|
24
|
+
skills?: SkillRegistry;
|
|
25
|
+
skillSnapshots?: SkillSnapshotStore;
|
|
26
|
+
slashRouter?: SlashCommandRouter;
|
|
27
|
+
appToolHost?: AppToolHost;
|
|
28
|
+
memoryRecall?: AutomaticMemoryRecall;
|
|
29
|
+
handoffSummaries?: Pick<HandoffSummaryCoordinator, "afterTerminal">;
|
|
30
|
+
skillContext?: () => SkillDiscoveryContext;
|
|
31
|
+
sessionStore?: AgentSessionStore;
|
|
32
|
+
sessionId?: string;
|
|
33
|
+
repository?: ConversationRepository;
|
|
34
|
+
initialLocation?: ConversationLocation | null;
|
|
35
|
+
createConversation?: (explicitFolder?: string) => CreateConversationInput;
|
|
36
|
+
subscribeConversationMoves?: (listener: (oldPath: string, newPath: string) => void) => () => void;
|
|
37
|
+
onRevealHistory?: () => void | Promise<void>;
|
|
38
|
+
onConversationLocationChange?: (location: ConversationLocation | null) => void;
|
|
39
|
+
currentConversationScope?: () => string;
|
|
40
|
+
listConversationFolders?: () => string[];
|
|
41
|
+
workspaceLeaf?: WorkspaceLeaf;
|
|
42
|
+
fileSearch?: ComposerSearchSource;
|
|
43
|
+
models?: ModelRef[];
|
|
44
|
+
modelCatalogError?: string | null;
|
|
45
|
+
settings?: Partial<AiPluginSettings>;
|
|
46
|
+
onSettingsChange?: (patch: Partial<AiPluginSettings>) => void | Promise<void>;
|
|
47
|
+
};
|
|
48
|
+
declare const AiChatPanel: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
49
|
+
type AiChatPanel = ReturnType<typeof AiChatPanel>;
|
|
50
|
+
export default AiChatPanel;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
[data-ui-component="ai-inventory-result"] {
|
|
2
|
+
display: flex;
|
|
3
|
+
width: 100%;
|
|
4
|
+
min-width: 0;
|
|
5
|
+
flex-direction: column;
|
|
6
|
+
gap: 0.25rem;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
[data-ui-component="ai-inventory-result"] [data-ui-part="empty"] {
|
|
10
|
+
padding: 0.375rem;
|
|
11
|
+
color: var(--muted-foreground);
|
|
12
|
+
font-size: 0.8125rem;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
[data-ui-component="ai-inventory-result"] [data-ui-part="item"] {
|
|
16
|
+
display: flex;
|
|
17
|
+
min-width: 0;
|
|
18
|
+
align-items: flex-start;
|
|
19
|
+
justify-content: space-between;
|
|
20
|
+
gap: 0.5rem;
|
|
21
|
+
border-radius: var(--ui-workspace-radius-small, 0.25rem);
|
|
22
|
+
padding: 0.375rem 0.5rem;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
[data-ui-component="ai-inventory-result"] [data-ui-part="item"]:hover,
|
|
26
|
+
[data-ui-component="ai-inventory-result"] [data-ui-part="item"]:focus-within {
|
|
27
|
+
background: var(
|
|
28
|
+
--ui-workspace-explorer-row-hover-background,
|
|
29
|
+
var(--sidebar-accent)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
[data-ui-component="ai-inventory-result"] [data-ui-part="body"] {
|
|
34
|
+
display: flex;
|
|
35
|
+
min-width: 0;
|
|
36
|
+
flex: 1 1 auto;
|
|
37
|
+
flex-direction: column;
|
|
38
|
+
gap: 0.125rem;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
[data-ui-component="ai-inventory-result"] [data-ui-part="name"] {
|
|
42
|
+
overflow: hidden;
|
|
43
|
+
font-weight: 600;
|
|
44
|
+
text-overflow: ellipsis;
|
|
45
|
+
white-space: nowrap;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
[data-ui-component="ai-inventory-result"] [data-ui-part="description"] {
|
|
49
|
+
color: var(--muted-foreground);
|
|
50
|
+
font-size: 0.75rem;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
[data-ui-component="ai-inventory-result"] [data-ui-part="open"] {
|
|
54
|
+
flex: 0 0 auto;
|
|
55
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { AgentResultViewProps, App } from "@lapis-notes/api";
|
|
3
|
+
import { Button } from "@lapismd/design-core/shadcn/button";
|
|
4
|
+
import SquareArrowOutUpRightIcon from "@lucide/svelte/icons/square-arrow-out-up-right";
|
|
5
|
+
import type { AiChatInventory, AiChatInventoryItem } from "./chat-items";
|
|
6
|
+
import "./ai-inventory-result.css";
|
|
7
|
+
|
|
8
|
+
let {
|
|
9
|
+
app,
|
|
10
|
+
output,
|
|
11
|
+
}: Partial<AgentResultViewProps<App>> = $props();
|
|
12
|
+
|
|
13
|
+
const inventory = $derived.by((): AiChatInventory | undefined => {
|
|
14
|
+
if (!output || typeof output !== "object" || Array.isArray(output)) {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
const record = output as Partial<AiChatInventory>;
|
|
18
|
+
if (record.kind !== "skills" && record.kind !== "tools") return undefined;
|
|
19
|
+
if (!Array.isArray(record.items)) return undefined;
|
|
20
|
+
return record as AiChatInventory;
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
async function openItem(item: AiChatInventoryItem): Promise<void> {
|
|
24
|
+
if (!item.path || !app) return;
|
|
25
|
+
await app.workspace.openLinkText(item.path, "");
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<div
|
|
30
|
+
data-ui-component="ai-inventory-result"
|
|
31
|
+
data-kind={inventory?.kind}
|
|
32
|
+
>
|
|
33
|
+
{#if !inventory || inventory.items.length === 0}
|
|
34
|
+
<p data-ui-part="empty">
|
|
35
|
+
{inventory?.kind === "tools"
|
|
36
|
+
? "No application tools are available."
|
|
37
|
+
: "No skills are available."}
|
|
38
|
+
</p>
|
|
39
|
+
{:else}
|
|
40
|
+
{#each inventory.items as item (`${item.kind}:${item.name}:${item.path ?? ""}`)}
|
|
41
|
+
<div data-ui-part="item">
|
|
42
|
+
<div data-ui-part="body">
|
|
43
|
+
<span data-ui-part="name">{item.name}</span>
|
|
44
|
+
{#if item.description}
|
|
45
|
+
<span data-ui-part="description">{item.description}</span>
|
|
46
|
+
{/if}
|
|
47
|
+
</div>
|
|
48
|
+
{#if item.path && app}
|
|
49
|
+
<Button
|
|
50
|
+
type="button"
|
|
51
|
+
size="icon-xs"
|
|
52
|
+
variant="ghost"
|
|
53
|
+
data-ui-part="open"
|
|
54
|
+
aria-label={`Open ${item.name}`}
|
|
55
|
+
onclick={() => void openItem(item)}
|
|
56
|
+
>
|
|
57
|
+
<SquareArrowOutUpRightIcon aria-hidden="true" />
|
|
58
|
+
</Button>
|
|
59
|
+
{/if}
|
|
60
|
+
</div>
|
|
61
|
+
{/each}
|
|
62
|
+
{/if}
|
|
63
|
+
</div>
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { AgentResultViewProps, App } from "@lapis-notes/api";
|
|
2
|
+
import "./ai-inventory-result.css";
|
|
3
|
+
declare const AiInventoryResult: import("svelte").Component<Partial<AgentResultViewProps<App>>, {}, "">;
|
|
4
|
+
type AiInventoryResult = ReturnType<typeof AiInventoryResult>;
|
|
5
|
+
export default AiInventoryResult;
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { Badge } from "@lapismd/design-core/shadcn/badge";
|
|
3
|
+
import { Button } from "@lapismd/design-core/shadcn/button";
|
|
4
|
+
import type {
|
|
5
|
+
UserInputAnswers,
|
|
6
|
+
UserInputQuestion,
|
|
7
|
+
UserInputRequest,
|
|
8
|
+
} from "../core/types";
|
|
9
|
+
|
|
10
|
+
let {
|
|
11
|
+
request,
|
|
12
|
+
disabled = false,
|
|
13
|
+
onRespond,
|
|
14
|
+
}: {
|
|
15
|
+
request: UserInputRequest;
|
|
16
|
+
disabled?: boolean;
|
|
17
|
+
onRespond(answers: UserInputAnswers): void;
|
|
18
|
+
} = $props();
|
|
19
|
+
|
|
20
|
+
let answers = $state<Record<string, string>>({});
|
|
21
|
+
let otherSelected = $state<Record<string, boolean>>({});
|
|
22
|
+
const complete = $derived(
|
|
23
|
+
request.questions.every((question) =>
|
|
24
|
+
Boolean(answers[question.id]?.trim()),
|
|
25
|
+
),
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
function answer(question: UserInputQuestion, value: string): void {
|
|
29
|
+
answers = { ...answers, [question.id]: value };
|
|
30
|
+
otherSelected = { ...otherSelected, [question.id]: false };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function chooseOther(question: UserInputQuestion): void {
|
|
34
|
+
answers = { ...answers, [question.id]: "" };
|
|
35
|
+
otherSelected = { ...otherSelected, [question.id]: true };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function updateInput(question: UserInputQuestion, value: string): void {
|
|
39
|
+
answers = { ...answers, [question.id]: value };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function submit(): void {
|
|
43
|
+
if (disabled || !complete) return;
|
|
44
|
+
onRespond(
|
|
45
|
+
Object.fromEntries(
|
|
46
|
+
request.questions.map((question) => [
|
|
47
|
+
question.id,
|
|
48
|
+
[answers[question.id]?.trim() ?? ""],
|
|
49
|
+
]),
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
</script>
|
|
54
|
+
|
|
55
|
+
<form
|
|
56
|
+
class="ai-agent-request"
|
|
57
|
+
data-ui-component="ai-agent-request"
|
|
58
|
+
data-kind="question"
|
|
59
|
+
data-testid="ai-question-card"
|
|
60
|
+
onsubmit={(event) => {
|
|
61
|
+
event.preventDefault();
|
|
62
|
+
submit();
|
|
63
|
+
}}
|
|
64
|
+
>
|
|
65
|
+
{#each request.questions as question (question.id)}
|
|
66
|
+
<fieldset>
|
|
67
|
+
<legend>{question.header}</legend>
|
|
68
|
+
<strong>{question.prompt}</strong>
|
|
69
|
+
{#if question.options?.length}
|
|
70
|
+
<div data-ui-part="options">
|
|
71
|
+
{#each question.options as option, index (option.id)}
|
|
72
|
+
<button
|
|
73
|
+
type="button"
|
|
74
|
+
data-ui-part="feedback-option"
|
|
75
|
+
data-selected={!otherSelected[question.id] &&
|
|
76
|
+
answers[question.id] === option.label}
|
|
77
|
+
disabled={disabled}
|
|
78
|
+
onclick={() => answer(question, option.label)}
|
|
79
|
+
>
|
|
80
|
+
<Badge
|
|
81
|
+
variant={!otherSelected[question.id] &&
|
|
82
|
+
answers[question.id] === option.label
|
|
83
|
+
? "default"
|
|
84
|
+
: "secondary"}
|
|
85
|
+
>{String.fromCharCode(65 + index)}</Badge
|
|
86
|
+
>
|
|
87
|
+
<span>
|
|
88
|
+
<span>{option.label}</span>
|
|
89
|
+
{#if option.description}
|
|
90
|
+
<small>{option.description}</small>
|
|
91
|
+
{/if}
|
|
92
|
+
</span>
|
|
93
|
+
</button>
|
|
94
|
+
{/each}
|
|
95
|
+
{#if question.allowOther}
|
|
96
|
+
<button
|
|
97
|
+
type="button"
|
|
98
|
+
data-ui-part="feedback-option"
|
|
99
|
+
data-selected={otherSelected[question.id] === true}
|
|
100
|
+
disabled={disabled}
|
|
101
|
+
onclick={() => chooseOther(question)}
|
|
102
|
+
>
|
|
103
|
+
<Badge
|
|
104
|
+
variant={otherSelected[question.id]
|
|
105
|
+
? "default"
|
|
106
|
+
: "secondary"}
|
|
107
|
+
>{String.fromCharCode(65 + question.options.length)}</Badge
|
|
108
|
+
>
|
|
109
|
+
<span>Other</span>
|
|
110
|
+
</button>
|
|
111
|
+
{/if}
|
|
112
|
+
</div>
|
|
113
|
+
{/if}
|
|
114
|
+
{#if !question.options?.length || otherSelected[question.id]}
|
|
115
|
+
<input
|
|
116
|
+
type={question.secret ? "password" : "text"}
|
|
117
|
+
aria-label={`${question.header} answer`}
|
|
118
|
+
autocomplete="off"
|
|
119
|
+
value={answers[question.id] ?? ""}
|
|
120
|
+
disabled={disabled}
|
|
121
|
+
oninput={(event) =>
|
|
122
|
+
updateInput(question, event.currentTarget.value)}
|
|
123
|
+
/>
|
|
124
|
+
{/if}
|
|
125
|
+
</fieldset>
|
|
126
|
+
{/each}
|
|
127
|
+
<div data-ui-part="request-actions">
|
|
128
|
+
<Button type="submit" size="sm" disabled={disabled || !complete}
|
|
129
|
+
>Submit answer</Button
|
|
130
|
+
>
|
|
131
|
+
</div>
|
|
132
|
+
</form>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { UserInputAnswers, UserInputRequest } from "../core/types";
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
request: UserInputRequest;
|
|
4
|
+
disabled?: boolean;
|
|
5
|
+
onRespond(answers: UserInputAnswers): void;
|
|
6
|
+
};
|
|
7
|
+
declare const AiQuestionCard: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
8
|
+
type AiQuestionCard = ReturnType<typeof AiQuestionCard>;
|
|
9
|
+
export default AiQuestionCard;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { AgentRuntime, ModelRef, McpServerContribution } from "../core/types";
|
|
2
|
+
import type { ConversationLocation } from "../conversations/types";
|
|
3
|
+
import type { AiPluginSettings } from "../settings/ai-settings";
|
|
4
|
+
import type { AiViewHost } from "./ai-view";
|
|
5
|
+
export type AiViewBootstrap = {
|
|
6
|
+
runtime: AgentRuntime;
|
|
7
|
+
settings: AiPluginSettings;
|
|
8
|
+
models: ModelRef[];
|
|
9
|
+
modelCatalogError: string | null;
|
|
10
|
+
unavailableReason: string | null;
|
|
11
|
+
};
|
|
12
|
+
export declare function initialAiViewBootstrap(host: AiViewHost): AiViewBootstrap;
|
|
13
|
+
export declare function prepareAiViewBootstrap(host: AiViewHost, initialLocation: ConversationLocation | null, mcpServers: McpServerContribution[]): Promise<AiViewBootstrap>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { ACP_AGENT_IDS } from "../settings/acp-agents";
|
|
2
|
+
export function initialAiViewBootstrap(host) {
|
|
3
|
+
return {
|
|
4
|
+
runtime: host.fallbackRuntime(),
|
|
5
|
+
settings: host.getSettings(),
|
|
6
|
+
models: [],
|
|
7
|
+
modelCatalogError: null,
|
|
8
|
+
unavailableReason: null,
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export async function prepareAiViewBootstrap(host, initialLocation, mcpServers) {
|
|
12
|
+
let settings = host.getSettings();
|
|
13
|
+
const conversation = initialLocation
|
|
14
|
+
? host.conversations.read(initialLocation)
|
|
15
|
+
: Promise.resolve(null);
|
|
16
|
+
const catalogs = ACP_AGENT_IDS.map((agent) => host.models.listModels(agent));
|
|
17
|
+
const [snapshot, settledCatalogs] = await Promise.all([
|
|
18
|
+
conversation.catch(() => null),
|
|
19
|
+
Promise.allSettled(catalogs),
|
|
20
|
+
]);
|
|
21
|
+
if (snapshot) {
|
|
22
|
+
const binding = snapshot.agents.find((record) => record.type === "binding.created" &&
|
|
23
|
+
record.id === snapshot.metadata.activeAgentBindingId);
|
|
24
|
+
if (binding?.type === "binding.created") {
|
|
25
|
+
const agent = binding.agent === "cursor" ? "cursor" : "codex";
|
|
26
|
+
settings = {
|
|
27
|
+
...settings,
|
|
28
|
+
acpAgent: agent,
|
|
29
|
+
defaultRuntime: binding.runtime === "codex-native"
|
|
30
|
+
? "codex-native"
|
|
31
|
+
: binding.runtime === "fake"
|
|
32
|
+
? "fake"
|
|
33
|
+
: "acp",
|
|
34
|
+
defaultModel: binding.model?.model ?? settings.defaultModels[agent],
|
|
35
|
+
thinking: binding.thinking ?? settings.thinking,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
const models = settledCatalogs.flatMap((catalog) => catalog.status === "fulfilled" ? catalog.value : []);
|
|
40
|
+
let modelCatalogError = null;
|
|
41
|
+
const selectedCatalog = settledCatalogs[ACP_AGENT_IDS.indexOf(settings.acpAgent)];
|
|
42
|
+
const agentModels = models.filter((model) => model.provider === settings.acpAgent);
|
|
43
|
+
if (selectedCatalog?.status === "rejected") {
|
|
44
|
+
modelCatalogError = errorMessage(selectedCatalog.reason);
|
|
45
|
+
}
|
|
46
|
+
else if (!initialLocation &&
|
|
47
|
+
agentModels.length > 0 &&
|
|
48
|
+
!agentModels.some((model) => model.model === settings.defaultModel)) {
|
|
49
|
+
const model = (agentModels.find((entry) => entry.isDefault) ??
|
|
50
|
+
agentModels[0]);
|
|
51
|
+
await host.updateSettings({ defaultModel: model.model });
|
|
52
|
+
settings = host.getSettings();
|
|
53
|
+
}
|
|
54
|
+
let runtime;
|
|
55
|
+
let unavailableReason = null;
|
|
56
|
+
try {
|
|
57
|
+
runtime = await host.selectRuntime({
|
|
58
|
+
prompt: "",
|
|
59
|
+
mcpServers,
|
|
60
|
+
agent: settings.acpAgent,
|
|
61
|
+
model: { provider: settings.acpAgent, model: settings.defaultModel },
|
|
62
|
+
thinking: settings.thinking,
|
|
63
|
+
metadata: settings.defaultRuntime === "auto"
|
|
64
|
+
? undefined
|
|
65
|
+
: { runtime: settings.defaultRuntime },
|
|
66
|
+
});
|
|
67
|
+
unavailableReason =
|
|
68
|
+
settings.defaultRuntime === "fake"
|
|
69
|
+
? null
|
|
70
|
+
: host.liveRuntimeUnavailableReason();
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
runtime = host.fallbackRuntime();
|
|
74
|
+
unavailableReason = errorMessage(error);
|
|
75
|
+
}
|
|
76
|
+
return {
|
|
77
|
+
runtime,
|
|
78
|
+
settings,
|
|
79
|
+
models,
|
|
80
|
+
modelCatalogError,
|
|
81
|
+
unavailableReason,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function errorMessage(error) {
|
|
85
|
+
return error instanceof Error ? error.message : String(error);
|
|
86
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount } from "svelte";
|
|
3
|
+
import type { App, WorkspaceLeaf } from "@lapis-notes/api";
|
|
4
|
+
import type { ConversationLocation } from "../conversations/types";
|
|
5
|
+
import AiChatPanel from "./ai-chat-panel.svelte";
|
|
6
|
+
import type { AiViewHost } from "./ai-view";
|
|
7
|
+
import {
|
|
8
|
+
initialAiViewBootstrap,
|
|
9
|
+
prepareAiViewBootstrap,
|
|
10
|
+
type AiViewBootstrap,
|
|
11
|
+
} from "./ai-view-bootstrap";
|
|
12
|
+
|
|
13
|
+
let {
|
|
14
|
+
app,
|
|
15
|
+
host,
|
|
16
|
+
workspaceLeaf,
|
|
17
|
+
initialLocation,
|
|
18
|
+
onConversationLocationChange,
|
|
19
|
+
}: {
|
|
20
|
+
app: App;
|
|
21
|
+
host: AiViewHost;
|
|
22
|
+
workspaceLeaf: WorkspaceLeaf;
|
|
23
|
+
initialLocation: ConversationLocation | null;
|
|
24
|
+
onConversationLocationChange: (
|
|
25
|
+
location: ConversationLocation | null,
|
|
26
|
+
) => void;
|
|
27
|
+
} = $props();
|
|
28
|
+
|
|
29
|
+
const mcpServers = $derived(host.mcpServers.list());
|
|
30
|
+
const initialBootstrap = $derived(initialAiViewBootstrap(host));
|
|
31
|
+
let preparedBootstrap = $state<AiViewBootstrap | null>(null);
|
|
32
|
+
const bootstrap = $derived(preparedBootstrap ?? initialBootstrap);
|
|
33
|
+
let initializing = $state(true);
|
|
34
|
+
|
|
35
|
+
onMount(() => {
|
|
36
|
+
let disposed = false;
|
|
37
|
+
void prepareAiViewBootstrap(host, initialLocation, mcpServers)
|
|
38
|
+
.then((prepared) => {
|
|
39
|
+
if (disposed) return;
|
|
40
|
+
preparedBootstrap = prepared;
|
|
41
|
+
initializing = false;
|
|
42
|
+
})
|
|
43
|
+
.catch((error) => {
|
|
44
|
+
if (disposed) return;
|
|
45
|
+
preparedBootstrap = {
|
|
46
|
+
...bootstrap,
|
|
47
|
+
unavailableReason:
|
|
48
|
+
error instanceof Error ? error.message : String(error),
|
|
49
|
+
};
|
|
50
|
+
initializing = false;
|
|
51
|
+
});
|
|
52
|
+
return () => {
|
|
53
|
+
disposed = true;
|
|
54
|
+
};
|
|
55
|
+
});
|
|
56
|
+
</script>
|
|
57
|
+
|
|
58
|
+
<AiChatPanel
|
|
59
|
+
{app}
|
|
60
|
+
runtime={bootstrap.runtime}
|
|
61
|
+
selectRuntime={(request) => host.selectRuntime(request)}
|
|
62
|
+
unavailableReason={bootstrap.unavailableReason}
|
|
63
|
+
{initializing}
|
|
64
|
+
{mcpServers}
|
|
65
|
+
appToolBridge={host.appToolBridge}
|
|
66
|
+
skills={host.skills}
|
|
67
|
+
skillSnapshots={host.skillSnapshots}
|
|
68
|
+
slashRouter={host.slashRouter}
|
|
69
|
+
appToolHost={host.appToolHost}
|
|
70
|
+
memoryRecall={host.memory}
|
|
71
|
+
handoffSummaries={host.handoffSummaries}
|
|
72
|
+
skillContext={host.skillContext}
|
|
73
|
+
workspace={host.workspace}
|
|
74
|
+
repository={host.conversations}
|
|
75
|
+
{initialLocation}
|
|
76
|
+
createConversation={(explicitFolder) =>
|
|
77
|
+
host.createConversationInput(explicitFolder)}
|
|
78
|
+
onRevealHistory={() => host.revealConversationHistory()}
|
|
79
|
+
subscribeConversationMoves={host.subscribeConversationMoves?.bind(host)}
|
|
80
|
+
{onConversationLocationChange}
|
|
81
|
+
currentConversationScope={() => host.currentConversationScope()}
|
|
82
|
+
listConversationFolders={() => host.listConversationFolders()}
|
|
83
|
+
{workspaceLeaf}
|
|
84
|
+
fileSearch={host.searchVaultFiles}
|
|
85
|
+
models={bootstrap.models}
|
|
86
|
+
modelCatalogError={bootstrap.modelCatalogError}
|
|
87
|
+
settings={bootstrap.settings}
|
|
88
|
+
onSettingsChange={(patch) => host.updateSettings(patch)}
|
|
89
|
+
/>
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { App, WorkspaceLeaf } from "@lapis-notes/api";
|
|
2
|
+
import type { ConversationLocation } from "../conversations/types";
|
|
3
|
+
import type { AiViewHost } from "./ai-view";
|
|
4
|
+
type $$ComponentProps = {
|
|
5
|
+
app: App;
|
|
6
|
+
host: AiViewHost;
|
|
7
|
+
workspaceLeaf: WorkspaceLeaf;
|
|
8
|
+
initialLocation: ConversationLocation | null;
|
|
9
|
+
onConversationLocationChange: (location: ConversationLocation | null) => void;
|
|
10
|
+
};
|
|
11
|
+
declare const AiViewPanel: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
12
|
+
type AiViewPanel = ReturnType<typeof AiViewPanel>;
|
|
13
|
+
export default AiViewPanel;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const AiViewType = "ai";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const AiViewType = "ai";
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { View, type ViewStateResult, type WorkspaceLeaf } from "@lapis-notes/api";
|
|
2
|
+
import type { ComposerSearchSource } from "@lapismd/design-core/ai/chat";
|
|
3
|
+
import type { AgentRequest, AgentRuntime, ModelRef, McpServerContribution } from "../core/types";
|
|
4
|
+
import type { ConversationRepository, CreateConversationInput } from "../conversations/conversation-repository";
|
|
5
|
+
import type { AiPluginSettings } from "../settings/ai-settings";
|
|
6
|
+
import type { AppToolBridgeCoordinator } from "../tools/desktop-app-tool-bridge";
|
|
7
|
+
export { AiViewType } from "./ai-view-type";
|
|
8
|
+
export type AiViewHost = {
|
|
9
|
+
selectRuntime(request: AgentRequest): Promise<AgentRuntime>;
|
|
10
|
+
fallbackRuntime(): AgentRuntime;
|
|
11
|
+
liveRuntimeUnavailableReason(): string | null;
|
|
12
|
+
mcpServers: {
|
|
13
|
+
list(): McpServerContribution[];
|
|
14
|
+
};
|
|
15
|
+
appToolBridge?: AppToolBridgeCoordinator;
|
|
16
|
+
skills?: import("../skills/registry").SkillRegistry;
|
|
17
|
+
skillSnapshots?: import("../skills/registry").SkillSnapshotStore;
|
|
18
|
+
slashRouter?: import("../commands/router").SlashCommandRouter;
|
|
19
|
+
appToolHost?: import("../tools/app-tool-host").AppToolHost;
|
|
20
|
+
memory?: import("../memory/types").AutomaticMemoryRecall;
|
|
21
|
+
handoffSummaries?: Pick<import("../conversations/handoff-summary").HandoffSummaryCoordinator, "afterTerminal">;
|
|
22
|
+
skillContext?: () => import("../skills/types").SkillDiscoveryContext;
|
|
23
|
+
conversations: ConversationRepository;
|
|
24
|
+
createConversationInput(explicitFolder?: string): CreateConversationInput;
|
|
25
|
+
currentConversationScope(): string;
|
|
26
|
+
listConversationFolders(): string[];
|
|
27
|
+
revealConversationHistory(): Promise<void>;
|
|
28
|
+
subscribeConversationMoves?(listener: (oldPath: string, newPath: string) => void): () => void;
|
|
29
|
+
searchVaultFiles: ComposerSearchSource;
|
|
30
|
+
getSettings(): AiPluginSettings;
|
|
31
|
+
updateSettings(patch: Partial<AiPluginSettings>): Promise<void>;
|
|
32
|
+
subscribeSettings?(listener: (patch: Partial<AiPluginSettings>) => void): () => void;
|
|
33
|
+
models: {
|
|
34
|
+
listModels(provider: string): Promise<ModelRef[]>;
|
|
35
|
+
};
|
|
36
|
+
workspace?: string;
|
|
37
|
+
};
|
|
38
|
+
export declare class AiView extends View {
|
|
39
|
+
private component;
|
|
40
|
+
private disposed;
|
|
41
|
+
private mountGeneration;
|
|
42
|
+
private readonly host;
|
|
43
|
+
constructor(leaf: WorkspaceLeaf, host: AiViewHost);
|
|
44
|
+
getViewType(): string;
|
|
45
|
+
getDisplayText(): string;
|
|
46
|
+
getIcon(): string;
|
|
47
|
+
getBreadcrumbFilePath(): string | null;
|
|
48
|
+
getBreadcrumbs(): {
|
|
49
|
+
id: string;
|
|
50
|
+
label: string;
|
|
51
|
+
onSelect: () => void;
|
|
52
|
+
}[];
|
|
53
|
+
protected onOpen(): Promise<void>;
|
|
54
|
+
protected onClose(): Promise<void>;
|
|
55
|
+
onload(): void;
|
|
56
|
+
onunload(): void;
|
|
57
|
+
setState(state: Record<string, unknown>, result?: ViewStateResult): Promise<void>;
|
|
58
|
+
private mountPanel;
|
|
59
|
+
private persistLocationHint;
|
|
60
|
+
}
|