@yunlefun/vue 0.1.2 → 0.2.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/README.md +6 -0
- package/components/YlfAiPromptLauncher.vue +325 -0
- package/components/_ai-providers/README.md +9 -0
- package/components/_ai-providers/chatgpt.svg +1 -0
- package/components/_ai-providers/claude.svg +1 -0
- package/components/_ai-providers/cursor.svg +1 -0
- package/components/_ai-providers/deepseek.svg +6 -0
- package/components/_ai-providers/doubao.png +0 -0
- package/components/_ai-providers/yuanbao.png +0 -0
- package/components/ai-prompt-launcher/demo.vue +14 -0
- package/components/ai-prompt-launcher/index.md +116 -0
- package/components/ai-prompt.ts +103 -0
- package/components/ai-provider-logos.ts +11 -0
- package/dist/ai-prompt.cjs +65 -0
- package/dist/ai-prompt.d.cts +36 -0
- package/dist/ai-prompt.d.mts +36 -0
- package/dist/ai-prompt.d.ts +36 -0
- package/dist/ai-prompt.mjs +61 -0
- package/package.json +21 -2
package/README.md
CHANGED
|
@@ -39,3 +39,9 @@ export default defineNuxtConfig({
|
|
|
39
39
|
根入口目前提供类型和组件目录信息,组件通过 `components/*.vue` 子路径或 Nuxt 模块使用;没有公开的 `YlfResolver`。
|
|
40
40
|
|
|
41
41
|
更多内容见 [Vue 接入](./index.md)、[子包职责](../guide/packages.md)与[设计体系](../guide/design-system.md)。
|
|
42
|
+
|
|
43
|
+
## AI 提示词入口
|
|
44
|
+
|
|
45
|
+
`YlfAiPromptLauncher` 统一元宝、豆包、DeepSeek、ChatGPT、Claude 与 Cursor 的 logo、带提示词跳转和完整复制兜底。`@yunlefun/vue/ai-prompt` 提供可用于 CMS 主进程的纯链接构建函数;Vue 组件通过 `launch` / `copyText` 对接宿主。
|
|
46
|
+
|
|
47
|
+
接口、站点能力与接入示例见 [AI 提示词入口](./components/ai-prompt-launcher/index.md)。
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import type { AiPromptLaunch, AiPromptLaunchResult, AiPromptOptions, AiPromptTarget, AiProviderId } from './ai-prompt'
|
|
3
|
+
import { computed, nextTick, shallowRef, useId, useTemplateRef, watch } from 'vue'
|
|
4
|
+
import { aiProviders, buildAiPromptTarget, copyAndOpenAiPrompt } from './ai-prompt'
|
|
5
|
+
import { aiProviderLogos } from './ai-provider-logos'
|
|
6
|
+
|
|
7
|
+
const props = withDefaults(defineProps<{
|
|
8
|
+
prompt: string
|
|
9
|
+
providers?: readonly AiProviderId[]
|
|
10
|
+
disabled?: boolean
|
|
11
|
+
locale?: 'zh-CN' | 'en'
|
|
12
|
+
options?: AiPromptOptions
|
|
13
|
+
/** Optional host adapter, e.g. a validated desktop IPC call. */
|
|
14
|
+
launch?: AiPromptLaunch
|
|
15
|
+
/** Optional native clipboard adapter. */
|
|
16
|
+
copyText?: (text: string) => Promise<void>
|
|
17
|
+
}>(), { locale: 'zh-CN' })
|
|
18
|
+
const emit = defineEmits<{
|
|
19
|
+
result: [result: AiPromptLaunchResult]
|
|
20
|
+
busy: [value: boolean]
|
|
21
|
+
/** User copied from the full-prompt field, including the manual fallback. */
|
|
22
|
+
manualCopy: [prompt: string]
|
|
23
|
+
}>()
|
|
24
|
+
const id = useId()
|
|
25
|
+
const busy = shallowRef(false)
|
|
26
|
+
const message = shallowRef('')
|
|
27
|
+
const expanded = shallowRef(false)
|
|
28
|
+
const fallback = shallowRef<AiPromptTarget>()
|
|
29
|
+
const manualPrompt = useTemplateRef<HTMLTextAreaElement>('manual-prompt')
|
|
30
|
+
const unavailable = computed(() => props.disabled || busy.value || !props.prompt.trim())
|
|
31
|
+
const targets = computed(() => [...new Set(props.providers ?? aiProviders.map(provider => provider.id))].map(provider => buildAiPromptTarget(provider, props.prompt, props.options)))
|
|
32
|
+
const text = (zh: string, en: string) => props.locale === 'en' ? en : zh
|
|
33
|
+
let revision = 0
|
|
34
|
+
watch(() => props.prompt, () => {
|
|
35
|
+
revision++
|
|
36
|
+
message.value = ''
|
|
37
|
+
fallback.value = undefined
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
function setBusy(value: boolean) {
|
|
41
|
+
busy.value = value
|
|
42
|
+
emit('busy', value)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function manualCopy() {
|
|
46
|
+
expanded.value = true
|
|
47
|
+
message.value = text('自动复制未成功,请手动复制下方完整提示词。', 'Copy failed. Select and copy the full prompt below.')
|
|
48
|
+
await nextTick()
|
|
49
|
+
manualPrompt.value?.focus()
|
|
50
|
+
manualPrompt.value?.select()
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function copyOnly() {
|
|
54
|
+
if (unavailable.value)
|
|
55
|
+
return
|
|
56
|
+
const currentRevision = revision
|
|
57
|
+
setBusy(true)
|
|
58
|
+
try {
|
|
59
|
+
await (props.copyText ? props.copyText(props.prompt) : navigator.clipboard.writeText(props.prompt))
|
|
60
|
+
if (currentRevision === revision)
|
|
61
|
+
message.value = text('提示词已复制,可粘贴到你常用的 AI。', 'Prompt copied. Paste it into your preferred AI.')
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
if (currentRevision === revision)
|
|
65
|
+
await manualCopy()
|
|
66
|
+
}
|
|
67
|
+
finally { setBusy(false) }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function action(target: AiPromptTarget) {
|
|
71
|
+
return target.mode === 'link'
|
|
72
|
+
? text('带提示词打开', 'Open with prompt')
|
|
73
|
+
: text('复制并打开', 'Copy and open')
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async function open(event: MouseEvent, target: AiPromptTarget) {
|
|
77
|
+
if (unavailable.value) {
|
|
78
|
+
event.preventDefault()
|
|
79
|
+
return
|
|
80
|
+
}
|
|
81
|
+
fallback.value = undefined
|
|
82
|
+
const currentRevision = revision
|
|
83
|
+
const prompt = props.prompt
|
|
84
|
+
if (target.mode === 'link' && !props.launch) {
|
|
85
|
+
message.value = text(`已前往 ${target.provider.name}。若未带入提示词,可在这里复制。`, `Opening ${target.provider.name}. If the prompt is missing, copy it here.`)
|
|
86
|
+
emit('result', { target, status: 'opened' })
|
|
87
|
+
return
|
|
88
|
+
}
|
|
89
|
+
event.preventDefault()
|
|
90
|
+
setBusy(true)
|
|
91
|
+
try {
|
|
92
|
+
const status = await (props.launch ? props.launch(target, prompt) : copyAndOpenAiPrompt(target, prompt))
|
|
93
|
+
emit('result', { target, status })
|
|
94
|
+
if (currentRevision !== revision)
|
|
95
|
+
return
|
|
96
|
+
if (status === 'copy-failed') {
|
|
97
|
+
fallback.value = target
|
|
98
|
+
await manualCopy()
|
|
99
|
+
}
|
|
100
|
+
else if (status === 'copied' || status === 'open-failed') {
|
|
101
|
+
fallback.value = target
|
|
102
|
+
message.value = status === 'copied'
|
|
103
|
+
? props.launch
|
|
104
|
+
? text('提示词已复制,但未能打开 AI,请重试或手动打开。', 'Prompt copied, but the AI could not open. Retry or open it manually.')
|
|
105
|
+
: text('提示词已复制,请点击下方链接继续。', 'Prompt copied. Use the link below to continue.')
|
|
106
|
+
: text('未能打开 AI,请重试或手动打开。', 'Could not open the AI. Retry or open it manually.')
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
message.value = target.mode === 'copy'
|
|
110
|
+
? text(`已复制,请在 ${target.provider.name} 粘贴并发送。`, `Copied. Paste and send in ${target.provider.name}.`)
|
|
111
|
+
: text(`已前往 ${target.provider.name}。若未带入提示词,可在这里复制。`, `Opening ${target.provider.name}. If the prompt is missing, copy it here.`)
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
if (currentRevision === revision) {
|
|
116
|
+
fallback.value = target
|
|
117
|
+
message.value = text('未能打开 AI,请复制提示词后重试。', 'Could not open the AI. Copy the prompt and try again.')
|
|
118
|
+
}
|
|
119
|
+
emit('result', { target, status: 'open-failed' })
|
|
120
|
+
}
|
|
121
|
+
finally { setBusy(false) }
|
|
122
|
+
}
|
|
123
|
+
</script>
|
|
124
|
+
|
|
125
|
+
<template>
|
|
126
|
+
<div class="ylf-ai-launcher" :aria-busy="busy">
|
|
127
|
+
<nav class="ylf-ai-launcher__providers" :aria-label="text('选择 AI,带上提示词', 'Choose an AI for this prompt')">
|
|
128
|
+
<component
|
|
129
|
+
:is="target.mode === 'link' && !launch ? 'a' : 'button'"
|
|
130
|
+
v-for="target in targets"
|
|
131
|
+
:key="target.provider.id"
|
|
132
|
+
class="ylf-ai-launcher__provider"
|
|
133
|
+
:data-provider="target.provider.id"
|
|
134
|
+
:href="target.mode === 'link' && !launch && !unavailable ? target.url : undefined"
|
|
135
|
+
:type="target.mode === 'link' && !launch ? undefined : 'button'"
|
|
136
|
+
:disabled="target.mode === 'link' && !launch ? undefined : unavailable"
|
|
137
|
+
:aria-disabled="unavailable"
|
|
138
|
+
:tabindex="unavailable ? -1 : undefined"
|
|
139
|
+
:aria-label="`${action(target)} ${target.provider.name}`"
|
|
140
|
+
:title="target.reason === 'too-long' ? text('内容较长,将复制完整提示词后打开', 'Long prompt: copy the full text and open') : undefined"
|
|
141
|
+
:target="target.mode === 'link' && !launch ? '_blank' : undefined"
|
|
142
|
+
:rel="target.mode === 'link' && !launch ? 'noopener noreferrer' : undefined"
|
|
143
|
+
:referrerpolicy="target.mode === 'link' && !launch ? 'no-referrer' : undefined"
|
|
144
|
+
@click="open($event, target)"
|
|
145
|
+
>
|
|
146
|
+
<img :src="aiProviderLogos[target.provider.id]" alt="" width="40" height="40">
|
|
147
|
+
<span class="ylf-ai-launcher__label">
|
|
148
|
+
<strong>{{ target.provider.name }}</strong>
|
|
149
|
+
<span>{{ action(target) }}</span>
|
|
150
|
+
</span>
|
|
151
|
+
<svg class="ylf-ai-launcher__arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" aria-hidden="true"><path d="M7 17 17 7M7 7h10v10" /></svg>
|
|
152
|
+
</component>
|
|
153
|
+
</nav>
|
|
154
|
+
<div class="ylf-ai-launcher__actions">
|
|
155
|
+
<button type="button" :disabled="unavailable" @click="copyOnly">
|
|
156
|
+
{{ text('仅复制提示词', 'Copy prompt only') }}
|
|
157
|
+
</button>
|
|
158
|
+
<span>{{ text('支持时直接带入;内容过长时复制完整提示词。', 'Opens with your prompt when supported; copies long prompts in full.') }}</span>
|
|
159
|
+
</div>
|
|
160
|
+
<p v-if="message" class="ylf-ai-launcher__status" role="status">
|
|
161
|
+
{{ message }}
|
|
162
|
+
</p>
|
|
163
|
+
<a v-if="fallback && !launch" :href="fallback.provider.url" target="_blank" rel="noopener noreferrer" class="ylf-ai-launcher__fallback">{{ text('打开', 'Open') }} {{ fallback.provider.name }}</a>
|
|
164
|
+
<details class="ylf-ai-launcher__details" :open="expanded" @toggle="expanded = ($event.target as HTMLDetailsElement).open">
|
|
165
|
+
<summary>{{ text('查看 / 手动复制提示词', 'View / manually copy prompt') }}</summary>
|
|
166
|
+
<label :for="`${id}-prompt`">{{ text('完整提示词', 'Full prompt') }}</label>
|
|
167
|
+
<textarea :id="`${id}-prompt`" ref="manual-prompt" :value="prompt" readonly rows="8" @copy="emit('manualCopy', prompt)" />
|
|
168
|
+
</details>
|
|
169
|
+
</div>
|
|
170
|
+
</template>
|
|
171
|
+
|
|
172
|
+
<style scoped>
|
|
173
|
+
.ylf-ai-launcher {
|
|
174
|
+
--_ink: var(--ylf-ai-text, var(--ylf-c-text, #0f172a));
|
|
175
|
+
--_muted: var(--ylf-ai-muted, var(--ylf-c-text-2, #475569));
|
|
176
|
+
--_border: var(--ylf-ai-border, var(--ylf-c-border, #e2e8f0));
|
|
177
|
+
--_surface: var(--ylf-ai-surface, var(--ylf-c-surface, #fff));
|
|
178
|
+
--_soft: var(--ylf-ai-background, var(--ylf-c-bg-soft, #f8fafc));
|
|
179
|
+
--_brand: var(--ylf-ai-accent, var(--ylf-c-brand, #2563eb));
|
|
180
|
+
color: var(--_ink);
|
|
181
|
+
container: ylf-ai / inline-size;
|
|
182
|
+
}
|
|
183
|
+
.ylf-ai-launcher__providers {
|
|
184
|
+
display: grid;
|
|
185
|
+
grid-template-columns: repeat(var(--ylf-ai-columns, 3), minmax(0, 1fr));
|
|
186
|
+
gap: var(--ylf-ai-gap, 10px);
|
|
187
|
+
margin: 12px 0;
|
|
188
|
+
}
|
|
189
|
+
.ylf-ai-launcher__provider {
|
|
190
|
+
box-sizing: border-box;
|
|
191
|
+
display: flex;
|
|
192
|
+
align-items: center;
|
|
193
|
+
gap: var(--ylf-ai-item-gap, 10px);
|
|
194
|
+
min-width: 0;
|
|
195
|
+
min-height: var(--ylf-ai-item-height, 76px);
|
|
196
|
+
padding: var(--ylf-ai-item-padding, 12px);
|
|
197
|
+
border: 1px solid var(--_border);
|
|
198
|
+
border-radius: var(--ylf-ai-radius, var(--ylf-radius, 14px));
|
|
199
|
+
background: var(--_soft);
|
|
200
|
+
color: inherit;
|
|
201
|
+
text-decoration: none;
|
|
202
|
+
font: inherit;
|
|
203
|
+
text-align: left;
|
|
204
|
+
cursor: pointer;
|
|
205
|
+
transition:
|
|
206
|
+
border-color 160ms ease,
|
|
207
|
+
background-color 160ms ease;
|
|
208
|
+
}
|
|
209
|
+
.ylf-ai-launcher__provider:not([aria-disabled='true']):hover {
|
|
210
|
+
border-color: var(--_brand);
|
|
211
|
+
background: var(--_surface);
|
|
212
|
+
}
|
|
213
|
+
.ylf-ai-launcher__provider img {
|
|
214
|
+
box-sizing: border-box;
|
|
215
|
+
flex: none;
|
|
216
|
+
width: var(--ylf-ai-logo-size, 40px);
|
|
217
|
+
height: var(--ylf-ai-logo-size, 40px);
|
|
218
|
+
padding: 4px;
|
|
219
|
+
border-radius: 10px;
|
|
220
|
+
background: #fff;
|
|
221
|
+
object-fit: contain;
|
|
222
|
+
}
|
|
223
|
+
.ylf-ai-launcher__label {
|
|
224
|
+
display: grid;
|
|
225
|
+
gap: 3px;
|
|
226
|
+
min-width: 0;
|
|
227
|
+
}
|
|
228
|
+
.ylf-ai-launcher__label strong {
|
|
229
|
+
font-size: var(--ylf-ai-name-size, 14px);
|
|
230
|
+
line-height: 1.4;
|
|
231
|
+
}
|
|
232
|
+
.ylf-ai-launcher__label > span {
|
|
233
|
+
color: var(--_muted);
|
|
234
|
+
font-size: var(--ylf-ai-action-size, 12px);
|
|
235
|
+
line-height: 1.5;
|
|
236
|
+
}
|
|
237
|
+
.ylf-ai-launcher__arrow {
|
|
238
|
+
flex: none;
|
|
239
|
+
width: 16px;
|
|
240
|
+
height: 16px;
|
|
241
|
+
margin-left: auto;
|
|
242
|
+
color: var(--_muted);
|
|
243
|
+
}
|
|
244
|
+
.ylf-ai-launcher__actions {
|
|
245
|
+
display: flex;
|
|
246
|
+
flex-wrap: wrap;
|
|
247
|
+
align-items: center;
|
|
248
|
+
gap: 10px;
|
|
249
|
+
}
|
|
250
|
+
.ylf-ai-launcher__actions button {
|
|
251
|
+
min-height: 44px;
|
|
252
|
+
padding: 8px 14px;
|
|
253
|
+
border: 1px solid var(--_border);
|
|
254
|
+
border-radius: 999px;
|
|
255
|
+
background: var(--_surface);
|
|
256
|
+
color: var(--_ink);
|
|
257
|
+
font: inherit;
|
|
258
|
+
font-size: 13px;
|
|
259
|
+
cursor: pointer;
|
|
260
|
+
}
|
|
261
|
+
.ylf-ai-launcher__actions > span,
|
|
262
|
+
.ylf-ai-launcher__status {
|
|
263
|
+
color: var(--_muted);
|
|
264
|
+
font-size: 12px;
|
|
265
|
+
line-height: 1.7;
|
|
266
|
+
}
|
|
267
|
+
.ylf-ai-launcher__fallback {
|
|
268
|
+
display: inline-flex;
|
|
269
|
+
align-items: center;
|
|
270
|
+
min-height: 44px;
|
|
271
|
+
color: var(--_brand);
|
|
272
|
+
}
|
|
273
|
+
.ylf-ai-launcher__details {
|
|
274
|
+
margin-top: 8px;
|
|
275
|
+
font-size: 13px;
|
|
276
|
+
}
|
|
277
|
+
.ylf-ai-launcher__details summary {
|
|
278
|
+
display: list-item;
|
|
279
|
+
min-height: 44px;
|
|
280
|
+
padding: 12px 0;
|
|
281
|
+
box-sizing: border-box;
|
|
282
|
+
color: var(--_muted);
|
|
283
|
+
cursor: pointer;
|
|
284
|
+
}
|
|
285
|
+
.ylf-ai-launcher__details label {
|
|
286
|
+
display: block;
|
|
287
|
+
margin: 8px 0;
|
|
288
|
+
}
|
|
289
|
+
.ylf-ai-launcher__details textarea {
|
|
290
|
+
box-sizing: border-box;
|
|
291
|
+
width: 100%;
|
|
292
|
+
padding: 12px;
|
|
293
|
+
border: 1px solid var(--_border);
|
|
294
|
+
border-radius: 10px;
|
|
295
|
+
background: var(--_surface);
|
|
296
|
+
color: var(--_ink);
|
|
297
|
+
font: inherit;
|
|
298
|
+
font-size: 16px;
|
|
299
|
+
line-height: 1.7;
|
|
300
|
+
resize: vertical;
|
|
301
|
+
}
|
|
302
|
+
:is(button, a, textarea, summary):focus-visible {
|
|
303
|
+
outline: 3px solid var(--_brand);
|
|
304
|
+
outline-offset: 3px;
|
|
305
|
+
}
|
|
306
|
+
:is(button:disabled, [aria-disabled='true']) {
|
|
307
|
+
opacity: 0.5;
|
|
308
|
+
cursor: not-allowed;
|
|
309
|
+
}
|
|
310
|
+
@container ylf-ai (max-width: 620px) {
|
|
311
|
+
.ylf-ai-launcher__providers {
|
|
312
|
+
grid-template-columns: repeat(var(--ylf-ai-columns, 2), minmax(0, 1fr));
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
@container ylf-ai (max-width: 400px) {
|
|
316
|
+
.ylf-ai-launcher__providers {
|
|
317
|
+
grid-template-columns: repeat(var(--ylf-ai-columns, 1), minmax(0, 1fr));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
@media (prefers-reduced-motion: reduce) {
|
|
321
|
+
.ylf-ai-launcher__provider {
|
|
322
|
+
transition: none;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
</style>
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# AI provider marks
|
|
2
|
+
|
|
3
|
+
Bundled locally for recognition, not affiliation. Names and marks belong to their owners.
|
|
4
|
+
|
|
5
|
+
- Yuanbao: https://static.yuanbao.tencent.com/m/yuanbao-web/logo_with_bg.png
|
|
6
|
+
- Doubao: https://lf-flow-web-cdn.doubao.com/obj/flow-doubao/favicon/new-doubao/128x128.png
|
|
7
|
+
- DeepSeek: https://fe-static.deepseek.com/chat/favicon.svg
|
|
8
|
+
- Cursor: https://cursor.com/marketing-static/favicon-light.svg
|
|
9
|
+
- ChatGPT and Claude: `openai-icon` / `claude-icon` from `@iconify-json/logos@1.2.11` (SVG Logos collection). https://github.com/gilbarbara/logos
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 260"><path d="M239.184 106.203a64.72 64.72 0 0 0-5.576-53.103C219.452 28.459 191 15.784 163.213 21.74A65.586 65.586 0 0 0 52.096 45.22a64.72 64.72 0 0 0-43.23 31.36c-14.31 24.602-11.061 55.634 8.033 76.74a64.67 64.67 0 0 0 5.525 53.102c14.174 24.65 42.644 37.324 70.446 31.36a64.72 64.72 0 0 0 48.754 21.744c28.481.025 53.714-18.361 62.414-45.481a64.77 64.77 0 0 0 43.229-31.36c14.137-24.558 10.875-55.423-8.083-76.483m-97.56 136.338a48.4 48.4 0 0 1-31.105-11.255l1.535-.87l51.67-29.825a8.6 8.6 0 0 0 4.247-7.367v-72.85l21.845 12.636c.218.111.37.32.409.563v60.367c-.056 26.818-21.783 48.545-48.601 48.601M37.158 197.93a48.35 48.35 0 0 1-5.781-32.589l1.534.921l51.722 29.826a8.34 8.34 0 0 0 8.441 0l63.181-36.425v25.221a.87.87 0 0 1-.358.665l-52.335 30.184c-23.257 13.398-52.97 5.431-66.404-17.803M23.549 85.38a48.5 48.5 0 0 1 25.58-21.333v61.39a8.29 8.29 0 0 0 4.195 7.316l62.874 36.272l-21.845 12.636a.82.82 0 0 1-.767 0L41.353 151.53c-23.211-13.454-31.171-43.144-17.804-66.405zm179.466 41.695l-63.08-36.63L161.73 77.86a.82.82 0 0 1 .768 0l52.233 30.184a48.6 48.6 0 0 1-7.316 87.635v-61.391a8.54 8.54 0 0 0-4.4-7.213m21.742-32.69l-1.535-.922l-51.619-30.081a8.39 8.39 0 0 0-8.492 0L99.98 99.808V74.587a.72.72 0 0 1 .307-.665l52.233-30.133a48.652 48.652 0 0 1 72.236 50.391zM88.061 139.097l-21.845-12.585a.87.87 0 0 1-.41-.614V65.685a48.652 48.652 0 0 1 79.757-37.346l-1.535.87l-51.67 29.825a8.6 8.6 0 0 0-4.246 7.367zm11.868-25.58L128.067 97.3l28.188 16.218v32.434l-28.086 16.218l-28.188-16.218z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 257"><path fill="#d97757" d="m50.228 170.321l50.357-28.257l.843-2.463l-.843-1.361h-2.462l-8.426-.518l-28.775-.778l-24.952-1.037l-24.175-1.296l-6.092-1.297L0 125.796l.583-3.759l5.12-3.434l7.324.648l16.202 1.101l24.304 1.685l17.629 1.037l26.118 2.722h4.148l.583-1.685l-1.426-1.037l-1.101-1.037l-25.147-17.045l-27.22-18.017l-14.258-10.37l-7.713-5.25l-3.888-4.925l-1.685-10.758l7-7.713l9.397.649l2.398.648l9.527 7.323l20.35 15.75L94.817 91.9l3.889 3.24l1.555-1.102l.195-.777l-1.75-2.917l-14.453-26.118l-15.425-26.572l-6.87-11.018l-1.814-6.61c-.648-2.723-1.102-4.991-1.102-7.778l7.972-10.823L71.42 0l10.63 1.426l4.472 3.888l6.61 15.101l10.694 23.786l16.591 32.34l4.861 9.592l2.592 8.879l.973 2.722h1.685v-1.556l1.36-18.211l2.528-22.36l2.463-28.776l.843-8.1l4.018-9.722l7.971-5.25l6.222 2.981l5.12 7.324l-.713 4.73l-3.046 19.768l-5.962 30.98l-3.889 20.739h2.268l2.593-2.593l10.499-13.934l17.628-22.036l7.778-8.749l9.073-9.657l5.833-4.601h11.018l8.1 12.055l-3.628 12.443l-11.342 14.388l-9.398 12.184l-13.48 18.147l-8.426 14.518l.778 1.166l2.01-.194l30.46-6.481l16.462-2.982l19.637-3.37l8.88 4.148l.971 4.213l-3.5 8.62l-20.998 5.184l-24.628 4.926l-36.682 8.685l-.454.324l.519.648l16.526 1.555l7.065.389h17.304l32.21 2.398l8.426 5.574l5.055 6.805l-.843 5.184l-12.962 6.611l-17.498-4.148l-40.83-9.721l-14-3.5h-1.944v1.167l11.666 11.406l21.387 19.314l26.767 24.887l1.36 6.157l-3.434 4.86l-3.63-.518l-23.526-17.693l-9.073-7.972l-20.545-17.304h-1.36v1.814l4.73 6.935l25.017 37.59l1.296 11.536l-1.814 3.76l-6.481 2.268l-7.13-1.297l-14.647-20.544l-15.1-23.138l-12.185-20.739l-1.49.843l-7.194 77.448l-3.37 3.953l-7.778 2.981l-6.48-4.925l-3.436-7.972l3.435-15.749l4.148-20.544l3.37-16.333l3.046-20.285l1.815-6.74l-.13-.454l-1.49.194l-15.295 20.999l-23.267 31.433l-18.406 19.702l-4.407 1.75l-7.648-3.954l.713-7.064l4.277-6.286l25.47-32.405l15.36-20.092l9.917-11.6l-.065-1.686h-.583L44.07 198.125l-12.055 1.555l-5.185-4.86l.648-7.972l2.463-2.593l20.35-13.999z"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" height="512" viewBox="0 0 512 512" width="512" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><clipPath id="a"><path d="m90 67h331.963v378.45h-331.963z"/></clipPath><path d="m511.998 325.499c0 7.124 0 14.24-.041 21.364-.034 5.999-.103 11.998-.267 17.99-.357 13.068-1.125 26.245-3.449 39.169-2.359 13.109-6.205 25.306-12.266 37.222-5.958 11.703-13.746 22.419-23.029 31.709-9.29 9.291-20 17.072-31.71 23.03-11.909 6.061-24.113 9.907-37.222 12.266-12.923 2.324-26.101 3.092-39.168 3.448-6 .165-11.992.233-17.991.268-7.123.048-14.24.041-21.364.041h-139c-7.124 0-14.24 0-21.364-.041-5.999-.035-11.998-.103-17.99-.268-13.068-.356-26.245-1.124-39.169-3.448-13.1089-2.359-25.3059-6.205-37.2218-12.266-11.7034-5.958-22.4195-13.746-31.7095-23.03-9.29-9.29-17.0717-19.999-23.0297-31.709-6.06075-11.909-9.90702-24.113-12.26552-37.222-2.32422-12.924-3.092103-26.101-3.448621-39.169-.164546-5.999-.2331072-11.991-.2673877-17.99-.02742442-7.124-.02742442-14.24-.02742442-21.364v-139c0-7.124 0-14.24.04113662-21.364.0342805-5.999.1028415-11.998.2673875-17.99.356518-13.068 1.124399-26.245 3.448619-39.169 2.3585-13.1091 6.20477-25.3061 12.26561-37.222 5.9579-11.7034 13.7464-22.4195 23.0296-31.7095 9.29-9.29 19.9993-17.0717 31.7095-23.0296 11.909-6.06082 24.1129-9.90709 37.2221-12.26559 12.923-2.32422 26.101-3.092101 39.169-3.448618 5.999-.164547 11.991-.2331075 17.99-.2673881 7.117-.03428046 14.233-.03428046 21.357-.03428046h139c7.124 0 14.24 0 21.364.04113656 5.999.0342806 11.998.102842 17.99.267388 13.068.356517 26.245 1.124402 39.169 3.448622 13.109 2.3585 25.306 6.20477 37.222 12.26553 11.703 5.958 22.419 13.7465 31.71 23.0297 9.29 9.29 17.071 19.9992 23.029 31.7094 6.061 11.9091 9.907 24.113 12.266 37.2222 2.324 12.923 3.092 26.101 3.448 39.169.165 5.999.233 11.991.268 17.99.048 7.123.041 14.24.041 21.364v139z" fill="#f7f7f4"/><path d="m186.498 3.99902h139c7.125 0 14.231-.00004 21.341.04102 5.985.0342 11.952.10221 17.903.26562h.001c13.003.35475 25.944 1.11709 38.569 3.3877 12.775 2.29828 24.592 6.03144 36.118 11.89354v-.001c10.974 5.5867 21.052 12.8384 29.848 21.4571l.847.8379c8.993 8.993 16.525 19.3593 22.292 30.6933l3.565-1.8135-3.565 1.8145c5.678 11.1575 9.36 22.6024 11.674 34.9208l.22 1.195c2.128 11.837 2.931 23.95 3.315 36.132l.072 2.438c.164 5.958.232 11.919.266 17.904v.004c.048 7.107.041 14.207.041 21.337v129.344l-.007-.007v9.656c0 7.125 0 14.231-.041 21.341-.034 5.985-.102 11.952-.266 17.903v.001c-.354 13.003-1.117 25.944-3.387 38.569-2.227 12.376-5.8 23.853-11.351 35.037l-.543 1.08c-5.766 11.327-13.306 21.701-22.293 30.695-8.993 8.993-19.361 16.526-30.695 22.293-11.518 5.861-23.342 9.595-36.116 11.894-11.837 2.128-23.95 2.931-36.132 3.315l-2.438.072c-5.958.164-11.919.232-17.904.266h-.004c-7.107.048-14.207.041-21.337.041h-139c-7.125 0-14.231 0-21.341-.041-5.985-.034-11.952-.102-17.903-.266h-.001c-13.003-.355-25.944-1.117-38.57-3.387-12.7742-2.299-24.5914-6.032-36.1164-11.894h.0009c-10.974-5.587-21.0533-12.837-29.8496-21.456l-.8466-.838c-8.7118-8.712-16.0531-18.713-21.7461-29.634l-.5459-1.06-.544-1.081c-5.3709-10.816-8.8904-21.919-11.12986-33.84l-.21973-1.196c-2.27061-12.625-3.03294-25.566-3.38769-38.569v-.001c-.16342-5.958-.23143-11.918-.26563-17.903-.02736-7.112-.02734-14.219-.02734-21.341v-139c0-7.125-.00005-14.231.04101-21.341.0342-5.985.10222-11.952.26563-17.903v-.001c.35475-13.003 1.11699-25.944 3.38769-38.57 2.29829-12.7743 6.03152-24.5916 11.89352-36.1166l-.0009-.001c5.7665-11.3269 13.3073-21.6999 22.2939-30.6934 8.9932-8.9932 19.36-16.5261 30.6943-22.2929h.001c11.5173-5.8615 23.3407-9.59617 36.1148-11.89455 11.837-2.12878 23.951-2.93017 36.133-3.31446l2.438-.07226c5.958-.16342 11.919-.23142 17.904-.26563l-.001-.00097c7.105-.03422 14.211-.03321 21.335-.03321z" stroke="#26251e" stroke-opacity=".2" stroke-width="8"/><g clip-path="url(#a)"><path d="m415.348 156.575-151.504-87.4699c-4.865-2.8094-10.868-2.8094-15.733 0l-151.4964 87.4699c-4.0897 2.361-6.6146 6.728-6.6146 11.458v176.383c0 4.73 2.5249 9.097 6.6146 11.459l151.5034 87.469c4.865 2.81 10.868 2.81 15.733 0l151.504-87.469c4.09-2.362 6.615-6.729 6.615-11.459v-176.383c0-4.73-2.525-9.097-6.615-11.458zm-9.517 18.528-146.254 253.319c-.989 1.707-3.599 1.01-3.599-.967v-165.871c0-3.314-1.771-6.38-4.645-8.044l-143.644-82.932c-1.707-.989-1.01-3.599.967-3.599h292.51c4.153 0 6.749 4.502 4.672 8.101h-.007z" fill="#26251e"/></g></svg>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg width="50.000000" height="50.000000" viewBox="0 0 50 50" fill="none" xmlns="http://www.w3.org/2000/svg"
|
|
2
|
+
xmlns:xlink="http://www.w3.org/1999/xlink">
|
|
3
|
+
<path id="path"
|
|
4
|
+
d="M48.8354 10.0479C48.3232 9.79199 48.1025 10.2798 47.8032 10.5278C47.7007 10.6079 47.6143 10.7119 47.5273 10.8076C46.7793 11.624 45.9048 12.1597 44.7622 12.0957C43.0923 12 41.666 12.5356 40.4058 13.8398C40.1377 12.2319 39.2476 11.272 37.8926 10.6558C37.1836 10.3359 36.4668 10.0156 35.9702 9.31982C35.6235 8.82373 35.5293 8.27197 35.356 7.72754C35.2456 7.3999 35.1353 7.06396 34.7651 7.00781C34.3633 6.94385 34.2056 7.2876 34.0479 7.57568C33.418 8.75195 33.1733 10.0479 33.1973 11.3599C33.2524 14.312 34.4736 16.6641 36.8999 18.3359C37.1758 18.5278 37.2466 18.7197 37.1597 19C36.9946 19.5757 36.7974 20.1357 36.624 20.7119C36.5137 21.0801 36.3486 21.1597 35.9624 21C34.6309 20.4321 33.481 19.5918 32.4644 18.5757C30.7393 16.8721 29.1792 14.9917 27.2334 13.52C26.7764 13.1758 26.3193 12.856 25.8467 12.5518C23.8618 10.584 26.1069 8.96777 26.627 8.77588C27.1704 8.57568 26.8159 7.8877 25.0591 7.896C23.3022 7.90381 21.6953 8.50391 19.647 9.30371C19.3477 9.42383 19.0322 9.51172 18.7095 9.58398C16.8501 9.22363 14.9199 9.14355 12.9033 9.37598C9.10596 9.80762 6.07275 11.6396 3.84326 14.7681C1.16455 18.5278 0.53418 22.7998 1.30664 27.2559C2.11768 31.9521 4.46582 35.8398 8.07373 38.8799C11.8159 42.0322 16.1255 43.5762 21.041 43.2803C24.0269 43.104 27.3516 42.6963 31.1016 39.4561C32.0469 39.936 33.0396 40.1279 34.686 40.272C35.9546 40.3921 37.1758 40.208 38.1211 40.0078C39.6021 39.688 39.4995 38.2881 38.9639 38.0322C34.623 35.9678 35.5762 36.8081 34.71 36.1279C36.9155 33.4639 40.2402 30.6958 41.54 21.728C41.6426 21.0161 41.5557 20.5679 41.54 19.9917C41.5322 19.6396 41.6108 19.5039 42.0049 19.4639C43.0923 19.3359 44.1479 19.0317 45.1167 18.4878C47.9292 16.9199 49.064 14.3438 49.3315 11.2559C49.3711 10.7837 49.3237 10.2959 48.8354 10.0479ZM24.3262 37.8398C20.1196 34.4639 18.0791 33.3521 17.2358 33.3999C16.4482 33.4482 16.5898 34.3682 16.7632 34.9678C16.9443 35.5601 17.1812 35.9683 17.5117 36.4878C17.7402 36.832 17.8979 37.3442 17.2832 37.728C15.9282 38.584 13.5728 37.4399 13.4624 37.3838C10.7207 35.7358 8.42822 33.5601 6.81348 30.584C5.25342 27.7197 4.34766 24.6479 4.19775 21.3677C4.1582 20.5757 4.38672 20.2959 5.15869 20.1519C6.17529 19.96 7.22314 19.9199 8.23926 20.0718C12.5327 20.7119 16.1885 22.6719 19.2529 25.7759C21.002 27.5439 22.3252 29.6558 23.6885 31.7202C25.1377 33.9121 26.6978 36 28.6831 37.7119C29.3843 38.312 29.9434 38.7681 30.479 39.104C28.8643 39.2881 26.1699 39.3281 24.3262 37.8398ZM26.3433 24.6001C26.3433 24.248 26.6191 23.9678 26.9658 23.9678C27.0444 23.9678 27.1152 23.9839 27.1782 24.0078C27.2651 24.04 27.3438 24.0879 27.4067 24.1602C27.5171 24.272 27.5801 24.4321 27.5801 24.6001C27.5801 24.9521 27.3042 25.2319 26.9575 25.2319C26.6108 25.2319 26.3433 24.9521 26.3433 24.6001ZM32.6064 27.8799C32.2046 28.0479 31.8027 28.1919 31.4165 28.208C30.8179 28.2397 30.1641 27.9922 29.8096 27.688C29.2583 27.2158 28.8643 26.9521 28.6987 26.1279C28.6279 25.7759 28.6675 25.2319 28.7305 24.9199C28.8721 24.248 28.7144 23.8159 28.2495 23.4238C27.8716 23.104 27.3911 23.0161 26.8633 23.0161C26.666 23.0161 26.4849 22.9277 26.3511 22.856C26.1304 22.7441 25.9492 22.4639 26.1226 22.1201C26.1777 22.0078 26.4458 21.7358 26.5088 21.688C27.2256 21.272 28.0527 21.4077 28.8169 21.7197C29.5259 22.0161 30.0615 22.5601 30.834 23.3281C31.6216 24.2559 31.7632 24.5117 32.2124 25.208C32.5669 25.752 32.8901 26.312 33.1104 26.9521C33.2446 27.3521 33.0713 27.6802 32.6064 27.8799Z"
|
|
5
|
+
fill="#4D6BFE" fill-opacity="1.000000" fill-rule="nonzero" />
|
|
6
|
+
</svg>
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { shallowRef } from 'vue'
|
|
3
|
+
import YlfAiPromptLauncher from '../YlfAiPromptLauncher.vue'
|
|
4
|
+
|
|
5
|
+
const prompt = shallowRef('请帮我把这段话写得更清楚:今天做一个让人开心的小作品。')
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<template>
|
|
9
|
+
<div>
|
|
10
|
+
<label for="ai-launcher-demo-prompt">想交给 AI 的提示词</label>
|
|
11
|
+
<textarea id="ai-launcher-demo-prompt" v-model="prompt" rows="3" style="display: block; width: 100%; padding: 12px; border: 1px solid var(--ylf-c-border); border-radius: 12px; font: inherit;" />
|
|
12
|
+
<YlfAiPromptLauncher :prompt="prompt" />
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: AI Prompt Launcher
|
|
3
|
+
title_zh: AI 提示词入口
|
|
4
|
+
description: 带服务 logo 的外部 AI 入口,统一提示词链接、复制兜底与桌面适配。
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
把提示词带到常用 AI。业务负责生成提示词与处理回复,组件负责入口、复制、状态提示和手动复制兜底。
|
|
8
|
+
|
|
9
|
+
## 使用
|
|
10
|
+
|
|
11
|
+
```vue
|
|
12
|
+
<script setup lang="ts">
|
|
13
|
+
import YlfAiPromptLauncher from '@yunlefun/vue/components/YlfAiPromptLauncher.vue'
|
|
14
|
+
</script>
|
|
15
|
+
|
|
16
|
+
<template>
|
|
17
|
+
<YlfAiPromptLauncher :prompt="prompt" :disabled="!valid" />
|
|
18
|
+
</template>
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
| 属性 | 说明 |
|
|
22
|
+
| ----------- | -------------------------------------------------------- |
|
|
23
|
+
| `prompt` | 必填的完整提示词;空白内容不可点击 |
|
|
24
|
+
| `providers` | 可选的服务 ID 列表,控制顺序和显示范围;默认六家 |
|
|
25
|
+
| `disabled` | 业务校验未通过时禁用 |
|
|
26
|
+
| `locale` | `zh-CN`(默认)或 `en` |
|
|
27
|
+
| `options` | `{ chatgptSearch?: boolean, maxUrlLength?: number }` |
|
|
28
|
+
| `launch` | 可选的宿主打开函数 `(target, prompt) => Promise<status>` |
|
|
29
|
+
| `copyText` | 可选的宿主剪贴板函数 `(text) => Promise<void>` |
|
|
30
|
+
|
|
31
|
+
事件:`busy(boolean)` 便于业务锁住输入;`result({ target, status })` 返回打开或复制结果。结果描述浏览器/宿主操作,无法证明第三方最终收到、预填或发送成功。
|
|
32
|
+
|
|
33
|
+
## 独立主题与样式覆盖
|
|
34
|
+
|
|
35
|
+
优先在组件的业务 class 上设置 `--ylf-ai-*`,只影响这个入口;未设置时沿用 `--ylf-c-*` 主题和组件默认值。无需图标插件,logo 随包分发。
|
|
36
|
+
|
|
37
|
+
| 变量 | 用途 / 默认值 |
|
|
38
|
+
| ------------------------------------------------------------------------------- | ----------------------------------------------- |
|
|
39
|
+
| `--ylf-ai-text`、`--ylf-ai-muted` | 主文字与说明文字 |
|
|
40
|
+
| `--ylf-ai-border`、`--ylf-ai-surface`、`--ylf-ai-background`、`--ylf-ai-accent` | 边框、表面、卡片底色、焦点强调 |
|
|
41
|
+
| `--ylf-ai-columns` | 固定列数;默认随容器宽度在 3 / 2 / 1 列之间切换 |
|
|
42
|
+
| `--ylf-ai-gap`、`--ylf-ai-item-gap` | 卡片间距、卡片内部间距,默认 `10px` |
|
|
43
|
+
| `--ylf-ai-item-height`、`--ylf-ai-item-padding` | 最小高度 `76px`、内边距 `12px` |
|
|
44
|
+
| `--ylf-ai-radius`、`--ylf-ai-logo-size` | 圆角 `14px`、logo `40px` |
|
|
45
|
+
| `--ylf-ai-name-size`、`--ylf-ai-action-size` | 服务名 `14px`、操作说明 `12px` |
|
|
46
|
+
|
|
47
|
+
```vue
|
|
48
|
+
<template>
|
|
49
|
+
<YlfAiPromptLauncher class="cms-ai-entry" :prompt="prompt" />
|
|
50
|
+
</template>
|
|
51
|
+
|
|
52
|
+
<style scoped>
|
|
53
|
+
.cms-ai-entry {
|
|
54
|
+
--ylf-ai-text: var(--ink);
|
|
55
|
+
--ylf-ai-muted: var(--muted);
|
|
56
|
+
--ylf-ai-border: var(--line);
|
|
57
|
+
--ylf-ai-surface: var(--paper);
|
|
58
|
+
--ylf-ai-background: var(--paper);
|
|
59
|
+
--ylf-ai-accent: var(--blue);
|
|
60
|
+
--ylf-ai-columns: 2;
|
|
61
|
+
--ylf-ai-radius: 8px;
|
|
62
|
+
--ylf-ai-logo-size: 28px;
|
|
63
|
+
--ylf-ai-item-padding: 8px;
|
|
64
|
+
}
|
|
65
|
+
/* 结构样式的额外定制可用 scoped :deep(),无需复制组件。 */
|
|
66
|
+
.cms-ai-entry :deep(.ylf-ai-launcher__actions) {
|
|
67
|
+
align-items: flex-start;
|
|
68
|
+
}
|
|
69
|
+
</style>
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
公开样式钩子:`.ylf-ai-launcher__providers`、`__provider`、`__label`、`__arrow`、`__actions`、`__status`、`__details`、`__fallback`。按服务覆盖可用 `[data-provider="cursor"]`。请保留禁用、键盘焦点和状态提示语义,不隐藏手动复制兜底。
|
|
73
|
+
|
|
74
|
+
`manual-copy(prompt)` 在用户复制完整提示词文本框时触发,CMS 可据此记录对应请求快照,保证剪贴板 API 不可用时仍能完成回填。该事件代表复制操作,不证明第三方收到了提示词。
|
|
75
|
+
|
|
76
|
+
## 链接能力
|
|
77
|
+
|
|
78
|
+
| ID | 入口 | 行为 |
|
|
79
|
+
| ---------- | ----------------------------------------- | ---------------------------------------- |
|
|
80
|
+
| `yuanbao` | `https://yuanbao.tencent.com/` | 未确认公开预填协议;复制后打开 |
|
|
81
|
+
| `doubao` | `https://www.doubao.com/chat/` | 站内 `url-action` 有校验令牌;复制后打开 |
|
|
82
|
+
| `deepseek` | `https://chat.deepseek.com/?q=...` | 预填,不添加 `autosend` |
|
|
83
|
+
| `chatgpt` | `https://chatgpt.com/?q=...` | 带提示词打开,站点可能直接开始对话 |
|
|
84
|
+
| `claude` | `https://claude.ai/new?q=...` | 带提示词打开,可能先登录或确认 |
|
|
85
|
+
| `cursor` | `https://cursor.com/link/prompt?text=...` | 官方 HTTPS 中转页,再由用户打开 Cursor |
|
|
86
|
+
|
|
87
|
+
没有统一的跨站协议,适配规则集中在 `ai-prompt.ts`。`chatgptSearch` 显式开启时才添加 `hints=search`,愿望故事和改稿默认不强制联网搜索。
|
|
88
|
+
|
|
89
|
+
默认 URL 编码后的预算是 8,192 个字符(组件的保守策略,不是各站官方上限)。Cursor 官方上限是 10,000,组件额外为 HTTPS 中转转成 `cursor://` 预留空间。超长提示词全部复制后打开;不截断、压缩或添加自动发送参数。用户也始终可以仅复制,或展开全文手动复制。登录、网络、第三方改版可能影响带入。
|
|
90
|
+
|
|
91
|
+
核验记录(2026-09-27):
|
|
92
|
+
|
|
93
|
+
- [Cursor 官方 Deeplinks](https://cursor.com/docs/reference/deeplinks):`text`、HTTPS 入口和 10,000 字符限制。
|
|
94
|
+
- [Claude 官方 Desktop 链接](https://support.claude.com/en/articles/14729294-open-claude-desktop-with-a-link):`q` 预填;该文档是桌面协议,不应当视为网页接口的稳定性保证。网页 `/new?q=` 本次保留参数进入登录流程,登录后的填入尚未验证。
|
|
95
|
+
- ChatGPT:按 `https://chatgpt.com/?q=...` 实测进入包含提示词的对话,行为可能直接发送,未找到公开稳定协议承诺。
|
|
96
|
+
- DeepSeek:当前网页实现支持 `q`,完整中文提示词在浏览器输入区实测匹配。
|
|
97
|
+
|
|
98
|
+
## 纯函数与 CMS / Electron
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
import { buildAiPromptTarget } from '@yunlefun/vue/ai-prompt'
|
|
102
|
+
|
|
103
|
+
const target = buildAiPromptTarget('cursor', articlePrompt)
|
|
104
|
+
// target.mode: 'link' | 'copy'
|
|
105
|
+
// target.reason: 'too-long' | 'unsupported' | 'empty' | undefined
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
模块可在 Node / SSR 使用,导入时不访问 `window` 或 `navigator`。组件不导入 Electron,也不持有 API Key。
|
|
109
|
+
|
|
110
|
+
CMS 接入时由文章工作区生成并保存 requestId / 原文快照,将完整提示词传入组件;通过 `launch` 和 `copyText` 接现有 preload API。主进程接收服务 ID、提示词及允许的选项,重新调用 `buildAiPromptTarget`,再决定写入原生剪贴板或 `shell.openExternal`。不要将任意 renderer URL 直接交给 `shell.openExternal`。
|
|
111
|
+
|
|
112
|
+
`launch` 返回 `opened`、`copied`、`copy-failed` 或 `open-failed`;`copied` 表示文本已复制但无法打开。组件可以手动复制兜底;桌面宿主应在自身 UI 中提供重试。复制、打开或用户手动复制后业务才确认本次请求快照,回复解析、差异确认、撤销和保存均留在 CMS。
|
|
113
|
+
|
|
114
|
+
## 分发
|
|
115
|
+
|
|
116
|
+
组件和纯函数统一通过 `@yunlefun/vue` 分发;业务只引用 npm 包并设置自己的 CSS 变量,不复制组件源码或链接规则。
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/** Services supported by the shared AI prompt launcher. */
|
|
2
|
+
export type AiProviderId = 'yuanbao' | 'doubao' | 'deepseek' | 'chatgpt' | 'claude' | 'cursor'
|
|
3
|
+
|
|
4
|
+
export interface AiProvider {
|
|
5
|
+
readonly id: AiProviderId
|
|
6
|
+
readonly name: string
|
|
7
|
+
readonly url: string
|
|
8
|
+
readonly promptParameter?: 'q' | 'text'
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Only public entry points belong here; never accept an arbitrary destination URL. */
|
|
12
|
+
export const aiProviders: readonly AiProvider[] = [
|
|
13
|
+
{ id: 'yuanbao', name: '元宝', url: 'https://yuanbao.tencent.com/' },
|
|
14
|
+
{ id: 'doubao', name: '豆包', url: 'https://www.doubao.com/chat/' },
|
|
15
|
+
{ id: 'deepseek', name: 'DeepSeek', url: 'https://chat.deepseek.com/', promptParameter: 'q' },
|
|
16
|
+
{ id: 'chatgpt', name: 'ChatGPT', url: 'https://chatgpt.com/', promptParameter: 'q' },
|
|
17
|
+
{ id: 'claude', name: 'Claude', url: 'https://claude.ai/new', promptParameter: 'q' },
|
|
18
|
+
{ id: 'cursor', name: 'Cursor', url: 'https://cursor.com/link/prompt', promptParameter: 'text' },
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
export interface AiPromptOptions {
|
|
22
|
+
/** Optional ChatGPT search hint. Ordinary creative/editorial prompts leave this off. */
|
|
23
|
+
chatgptSearch?: boolean
|
|
24
|
+
/** Conservative encoded URL budget; Cursor also has a hard 10,000-character limit. */
|
|
25
|
+
maxUrlLength?: number
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface AiPromptTarget {
|
|
29
|
+
provider: AiProvider
|
|
30
|
+
url: string
|
|
31
|
+
mode: 'link' | 'copy'
|
|
32
|
+
reason?: 'unsupported' | 'too-long' | 'empty'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Build a lossless handoff. Long prompts use the clipboard, never truncated query text. */
|
|
36
|
+
export function buildAiPromptTarget(id: AiProviderId, prompt: string, options: AiPromptOptions = {}): AiPromptTarget {
|
|
37
|
+
const provider = aiProviders.find(item => item.id === id)
|
|
38
|
+
if (!provider)
|
|
39
|
+
throw new Error(`Unknown AI provider: ${id}`)
|
|
40
|
+
const fallback = (reason: AiPromptTarget['reason']): AiPromptTarget => ({ provider, url: provider.url, mode: 'copy', reason })
|
|
41
|
+
if (!prompt.trim())
|
|
42
|
+
return fallback('empty')
|
|
43
|
+
if (!provider.promptParameter)
|
|
44
|
+
return fallback('unsupported')
|
|
45
|
+
|
|
46
|
+
const url = new URL(provider.url)
|
|
47
|
+
url.searchParams.set(provider.promptParameter, prompt)
|
|
48
|
+
if (id === 'chatgpt' && options.chatgptSearch)
|
|
49
|
+
url.searchParams.set('hints', 'search')
|
|
50
|
+
// Cursor's HTTPS landing page builds a slightly longer cursor:// deeplink.
|
|
51
|
+
const providerLimit = id === 'cursor' ? 9900 : Number.POSITIVE_INFINITY
|
|
52
|
+
const limit = Math.min(options.maxUrlLength ?? 8192, providerLimit)
|
|
53
|
+
if (!Number.isFinite(limit) || limit <= 0 || url.href.length > limit)
|
|
54
|
+
return fallback('too-long')
|
|
55
|
+
return { provider, url: url.href, mode: 'link' }
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export type AiPromptLaunchStatus = 'opened' | 'copied' | 'copy-failed' | 'open-failed'
|
|
59
|
+
export interface AiPromptLaunchResult {
|
|
60
|
+
target: AiPromptTarget
|
|
61
|
+
status: AiPromptLaunchStatus
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** A desktop host can supply an IPC-backed launcher without exposing Electron to this package. */
|
|
65
|
+
export type AiPromptLaunch = (target: AiPromptTarget, prompt: string) => Promise<AiPromptLaunchStatus>
|
|
66
|
+
|
|
67
|
+
/** Browser fallback. Call synchronously from a click to retain clipboard and popup activation. */
|
|
68
|
+
export async function copyAndOpenAiPrompt(target: AiPromptTarget, prompt: string): Promise<AiPromptLaunchStatus> {
|
|
69
|
+
let copying: Promise<void>
|
|
70
|
+
try {
|
|
71
|
+
copying = navigator.clipboard.writeText(prompt)
|
|
72
|
+
}
|
|
73
|
+
catch { return 'copy-failed' }
|
|
74
|
+
|
|
75
|
+
let popup: Window | null = null
|
|
76
|
+
try {
|
|
77
|
+
popup = window.open('about:blank', '_blank')
|
|
78
|
+
if (popup)
|
|
79
|
+
popup.opener = null
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
popup?.close()
|
|
83
|
+
popup = null
|
|
84
|
+
}
|
|
85
|
+
try {
|
|
86
|
+
await copying
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
popup?.close()
|
|
90
|
+
return 'copy-failed'
|
|
91
|
+
}
|
|
92
|
+
if (!popup || popup.closed)
|
|
93
|
+
return 'copied'
|
|
94
|
+
try {
|
|
95
|
+
// This helper is only the copy fallback: never navigate to a stale prompt query.
|
|
96
|
+
popup.location.replace(target.provider.url)
|
|
97
|
+
return 'opened'
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
popup.close()
|
|
101
|
+
return 'copied'
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { AiProviderId } from './ai-prompt'
|
|
2
|
+
|
|
3
|
+
/** Bundled assets: no favicon service or third-party image requests. */
|
|
4
|
+
export const aiProviderLogos: Record<AiProviderId, string> = {
|
|
5
|
+
yuanbao: new URL('./_ai-providers/yuanbao.png', import.meta.url).href,
|
|
6
|
+
doubao: new URL('./_ai-providers/doubao.png', import.meta.url).href,
|
|
7
|
+
deepseek: new URL('./_ai-providers/deepseek.svg', import.meta.url).href,
|
|
8
|
+
chatgpt: new URL('./_ai-providers/chatgpt.svg', import.meta.url).href,
|
|
9
|
+
claude: new URL('./_ai-providers/claude.svg', import.meta.url).href,
|
|
10
|
+
cursor: new URL('./_ai-providers/cursor.svg', import.meta.url).href,
|
|
11
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const aiProviders = [
|
|
4
|
+
{ id: "yuanbao", name: "\u5143\u5B9D", url: "https://yuanbao.tencent.com/" },
|
|
5
|
+
{ id: "doubao", name: "\u8C46\u5305", url: "https://www.doubao.com/chat/" },
|
|
6
|
+
{ id: "deepseek", name: "DeepSeek", url: "https://chat.deepseek.com/", promptParameter: "q" },
|
|
7
|
+
{ id: "chatgpt", name: "ChatGPT", url: "https://chatgpt.com/", promptParameter: "q" },
|
|
8
|
+
{ id: "claude", name: "Claude", url: "https://claude.ai/new", promptParameter: "q" },
|
|
9
|
+
{ id: "cursor", name: "Cursor", url: "https://cursor.com/link/prompt", promptParameter: "text" }
|
|
10
|
+
];
|
|
11
|
+
function buildAiPromptTarget(id, prompt, options = {}) {
|
|
12
|
+
const provider = aiProviders.find((item) => item.id === id);
|
|
13
|
+
if (!provider)
|
|
14
|
+
throw new Error(`Unknown AI provider: ${id}`);
|
|
15
|
+
const fallback = (reason) => ({ provider, url: provider.url, mode: "copy", reason });
|
|
16
|
+
if (!prompt.trim())
|
|
17
|
+
return fallback("empty");
|
|
18
|
+
if (!provider.promptParameter)
|
|
19
|
+
return fallback("unsupported");
|
|
20
|
+
const url = new URL(provider.url);
|
|
21
|
+
url.searchParams.set(provider.promptParameter, prompt);
|
|
22
|
+
if (id === "chatgpt" && options.chatgptSearch)
|
|
23
|
+
url.searchParams.set("hints", "search");
|
|
24
|
+
const providerLimit = id === "cursor" ? 9900 : Number.POSITIVE_INFINITY;
|
|
25
|
+
const limit = Math.min(options.maxUrlLength ?? 8192, providerLimit);
|
|
26
|
+
if (!Number.isFinite(limit) || limit <= 0 || url.href.length > limit)
|
|
27
|
+
return fallback("too-long");
|
|
28
|
+
return { provider, url: url.href, mode: "link" };
|
|
29
|
+
}
|
|
30
|
+
async function copyAndOpenAiPrompt(target, prompt) {
|
|
31
|
+
let copying;
|
|
32
|
+
try {
|
|
33
|
+
copying = navigator.clipboard.writeText(prompt);
|
|
34
|
+
} catch {
|
|
35
|
+
return "copy-failed";
|
|
36
|
+
}
|
|
37
|
+
let popup = null;
|
|
38
|
+
try {
|
|
39
|
+
popup = window.open("about:blank", "_blank");
|
|
40
|
+
if (popup)
|
|
41
|
+
popup.opener = null;
|
|
42
|
+
} catch {
|
|
43
|
+
popup?.close();
|
|
44
|
+
popup = null;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
await copying;
|
|
48
|
+
} catch {
|
|
49
|
+
popup?.close();
|
|
50
|
+
return "copy-failed";
|
|
51
|
+
}
|
|
52
|
+
if (!popup || popup.closed)
|
|
53
|
+
return "copied";
|
|
54
|
+
try {
|
|
55
|
+
popup.location.replace(target.provider.url);
|
|
56
|
+
return "opened";
|
|
57
|
+
} catch {
|
|
58
|
+
popup.close();
|
|
59
|
+
return "copied";
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
exports.aiProviders = aiProviders;
|
|
64
|
+
exports.buildAiPromptTarget = buildAiPromptTarget;
|
|
65
|
+
exports.copyAndOpenAiPrompt = copyAndOpenAiPrompt;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Services supported by the shared AI prompt launcher. */
|
|
2
|
+
type AiProviderId = 'yuanbao' | 'doubao' | 'deepseek' | 'chatgpt' | 'claude' | 'cursor';
|
|
3
|
+
interface AiProvider {
|
|
4
|
+
readonly id: AiProviderId;
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly url: string;
|
|
7
|
+
readonly promptParameter?: 'q' | 'text';
|
|
8
|
+
}
|
|
9
|
+
/** Only public entry points belong here; never accept an arbitrary destination URL. */
|
|
10
|
+
declare const aiProviders: readonly AiProvider[];
|
|
11
|
+
interface AiPromptOptions {
|
|
12
|
+
/** Optional ChatGPT search hint. Ordinary creative/editorial prompts leave this off. */
|
|
13
|
+
chatgptSearch?: boolean;
|
|
14
|
+
/** Conservative encoded URL budget; Cursor also has a hard 10,000-character limit. */
|
|
15
|
+
maxUrlLength?: number;
|
|
16
|
+
}
|
|
17
|
+
interface AiPromptTarget {
|
|
18
|
+
provider: AiProvider;
|
|
19
|
+
url: string;
|
|
20
|
+
mode: 'link' | 'copy';
|
|
21
|
+
reason?: 'unsupported' | 'too-long' | 'empty';
|
|
22
|
+
}
|
|
23
|
+
/** Build a lossless handoff. Long prompts use the clipboard, never truncated query text. */
|
|
24
|
+
declare function buildAiPromptTarget(id: AiProviderId, prompt: string, options?: AiPromptOptions): AiPromptTarget;
|
|
25
|
+
type AiPromptLaunchStatus = 'opened' | 'copied' | 'copy-failed' | 'open-failed';
|
|
26
|
+
interface AiPromptLaunchResult {
|
|
27
|
+
target: AiPromptTarget;
|
|
28
|
+
status: AiPromptLaunchStatus;
|
|
29
|
+
}
|
|
30
|
+
/** A desktop host can supply an IPC-backed launcher without exposing Electron to this package. */
|
|
31
|
+
type AiPromptLaunch = (target: AiPromptTarget, prompt: string) => Promise<AiPromptLaunchStatus>;
|
|
32
|
+
/** Browser fallback. Call synchronously from a click to retain clipboard and popup activation. */
|
|
33
|
+
declare function copyAndOpenAiPrompt(target: AiPromptTarget, prompt: string): Promise<AiPromptLaunchStatus>;
|
|
34
|
+
|
|
35
|
+
export { aiProviders, buildAiPromptTarget, copyAndOpenAiPrompt };
|
|
36
|
+
export type { AiPromptLaunch, AiPromptLaunchResult, AiPromptLaunchStatus, AiPromptOptions, AiPromptTarget, AiProvider, AiProviderId };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Services supported by the shared AI prompt launcher. */
|
|
2
|
+
type AiProviderId = 'yuanbao' | 'doubao' | 'deepseek' | 'chatgpt' | 'claude' | 'cursor';
|
|
3
|
+
interface AiProvider {
|
|
4
|
+
readonly id: AiProviderId;
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly url: string;
|
|
7
|
+
readonly promptParameter?: 'q' | 'text';
|
|
8
|
+
}
|
|
9
|
+
/** Only public entry points belong here; never accept an arbitrary destination URL. */
|
|
10
|
+
declare const aiProviders: readonly AiProvider[];
|
|
11
|
+
interface AiPromptOptions {
|
|
12
|
+
/** Optional ChatGPT search hint. Ordinary creative/editorial prompts leave this off. */
|
|
13
|
+
chatgptSearch?: boolean;
|
|
14
|
+
/** Conservative encoded URL budget; Cursor also has a hard 10,000-character limit. */
|
|
15
|
+
maxUrlLength?: number;
|
|
16
|
+
}
|
|
17
|
+
interface AiPromptTarget {
|
|
18
|
+
provider: AiProvider;
|
|
19
|
+
url: string;
|
|
20
|
+
mode: 'link' | 'copy';
|
|
21
|
+
reason?: 'unsupported' | 'too-long' | 'empty';
|
|
22
|
+
}
|
|
23
|
+
/** Build a lossless handoff. Long prompts use the clipboard, never truncated query text. */
|
|
24
|
+
declare function buildAiPromptTarget(id: AiProviderId, prompt: string, options?: AiPromptOptions): AiPromptTarget;
|
|
25
|
+
type AiPromptLaunchStatus = 'opened' | 'copied' | 'copy-failed' | 'open-failed';
|
|
26
|
+
interface AiPromptLaunchResult {
|
|
27
|
+
target: AiPromptTarget;
|
|
28
|
+
status: AiPromptLaunchStatus;
|
|
29
|
+
}
|
|
30
|
+
/** A desktop host can supply an IPC-backed launcher without exposing Electron to this package. */
|
|
31
|
+
type AiPromptLaunch = (target: AiPromptTarget, prompt: string) => Promise<AiPromptLaunchStatus>;
|
|
32
|
+
/** Browser fallback. Call synchronously from a click to retain clipboard and popup activation. */
|
|
33
|
+
declare function copyAndOpenAiPrompt(target: AiPromptTarget, prompt: string): Promise<AiPromptLaunchStatus>;
|
|
34
|
+
|
|
35
|
+
export { aiProviders, buildAiPromptTarget, copyAndOpenAiPrompt };
|
|
36
|
+
export type { AiPromptLaunch, AiPromptLaunchResult, AiPromptLaunchStatus, AiPromptOptions, AiPromptTarget, AiProvider, AiProviderId };
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Services supported by the shared AI prompt launcher. */
|
|
2
|
+
type AiProviderId = 'yuanbao' | 'doubao' | 'deepseek' | 'chatgpt' | 'claude' | 'cursor';
|
|
3
|
+
interface AiProvider {
|
|
4
|
+
readonly id: AiProviderId;
|
|
5
|
+
readonly name: string;
|
|
6
|
+
readonly url: string;
|
|
7
|
+
readonly promptParameter?: 'q' | 'text';
|
|
8
|
+
}
|
|
9
|
+
/** Only public entry points belong here; never accept an arbitrary destination URL. */
|
|
10
|
+
declare const aiProviders: readonly AiProvider[];
|
|
11
|
+
interface AiPromptOptions {
|
|
12
|
+
/** Optional ChatGPT search hint. Ordinary creative/editorial prompts leave this off. */
|
|
13
|
+
chatgptSearch?: boolean;
|
|
14
|
+
/** Conservative encoded URL budget; Cursor also has a hard 10,000-character limit. */
|
|
15
|
+
maxUrlLength?: number;
|
|
16
|
+
}
|
|
17
|
+
interface AiPromptTarget {
|
|
18
|
+
provider: AiProvider;
|
|
19
|
+
url: string;
|
|
20
|
+
mode: 'link' | 'copy';
|
|
21
|
+
reason?: 'unsupported' | 'too-long' | 'empty';
|
|
22
|
+
}
|
|
23
|
+
/** Build a lossless handoff. Long prompts use the clipboard, never truncated query text. */
|
|
24
|
+
declare function buildAiPromptTarget(id: AiProviderId, prompt: string, options?: AiPromptOptions): AiPromptTarget;
|
|
25
|
+
type AiPromptLaunchStatus = 'opened' | 'copied' | 'copy-failed' | 'open-failed';
|
|
26
|
+
interface AiPromptLaunchResult {
|
|
27
|
+
target: AiPromptTarget;
|
|
28
|
+
status: AiPromptLaunchStatus;
|
|
29
|
+
}
|
|
30
|
+
/** A desktop host can supply an IPC-backed launcher without exposing Electron to this package. */
|
|
31
|
+
type AiPromptLaunch = (target: AiPromptTarget, prompt: string) => Promise<AiPromptLaunchStatus>;
|
|
32
|
+
/** Browser fallback. Call synchronously from a click to retain clipboard and popup activation. */
|
|
33
|
+
declare function copyAndOpenAiPrompt(target: AiPromptTarget, prompt: string): Promise<AiPromptLaunchStatus>;
|
|
34
|
+
|
|
35
|
+
export { aiProviders, buildAiPromptTarget, copyAndOpenAiPrompt };
|
|
36
|
+
export type { AiPromptLaunch, AiPromptLaunchResult, AiPromptLaunchStatus, AiPromptOptions, AiPromptTarget, AiProvider, AiProviderId };
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
const aiProviders = [
|
|
2
|
+
{ id: "yuanbao", name: "\u5143\u5B9D", url: "https://yuanbao.tencent.com/" },
|
|
3
|
+
{ id: "doubao", name: "\u8C46\u5305", url: "https://www.doubao.com/chat/" },
|
|
4
|
+
{ id: "deepseek", name: "DeepSeek", url: "https://chat.deepseek.com/", promptParameter: "q" },
|
|
5
|
+
{ id: "chatgpt", name: "ChatGPT", url: "https://chatgpt.com/", promptParameter: "q" },
|
|
6
|
+
{ id: "claude", name: "Claude", url: "https://claude.ai/new", promptParameter: "q" },
|
|
7
|
+
{ id: "cursor", name: "Cursor", url: "https://cursor.com/link/prompt", promptParameter: "text" }
|
|
8
|
+
];
|
|
9
|
+
function buildAiPromptTarget(id, prompt, options = {}) {
|
|
10
|
+
const provider = aiProviders.find((item) => item.id === id);
|
|
11
|
+
if (!provider)
|
|
12
|
+
throw new Error(`Unknown AI provider: ${id}`);
|
|
13
|
+
const fallback = (reason) => ({ provider, url: provider.url, mode: "copy", reason });
|
|
14
|
+
if (!prompt.trim())
|
|
15
|
+
return fallback("empty");
|
|
16
|
+
if (!provider.promptParameter)
|
|
17
|
+
return fallback("unsupported");
|
|
18
|
+
const url = new URL(provider.url);
|
|
19
|
+
url.searchParams.set(provider.promptParameter, prompt);
|
|
20
|
+
if (id === "chatgpt" && options.chatgptSearch)
|
|
21
|
+
url.searchParams.set("hints", "search");
|
|
22
|
+
const providerLimit = id === "cursor" ? 9900 : Number.POSITIVE_INFINITY;
|
|
23
|
+
const limit = Math.min(options.maxUrlLength ?? 8192, providerLimit);
|
|
24
|
+
if (!Number.isFinite(limit) || limit <= 0 || url.href.length > limit)
|
|
25
|
+
return fallback("too-long");
|
|
26
|
+
return { provider, url: url.href, mode: "link" };
|
|
27
|
+
}
|
|
28
|
+
async function copyAndOpenAiPrompt(target, prompt) {
|
|
29
|
+
let copying;
|
|
30
|
+
try {
|
|
31
|
+
copying = navigator.clipboard.writeText(prompt);
|
|
32
|
+
} catch {
|
|
33
|
+
return "copy-failed";
|
|
34
|
+
}
|
|
35
|
+
let popup = null;
|
|
36
|
+
try {
|
|
37
|
+
popup = window.open("about:blank", "_blank");
|
|
38
|
+
if (popup)
|
|
39
|
+
popup.opener = null;
|
|
40
|
+
} catch {
|
|
41
|
+
popup?.close();
|
|
42
|
+
popup = null;
|
|
43
|
+
}
|
|
44
|
+
try {
|
|
45
|
+
await copying;
|
|
46
|
+
} catch {
|
|
47
|
+
popup?.close();
|
|
48
|
+
return "copy-failed";
|
|
49
|
+
}
|
|
50
|
+
if (!popup || popup.closed)
|
|
51
|
+
return "copied";
|
|
52
|
+
try {
|
|
53
|
+
popup.location.replace(target.provider.url);
|
|
54
|
+
return "opened";
|
|
55
|
+
} catch {
|
|
56
|
+
popup.close();
|
|
57
|
+
return "copied";
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export { aiProviders, buildAiPromptTarget, copyAndOpenAiPrompt };
|
package/package.json
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@yunlefun/vue",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.2.0",
|
|
5
5
|
"description": "Vue components and Nuxt integration for YunLeFun Design",
|
|
6
|
+
"author": "YunLeFun",
|
|
7
|
+
"license": "MIT",
|
|
8
|
+
"homepage": "https://ui.yunle.fun",
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/YunLeFun/design.git",
|
|
12
|
+
"directory": "packages/vue"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/YunLeFun/design/issues"
|
|
16
|
+
},
|
|
6
17
|
"exports": {
|
|
7
18
|
"./components/*": "./components/*",
|
|
8
19
|
"./nuxt": "./nuxt.mjs",
|
|
@@ -11,7 +22,12 @@
|
|
|
11
22
|
"import": "./dist/index.mjs",
|
|
12
23
|
"require": "./dist/index.cjs"
|
|
13
24
|
},
|
|
14
|
-
"./*": "./*"
|
|
25
|
+
"./*": "./*",
|
|
26
|
+
"./ai-prompt": {
|
|
27
|
+
"types": "./dist/ai-prompt.d.ts",
|
|
28
|
+
"import": "./dist/ai-prompt.mjs",
|
|
29
|
+
"require": "./dist/ai-prompt.cjs"
|
|
30
|
+
}
|
|
15
31
|
},
|
|
16
32
|
"main": "./dist/index.cjs",
|
|
17
33
|
"module": "./dist/index.mjs",
|
|
@@ -21,6 +37,9 @@
|
|
|
21
37
|
"dist",
|
|
22
38
|
"nuxt.mjs"
|
|
23
39
|
],
|
|
40
|
+
"publishConfig": {
|
|
41
|
+
"access": "public"
|
|
42
|
+
},
|
|
24
43
|
"scripts": {
|
|
25
44
|
"build": "unbuild",
|
|
26
45
|
"prepublishOnly": "npm run build",
|