@ouraihub/aether-ui 0.0.0-stage → 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/LICENSE +789 -0
- package/fixtures/model-classify.json +42 -0
- package/fixtures/pricing-cost.json +56 -0
- package/fixtures/reasoning-infer.json +44 -0
- package/fixtures/sse/anthropic-messages/chinese.sse +24 -0
- package/fixtures/sse/anthropic-messages/multi-tool.sse +33 -0
- package/fixtures/sse/anthropic-messages/single-tool.sse +39 -0
- package/fixtures/sse/anthropic-messages/text.sse +27 -0
- package/fixtures/sse/anthropic-messages/truncated.sse +21 -0
- package/fixtures/sse/anthropic-messages/upstream-error.sse +12 -0
- package/fixtures/sse/expect.json +20 -0
- package/fixtures/sse/openai-chat/chinese.sse +14 -0
- package/fixtures/sse/openai-chat/multi-tool.sse +18 -0
- package/fixtures/sse/openai-chat/single-tool.sse +22 -0
- package/fixtures/sse/openai-chat/text.sse +14 -0
- package/fixtures/sse/openai-chat/truncated.sse +12 -0
- package/fixtures/sse/openai-chat/upstream-error.sse +6 -0
- package/fixtures/sse/openai-responses/chinese.sse +33 -0
- package/fixtures/sse/openai-responses/multi-tool.sse +36 -0
- package/fixtures/sse/openai-responses/single-tool.sse +27 -0
- package/fixtures/sse/openai-responses/text.sse +33 -0
- package/fixtures/sse/openai-responses/truncated.sse +30 -0
- package/fixtures/sse/openai-responses/upstream-error.sse +9 -0
- package/package.json +71 -3
- package/src/CopyInput.svelte +33 -0
- package/src/KitNotifier.svelte +33 -0
- package/src/chat/ApprovalCard.svelte +23 -0
- package/src/chat/ChatPanel.svelte +2097 -0
- package/src/chat/MessageItem.svelte +583 -0
- package/src/chat/SkillPicker.svelte +126 -0
- package/src/chat/ToolCallItem.svelte +23 -0
- package/src/chat/UsageDetails.svelte +138 -0
- package/src/chat/index.ts +2 -0
- package/src/chat/run-session.svelte.ts +556 -0
- package/src/components/ui/alert/alert-action.svelte +20 -0
- package/src/components/ui/alert/alert-description.svelte +23 -0
- package/src/components/ui/alert/alert-title.svelte +23 -0
- package/src/components/ui/alert/alert.svelte +43 -0
- package/src/components/ui/alert/index.ts +17 -0
- package/src/components/ui/alert-dialog/alert-dialog-action.svelte +27 -0
- package/src/components/ui/alert-dialog/alert-dialog-cancel.svelte +27 -0
- package/src/components/ui/alert-dialog/alert-dialog-content.svelte +32 -0
- package/src/components/ui/alert-dialog/alert-dialog-description.svelte +17 -0
- package/src/components/ui/alert-dialog/alert-dialog-footer.svelte +23 -0
- package/src/components/ui/alert-dialog/alert-dialog-header.svelte +20 -0
- package/src/components/ui/alert-dialog/alert-dialog-media.svelte +20 -0
- package/src/components/ui/alert-dialog/alert-dialog-overlay.svelte +17 -0
- package/src/components/ui/alert-dialog/alert-dialog-portal.svelte +7 -0
- package/src/components/ui/alert-dialog/alert-dialog-title.svelte +17 -0
- package/src/components/ui/alert-dialog/alert-dialog-trigger.svelte +7 -0
- package/src/components/ui/alert-dialog/alert-dialog.svelte +7 -0
- package/src/components/ui/alert-dialog/index.ts +40 -0
- package/src/components/ui/button/button.svelte +81 -0
- package/src/components/ui/button/index.ts +17 -0
- package/src/components/ui/dialog/dialog-close.svelte +11 -0
- package/src/components/ui/dialog/dialog-content.svelte +48 -0
- package/src/components/ui/dialog/dialog-description.svelte +17 -0
- package/src/components/ui/dialog/dialog-footer.svelte +32 -0
- package/src/components/ui/dialog/dialog-header.svelte +20 -0
- package/src/components/ui/dialog/dialog-overlay.svelte +17 -0
- package/src/components/ui/dialog/dialog-portal.svelte +7 -0
- package/src/components/ui/dialog/dialog-title.svelte +17 -0
- package/src/components/ui/dialog/dialog-trigger.svelte +11 -0
- package/src/components/ui/dialog/dialog.svelte +7 -0
- package/src/components/ui/dialog/index.ts +34 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-checkbox-group.svelte +16 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-checkbox-item.svelte +46 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-content.svelte +31 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-group-heading.svelte +22 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-group.svelte +7 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-item.svelte +27 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-label.svelte +24 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-portal.svelte +7 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-radio-group.svelte +17 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-radio-item.svelte +36 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-separator.svelte +17 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-shortcut.svelte +20 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-sub-content.svelte +31 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-sub-trigger.svelte +29 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-sub.svelte +7 -0
- package/src/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +7 -0
- package/src/components/ui/dropdown-menu/dropdown-menu.svelte +7 -0
- package/src/components/ui/dropdown-menu/index.ts +54 -0
- package/src/components/ui/empty/empty-content.svelte +23 -0
- package/src/components/ui/empty/empty-description.svelte +23 -0
- package/src/components/ui/empty/empty-header.svelte +20 -0
- package/src/components/ui/empty/empty-media.svelte +41 -0
- package/src/components/ui/empty/empty-title.svelte +20 -0
- package/src/components/ui/empty/empty.svelte +23 -0
- package/src/components/ui/empty/index.ts +22 -0
- package/src/components/ui/field/field-content.svelte +20 -0
- package/src/components/ui/field/field-description.svelte +25 -0
- package/src/components/ui/field/field-error.svelte +58 -0
- package/src/components/ui/field/field-group.svelte +23 -0
- package/src/components/ui/field/field-label.svelte +25 -0
- package/src/components/ui/field/field-legend.svelte +24 -0
- package/src/components/ui/field/field-separator.svelte +35 -0
- package/src/components/ui/field/field-set.svelte +20 -0
- package/src/components/ui/field/field-title.svelte +20 -0
- package/src/components/ui/field/field.svelte +47 -0
- package/src/components/ui/field/index.ts +33 -0
- package/src/components/ui/input/index.ts +7 -0
- package/src/components/ui/input/input.svelte +48 -0
- package/src/components/ui/label/index.ts +7 -0
- package/src/components/ui/label/label.svelte +20 -0
- package/src/components/ui/select/index.ts +40 -0
- package/src/components/ui/select/select-content.svelte +45 -0
- package/src/components/ui/select/select-group-heading.svelte +21 -0
- package/src/components/ui/select/select-group.svelte +17 -0
- package/src/components/ui/select/select-item.svelte +43 -0
- package/src/components/ui/select/select-label.svelte +20 -0
- package/src/components/ui/select/select-portal.svelte +7 -0
- package/src/components/ui/select/select-scroll-down-button.svelte +20 -0
- package/src/components/ui/select/select-scroll-up-button.svelte +20 -0
- package/src/components/ui/select/select-separator.svelte +18 -0
- package/src/components/ui/select/select-trigger.svelte +29 -0
- package/src/components/ui/select/select-value.svelte +7 -0
- package/src/components/ui/select/select.svelte +11 -0
- package/src/components/ui/separator/index.ts +7 -0
- package/src/components/ui/separator/separator.svelte +21 -0
- package/src/components/ui/sonner/index.ts +1 -0
- package/src/components/ui/sonner/sonner.svelte +40 -0
- package/src/components/ui/switch/index.ts +7 -0
- package/src/components/ui/switch/switch.svelte +34 -0
- package/src/components/ui/textarea/index.ts +7 -0
- package/src/components/ui/textarea/textarea.svelte +22 -0
- package/src/context.ts +34 -0
- package/src/dev/Demo.svelte +45 -0
- package/src/dev/main.ts +7 -0
- package/src/i18n/en.ts +617 -0
- package/src/i18n/index.ts +23 -0
- package/src/i18n/zh-CN.ts +599 -0
- package/src/index.ts +10 -0
- package/src/logs/LogPanel.svelte +97 -0
- package/src/logs/LogSettings.svelte +56 -0
- package/src/logs/index.ts +5 -0
- package/src/logs/logger.ts +96 -0
- package/src/markdown.ts +8 -0
- package/src/mcp/McpClientSettings.svelte +491 -0
- package/src/mcp/McpServerSettings.svelte +327 -0
- package/src/mcp/config.ts +26 -0
- package/src/mcp/index.ts +3 -0
- package/src/mcp/input.ts +73 -0
- package/src/notify.svelte.ts +43 -0
- package/src/ports/contract.ts +165 -0
- package/src/ports/fake.ts +484 -0
- package/src/ports/index.ts +7 -0
- package/src/ports/search.ts +47 -0
- package/src/ports/skill-sources.ts +45 -0
- package/src/providers/EndpointKeysDialog.svelte +343 -0
- package/src/providers/EndpointPanel.svelte +491 -0
- package/src/providers/ModelPickerDialog.svelte +187 -0
- package/src/providers/ModelSelect.svelte +434 -0
- package/src/providers/ModelSettingsDialog.svelte +453 -0
- package/src/providers/ProvidersSettings.svelte +631 -0
- package/src/providers/RequestConfigDialog.svelte +250 -0
- package/src/providers/SecretInput.svelte +276 -0
- package/src/providers/draft.ts +329 -0
- package/src/providers/edit-session.ts +146 -0
- package/src/providers/index.ts +5 -0
- package/src/providers/reasoning.ts +41 -0
- package/src/providers/revision.svelte.ts +4 -0
- package/src/search/SearchSettings.svelte +703 -0
- package/src/search/index.ts +13 -0
- package/src/search/settings-flow.ts +62 -0
- package/src/session.ts +20 -0
- package/src/skills/SkillAdd.svelte +347 -0
- package/src/skills/SkillAuthoring.svelte +381 -0
- package/src/skills/SkillEditor.svelte +94 -0
- package/src/skills/SkillPreviewDialog.svelte +122 -0
- package/src/skills/SkillsSettings.svelte +140 -0
- package/src/skills/error.ts +32 -0
- package/src/skills/index.ts +4 -0
- package/src/styles/kit.css +137 -0
- package/src/styles/tokens.css +22 -0
- package/src/tauri/index.ts +282 -0
- package/src/utils.ts +22 -0
- package/src/web/index.ts +376 -0
- package/README.md +0 -3
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { default as SearchSettings } from './SearchSettings.svelte'
|
|
2
|
+
export type {
|
|
3
|
+
SearchCap,
|
|
4
|
+
SearchKey,
|
|
5
|
+
SearchPreferences,
|
|
6
|
+
SearchHit,
|
|
7
|
+
SearchPage,
|
|
8
|
+
SearchPort,
|
|
9
|
+
SearchProvider,
|
|
10
|
+
SearchResult,
|
|
11
|
+
SearchSave,
|
|
12
|
+
SearchSettingsDto,
|
|
13
|
+
} from '../ports/search'
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { KitClientError } from '../ports/contract'
|
|
2
|
+
import type { CallOptions } from '../ports/contract'
|
|
3
|
+
import type { SearchPort, SearchSave, SearchSettingsDto } from '../ports/search'
|
|
4
|
+
|
|
5
|
+
/** Applies each persisted step before attempting the next (docs/30 §4). */
|
|
6
|
+
export async function saveSearchSettings(
|
|
7
|
+
port: SearchPort,
|
|
8
|
+
input: { config: SearchSave; key?: { value: string; keyId?: string }; check: boolean },
|
|
9
|
+
session: {
|
|
10
|
+
isCurrent(): boolean
|
|
11
|
+
onSettings(next: SearchSettingsDto): void
|
|
12
|
+
onConfigSaved(sent: SearchSave): void
|
|
13
|
+
onKeySaved(): void
|
|
14
|
+
},
|
|
15
|
+
options?: CallOptions,
|
|
16
|
+
): Promise<{ settings: SearchSettingsDto; checked?: { count: number } }> {
|
|
17
|
+
const config = { ...input.config }
|
|
18
|
+
const key = input.key ? { ...input.key } : undefined
|
|
19
|
+
const check = input.check
|
|
20
|
+
const cancelled = () => new KitClientError({ code: 'cancelled', message: 'Search settings cancelled', details: {} })
|
|
21
|
+
function assertCurrent() {
|
|
22
|
+
if (options?.signal?.aborted || !session.isCurrent()) throw cancelled()
|
|
23
|
+
}
|
|
24
|
+
// Consume an in-flight response even if its transport does not honor abort.
|
|
25
|
+
async function step<T>(call: () => Promise<T>): Promise<T> {
|
|
26
|
+
assertCurrent()
|
|
27
|
+
const signal = options?.signal
|
|
28
|
+
let onAbort: (() => void) | undefined
|
|
29
|
+
try {
|
|
30
|
+
const operation = call()
|
|
31
|
+
let value: T
|
|
32
|
+
if (signal) {
|
|
33
|
+
const abort = Promise.withResolvers<never>()
|
|
34
|
+
onAbort = () => abort.reject(cancelled())
|
|
35
|
+
signal.addEventListener('abort', onAbort, { once: true })
|
|
36
|
+
if (signal.aborted) onAbort()
|
|
37
|
+
value = await Promise.race([operation, abort.promise])
|
|
38
|
+
} else value = await operation
|
|
39
|
+
assertCurrent()
|
|
40
|
+
return value
|
|
41
|
+
} catch (error) {
|
|
42
|
+
assertCurrent()
|
|
43
|
+
throw error
|
|
44
|
+
} finally {
|
|
45
|
+
if (signal && onAbort) signal.removeEventListener('abort', onAbort)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let settings = await step(() => port.save(config, options))
|
|
50
|
+
session.onSettings(settings)
|
|
51
|
+
assertCurrent()
|
|
52
|
+
session.onConfigSaved(config)
|
|
53
|
+
if (key?.value.trim()) {
|
|
54
|
+
settings = await step(() => port.saveKey(config.capability, config.provider, key.value, key.keyId, options))
|
|
55
|
+
session.onSettings(settings)
|
|
56
|
+
assertCurrent()
|
|
57
|
+
session.onKeySaved()
|
|
58
|
+
}
|
|
59
|
+
const checked = check ? await step(() => port.check(config.capability, config.provider, options)) : undefined
|
|
60
|
+
assertCurrent()
|
|
61
|
+
return checked ? { settings, checked } : { settings }
|
|
62
|
+
}
|
package/src/session.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** A token belongs to exactly one session instance and one target generation. */
|
|
2
|
+
export interface SessionToken {
|
|
3
|
+
readonly generation: number
|
|
4
|
+
}
|
|
5
|
+
export interface ClientSession {
|
|
6
|
+
capture(): SessionToken
|
|
7
|
+
isCurrent(token: SessionToken): boolean
|
|
8
|
+
invalidate(): void
|
|
9
|
+
}
|
|
10
|
+
export function createClientSession(): ClientSession {
|
|
11
|
+
let generation = 0
|
|
12
|
+
let current: SessionToken = { generation }
|
|
13
|
+
return {
|
|
14
|
+
capture: () => current,
|
|
15
|
+
isCurrent: (token) => token === current,
|
|
16
|
+
invalidate: () => {
|
|
17
|
+
current = { generation: ++generation }
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onDestroy } from 'svelte'
|
|
3
|
+
import { Bot, ChevronDown, FolderSearch, Import, Plus, Search } from '@lucide/svelte'
|
|
4
|
+
import { Button } from '#lib/components/ui/button/index.js'
|
|
5
|
+
import * as Dialog from '#lib/components/ui/dialog/index.js'
|
|
6
|
+
import * as DropdownMenu from '#lib/components/ui/dropdown-menu/index.js'
|
|
7
|
+
import * as Field from '#lib/components/ui/field/index.js'
|
|
8
|
+
import { Input } from '#lib/components/ui/input/index.js'
|
|
9
|
+
import { getKitContext } from '../context'
|
|
10
|
+
import {
|
|
11
|
+
isKitClientError,
|
|
12
|
+
type Preview,
|
|
13
|
+
type PreparedSkill,
|
|
14
|
+
type SkillMeta,
|
|
15
|
+
type Source,
|
|
16
|
+
type SkillCandidate,
|
|
17
|
+
type SystemSkillCandidate,
|
|
18
|
+
} from '../ports'
|
|
19
|
+
import { errorText } from './error'
|
|
20
|
+
import SkillPreviewDialog from './SkillPreviewDialog.svelte'
|
|
21
|
+
import SkillAuthoring from './SkillAuthoring.svelte'
|
|
22
|
+
|
|
23
|
+
let {
|
|
24
|
+
oninstalled,
|
|
25
|
+
onmanual,
|
|
26
|
+
disabled = false,
|
|
27
|
+
}: {
|
|
28
|
+
oninstalled: (skills: SkillMeta[]) => void
|
|
29
|
+
onmanual: () => void
|
|
30
|
+
disabled?: boolean
|
|
31
|
+
} = $props()
|
|
32
|
+
const { ports, i18n, notify } = getKitContext()
|
|
33
|
+
const { t } = i18n
|
|
34
|
+
const port = ports.skills
|
|
35
|
+
const sources = ports.skillSources
|
|
36
|
+
type Mode = 'agent' | 'online' | 'system' | 'local'
|
|
37
|
+
let mode = $state<Mode | null>(null)
|
|
38
|
+
let busy = $state(false)
|
|
39
|
+
let error = $state<string | null>(null)
|
|
40
|
+
let query = $state('')
|
|
41
|
+
let candidates = $state<SkillCandidate[]>([])
|
|
42
|
+
let system = $state<SystemSkillCandidate[]>([])
|
|
43
|
+
let searched = $state(false)
|
|
44
|
+
let file = $state<HTMLInputElement | null>(null)
|
|
45
|
+
let source: Source | null = null
|
|
46
|
+
let preview = $state<Preview | null>(null)
|
|
47
|
+
let origin = $state('')
|
|
48
|
+
let ignored = $state<string[]>([])
|
|
49
|
+
let operation: AbortController | null = null
|
|
50
|
+
let generation = 0
|
|
51
|
+
let alive = true
|
|
52
|
+
|
|
53
|
+
function invalidate(): void {
|
|
54
|
+
generation += 1
|
|
55
|
+
operation?.abort()
|
|
56
|
+
operation = null
|
|
57
|
+
busy = false
|
|
58
|
+
}
|
|
59
|
+
function close(): void {
|
|
60
|
+
invalidate()
|
|
61
|
+
mode = null
|
|
62
|
+
preview = null
|
|
63
|
+
source = null
|
|
64
|
+
error = null
|
|
65
|
+
}
|
|
66
|
+
onDestroy(() => {
|
|
67
|
+
alive = false
|
|
68
|
+
invalidate()
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
async function act(work: (signal: AbortSignal, current: () => boolean) => Promise<void>): Promise<void> {
|
|
72
|
+
invalidate()
|
|
73
|
+
const id = generation
|
|
74
|
+
const controller = new AbortController()
|
|
75
|
+
operation = controller
|
|
76
|
+
const current = (): boolean => alive && generation === id && !controller.signal.aborted
|
|
77
|
+
busy = true
|
|
78
|
+
error = null
|
|
79
|
+
try {
|
|
80
|
+
await work(controller.signal, current)
|
|
81
|
+
} catch (cause) {
|
|
82
|
+
if (current()) error = errorText(t, cause)
|
|
83
|
+
} finally {
|
|
84
|
+
if (current()) {
|
|
85
|
+
busy = false
|
|
86
|
+
operation = null
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function open(next: Mode): void {
|
|
91
|
+
close()
|
|
92
|
+
query = ''
|
|
93
|
+
candidates = []
|
|
94
|
+
system = []
|
|
95
|
+
searched = false
|
|
96
|
+
mode = next
|
|
97
|
+
if (next === 'system') void scan(false)
|
|
98
|
+
}
|
|
99
|
+
async function showPreview(
|
|
100
|
+
next: Source,
|
|
101
|
+
from: string,
|
|
102
|
+
current: () => boolean,
|
|
103
|
+
excluded: string[] = [],
|
|
104
|
+
): Promise<void> {
|
|
105
|
+
if (!port || !current()) return
|
|
106
|
+
const result = await port.preview(next)
|
|
107
|
+
if (!current()) return
|
|
108
|
+
source = next
|
|
109
|
+
origin = from
|
|
110
|
+
ignored = excluded
|
|
111
|
+
preview = result
|
|
112
|
+
}
|
|
113
|
+
function search(): Promise<void> {
|
|
114
|
+
return act(async (signal, current) => {
|
|
115
|
+
if (!sources) return
|
|
116
|
+
const result = await sources.search(query.trim(), { signal })
|
|
117
|
+
if (current()) {
|
|
118
|
+
candidates = result
|
|
119
|
+
searched = true
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
}
|
|
123
|
+
function prepare(candidate: SkillCandidate): Promise<void> {
|
|
124
|
+
return act(async (signal, current) => {
|
|
125
|
+
if (!sources) return
|
|
126
|
+
const prepared = await sources.prepare(candidate.url, { signal })
|
|
127
|
+
await showPreview(prepared.source, prepared.origin, current, prepared.ignored)
|
|
128
|
+
})
|
|
129
|
+
}
|
|
130
|
+
function scan(pick: boolean): Promise<void> {
|
|
131
|
+
return act(async (signal, current) => {
|
|
132
|
+
if (!sources) return
|
|
133
|
+
const found = await (pick ? sources.pickSearchDirectory({ signal }) : sources.discover({ signal }))
|
|
134
|
+
if (current() && found !== null) {
|
|
135
|
+
system = found
|
|
136
|
+
searched = true
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
function prepareSystem(candidate: SystemSkillCandidate): Promise<void> {
|
|
141
|
+
return act(async (signal, current) => {
|
|
142
|
+
if (!sources) return
|
|
143
|
+
const prepared = await sources.prepareSystem(candidate.path, { signal })
|
|
144
|
+
await showPreview(prepared.source, prepared.origin, current, prepared.ignored)
|
|
145
|
+
})
|
|
146
|
+
}
|
|
147
|
+
function pickDirectory(): Promise<void> {
|
|
148
|
+
return act(async (signal, current) => {
|
|
149
|
+
const prepared = await sources?.pickDirectory({ signal })
|
|
150
|
+
if (prepared) await showPreview(prepared.source, prepared.origin, current, prepared.ignored)
|
|
151
|
+
})
|
|
152
|
+
}
|
|
153
|
+
function pickFile(input: HTMLInputElement): void {
|
|
154
|
+
const picked = input.files?.[0]
|
|
155
|
+
input.value = ''
|
|
156
|
+
if (!picked) return
|
|
157
|
+
void act(async (_signal, current) => {
|
|
158
|
+
const next: Source = { bytes: { fileName: picked.name, bytes: [...new Uint8Array(await picked.arrayBuffer())] } }
|
|
159
|
+
await showPreview(next, picked.name, current)
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
function closePreview(): void {
|
|
163
|
+
invalidate()
|
|
164
|
+
preview = null
|
|
165
|
+
source = null
|
|
166
|
+
}
|
|
167
|
+
function install(replace: boolean): void {
|
|
168
|
+
const confirmed = preview
|
|
169
|
+
const from = source
|
|
170
|
+
if (!port || !confirmed || !from || busy) return
|
|
171
|
+
void act(async (_signal, current) => {
|
|
172
|
+
try {
|
|
173
|
+
const installed = await port.install(from, confirmed.digest, replace)
|
|
174
|
+
if (!current()) return
|
|
175
|
+
oninstalled(installed)
|
|
176
|
+
close()
|
|
177
|
+
notify.toast('success', t('skills.installed', { name: confirmed.name }))
|
|
178
|
+
} catch (cause) {
|
|
179
|
+
if (isKitClientError(cause) && cause.details.reason === 'content_changed') {
|
|
180
|
+
const refreshed = await port.preview(from)
|
|
181
|
+
if (current()) {
|
|
182
|
+
preview = refreshed
|
|
183
|
+
notify.toast('info', t('skills.content_changed'))
|
|
184
|
+
}
|
|
185
|
+
return
|
|
186
|
+
}
|
|
187
|
+
throw cause
|
|
188
|
+
}
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
</script>
|
|
192
|
+
|
|
193
|
+
<div class="flex gap-2">
|
|
194
|
+
<DropdownMenu.Root>
|
|
195
|
+
<DropdownMenu.Trigger>
|
|
196
|
+
{#snippet child({ props })}
|
|
197
|
+
<Button {...props} {disabled}
|
|
198
|
+
><Plus data-icon="inline-start" />{t('skills.add.title')}<ChevronDown data-icon="inline-end" /></Button
|
|
199
|
+
>
|
|
200
|
+
{/snippet}
|
|
201
|
+
</DropdownMenu.Trigger>
|
|
202
|
+
<DropdownMenu.Content align="start">
|
|
203
|
+
<DropdownMenu.Group>
|
|
204
|
+
{#if sources}
|
|
205
|
+
{#if sources.authoring && ports.providers}
|
|
206
|
+
<DropdownMenu.Item onSelect={() => open('agent')}><Bot />{t('skills.add.agent')}</DropdownMenu.Item>
|
|
207
|
+
{/if}
|
|
208
|
+
<DropdownMenu.Item onSelect={() => open('online')}><Search />{t('skills.add.online')}</DropdownMenu.Item>
|
|
209
|
+
<DropdownMenu.Item onSelect={() => open('system')}><FolderSearch />{t('skills.add.system')}</DropdownMenu.Item
|
|
210
|
+
>
|
|
211
|
+
{/if}
|
|
212
|
+
<DropdownMenu.Item onSelect={() => open('local')}><Import />{t('skills.add.local')}</DropdownMenu.Item>
|
|
213
|
+
</DropdownMenu.Group>
|
|
214
|
+
</DropdownMenu.Content>
|
|
215
|
+
</DropdownMenu.Root>
|
|
216
|
+
<Button variant="outline" {disabled} onclick={onmanual}>{t('skills.new')}</Button>
|
|
217
|
+
</div>
|
|
218
|
+
<input
|
|
219
|
+
bind:this={file}
|
|
220
|
+
type="file"
|
|
221
|
+
accept=".md,.zip"
|
|
222
|
+
class="hidden"
|
|
223
|
+
aria-label={t('skills.import')}
|
|
224
|
+
onchange={(e) => pickFile(e.currentTarget)}
|
|
225
|
+
/>
|
|
226
|
+
|
|
227
|
+
<Dialog.Root
|
|
228
|
+
open={mode !== null && mode !== 'agent' && preview === null}
|
|
229
|
+
onOpenChange={(value) => {
|
|
230
|
+
if (!value) close()
|
|
231
|
+
}}
|
|
232
|
+
>
|
|
233
|
+
<Dialog.Content class="flex max-h-[85vh] flex-col sm:max-w-2xl">
|
|
234
|
+
<Dialog.Header>
|
|
235
|
+
<Dialog.Title>{mode ? t(`skills.add.${mode}`) : t('skills.add.title')}</Dialog.Title>
|
|
236
|
+
<Dialog.Description>{t('skills.add.review_notice')}</Dialog.Description>
|
|
237
|
+
</Dialog.Header>
|
|
238
|
+
<div class="flex min-h-0 flex-col gap-4 overflow-y-auto">
|
|
239
|
+
{#if error}<p role="alert" class="text-destructive text-sm">{error}</p>{/if}
|
|
240
|
+
{#if mode === 'online'}
|
|
241
|
+
<form
|
|
242
|
+
onsubmit={(event) => {
|
|
243
|
+
event.preventDefault()
|
|
244
|
+
if (!busy && query.trim()) void search()
|
|
245
|
+
}}
|
|
246
|
+
>
|
|
247
|
+
<Field.Group>
|
|
248
|
+
<Field.Field>
|
|
249
|
+
<Field.Label for="skill-source-query">{t('skills.add.query')}</Field.Label>
|
|
250
|
+
<Input
|
|
251
|
+
id="skill-source-query"
|
|
252
|
+
bind:value={query}
|
|
253
|
+
placeholder={t('skills.add.query_hint')}
|
|
254
|
+
disabled={busy}
|
|
255
|
+
/>
|
|
256
|
+
</Field.Field>
|
|
257
|
+
<Button type="submit" disabled={busy || !query.trim()}
|
|
258
|
+
><Search data-icon="inline-start" />{t('skills.add.search')}</Button
|
|
259
|
+
>
|
|
260
|
+
</Field.Group>
|
|
261
|
+
</form>
|
|
262
|
+
<ul class="flex flex-col gap-2" aria-label={t('skills.add.results')}>
|
|
263
|
+
{#each candidates as candidate (candidate.url)}
|
|
264
|
+
<li class="flex items-start gap-3 rounded-md border p-3">
|
|
265
|
+
<div class="flex min-w-0 flex-1 flex-col gap-1">
|
|
266
|
+
<strong class="text-sm">{candidate.name}</strong>
|
|
267
|
+
<p class="text-muted-foreground text-xs">{candidate.description}</p>
|
|
268
|
+
<p class="text-muted-foreground break-all text-xs">{candidate.origin} · {candidate.url}</p>
|
|
269
|
+
</div>
|
|
270
|
+
<Button size="sm" variant="outline" disabled={busy} onclick={() => void prepare(candidate)}
|
|
271
|
+
>{t('skills.add.preview')}</Button
|
|
272
|
+
>
|
|
273
|
+
</li>
|
|
274
|
+
{/each}
|
|
275
|
+
</ul>
|
|
276
|
+
{:else if mode === 'system'}
|
|
277
|
+
<p class="text-muted-foreground text-sm">{t('skills.add.system_hint')}</p>
|
|
278
|
+
<div class="flex flex-wrap gap-2">
|
|
279
|
+
<Button variant="outline" disabled={busy} onclick={() => void scan(false)}>{t('skills.add.rescan')}</Button>
|
|
280
|
+
<Button variant="outline" disabled={busy} onclick={() => void scan(true)}
|
|
281
|
+
><FolderSearch data-icon="inline-start" />{t('skills.add.pick_search_directory')}</Button
|
|
282
|
+
>
|
|
283
|
+
</div>
|
|
284
|
+
<ul class="flex flex-col gap-2" aria-label={t('skills.add.results')}>
|
|
285
|
+
{#each system as candidate (candidate.path)}
|
|
286
|
+
<li class="flex items-start gap-3 rounded-md border p-3">
|
|
287
|
+
<div class="flex min-w-0 flex-1 flex-col gap-1">
|
|
288
|
+
<strong class="text-sm">{candidate.name}</strong>
|
|
289
|
+
<p class="text-muted-foreground break-all text-xs">{candidate.origin} · {candidate.path}</p>
|
|
290
|
+
</div>
|
|
291
|
+
<Button size="sm" variant="outline" disabled={busy} onclick={() => void prepareSystem(candidate)}
|
|
292
|
+
>{t('skills.add.preview')}</Button
|
|
293
|
+
>
|
|
294
|
+
</li>
|
|
295
|
+
{/each}
|
|
296
|
+
</ul>
|
|
297
|
+
{:else if mode === 'local'}
|
|
298
|
+
<p class="text-muted-foreground text-sm">{t('skills.add.local_hint')}</p>
|
|
299
|
+
<div class="flex flex-wrap gap-2">
|
|
300
|
+
<Button disabled={busy} onclick={() => file?.click()}
|
|
301
|
+
><Import data-icon="inline-start" />{t('skills.import')}</Button
|
|
302
|
+
>
|
|
303
|
+
<Button variant="outline" disabled={busy || !sources} onclick={() => void pickDirectory()}
|
|
304
|
+
><FolderSearch data-icon="inline-start" />{t('skills.add.pick_directory')}</Button
|
|
305
|
+
>
|
|
306
|
+
</div>
|
|
307
|
+
{/if}
|
|
308
|
+
{#if busy}<p role="status" class="text-muted-foreground text-sm">{t('skills.add.working')}</p>{/if}
|
|
309
|
+
{#if !busy && searched && ((mode === 'online' && candidates.length === 0) || (mode === 'system' && system.length === 0))}
|
|
310
|
+
<p role="status" class="text-muted-foreground text-sm">{t('skills.add.no_results')}</p>
|
|
311
|
+
{/if}
|
|
312
|
+
</div>
|
|
313
|
+
<Dialog.Footer><Button variant="outline" onclick={close}>{t('common.cancel')}</Button></Dialog.Footer>
|
|
314
|
+
</Dialog.Content>
|
|
315
|
+
</Dialog.Root>
|
|
316
|
+
|
|
317
|
+
<SkillAuthoring
|
|
318
|
+
active={mode === 'agent'}
|
|
319
|
+
open={mode === 'agent' && preview === null}
|
|
320
|
+
onclose={close}
|
|
321
|
+
onprepared={async (prepared: PreparedSkill) => {
|
|
322
|
+
const id = generation
|
|
323
|
+
await showPreview(
|
|
324
|
+
prepared.source,
|
|
325
|
+
prepared.origin,
|
|
326
|
+
() => alive && generation === id && mode === 'agent',
|
|
327
|
+
prepared.ignored,
|
|
328
|
+
)
|
|
329
|
+
}}
|
|
330
|
+
/>
|
|
331
|
+
|
|
332
|
+
<SkillPreviewDialog
|
|
333
|
+
bind:open={
|
|
334
|
+
() => preview !== null,
|
|
335
|
+
(value) => {
|
|
336
|
+
if (!value) closePreview()
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
skill={preview}
|
|
340
|
+
title={t('skills.preview_title')}
|
|
341
|
+
current={preview?.current ?? null}
|
|
342
|
+
{busy}
|
|
343
|
+
{error}
|
|
344
|
+
{origin}
|
|
345
|
+
{ignored}
|
|
346
|
+
onconfirm={install}
|
|
347
|
+
/>
|