vue-ai-hooks 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/LICENSE +21 -0
- package/README.md +205 -0
- package/README.zh-CN.md +178 -0
- package/dist/composables/_tc_merge.d.ts +13 -0
- package/dist/composables/_tc_merge.d.ts.map +1 -0
- package/dist/composables/useChat.d.ts +39 -0
- package/dist/composables/useChat.d.ts.map +1 -0
- package/dist/composables/useCompletion.d.ts +31 -0
- package/dist/composables/useCompletion.d.ts.map +1 -0
- package/dist/composables/useEmbedding.d.ts +29 -0
- package/dist/composables/useEmbedding.d.ts.map +1 -0
- package/dist/composables/usePersist.d.ts +48 -0
- package/dist/composables/usePersist.d.ts.map +1 -0
- package/dist/index.cjs +8 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +679 -0
- package/dist/providers/anthropic.d.ts +40 -0
- package/dist/providers/anthropic.d.ts.map +1 -0
- package/dist/providers/openai.d.ts +39 -0
- package/dist/providers/openai.d.ts.map +1 -0
- package/dist/providers/types.d.ts +20 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/types/index.d.ts +140 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/utils/fetch.d.ts +10 -0
- package/dist/utils/fetch.d.ts.map +1 -0
- package/dist/utils/id.d.ts +2 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/stream.d.ts +9 -0
- package/dist/utils/stream.d.ts.map +1 -0
- package/package.json +88 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 hexinmiao96
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY EVENT CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# vue-ai-hooks
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | [็ฎไฝไธญๆ](./README.zh-CN.md)
|
|
4
|
+
|
|
5
|
+
> Vue 3 Composable library for building AI-powered applications.
|
|
6
|
+
> Streaming-first, multi-provider, fully typed.
|
|
7
|
+
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://vuejs.org)
|
|
10
|
+
[](https://www.typescriptlang.org)
|
|
11
|
+
[](CONTRIBUTING.md)
|
|
12
|
+
|
|
13
|
+
`vue-ai-hooks` brings the same DX you'd expect from [VueUse](https://vueuse.org) or
|
|
14
|
+
[Axios](https://axios-http.com) to the LLM world. Three composables, pluggable
|
|
15
|
+
providers, Server-Sent Events streaming handled for you. Works with OpenAI and any
|
|
16
|
+
OpenAI-compatible service (DeepSeek, Moonshot, Zhipu, Ollama via its OpenAI shim,
|
|
17
|
+
vLLM, etc.).
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
import { useChat, openai } from 'vue-ai-hooks'
|
|
21
|
+
|
|
22
|
+
const { messages, input, append, isLoading, stop } = useChat({
|
|
23
|
+
provider: openai({ apiKey: import.meta.env.VITE_OPENAI_KEY })
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Why
|
|
28
|
+
|
|
29
|
+
The AI-in-Vue story is currently fragmented. Options today:
|
|
30
|
+
|
|
31
|
+
| Library | Tradeoff |
|
|
32
|
+
|---|---|
|
|
33
|
+
| **Vercel AI SDK** | React-first; Vue support is unofficial and lags behind |
|
|
34
|
+
| **LangChain.js** | Powerful but heavy; opinionated chains, lots of magic |
|
|
35
|
+
| **Direct fetch + manual SSE** | Works, but you re-implement aborts, retries, and state for every project |
|
|
36
|
+
| **vue-ai-hooks** | A focused, framework-native SDK with the boring parts done |
|
|
37
|
+
|
|
38
|
+
## Features
|
|
39
|
+
|
|
40
|
+
- ๐ฏ **Three composables, one mental model** โ `useChat`, `useCompletion`, `useEmbedding`
|
|
41
|
+
- ๐ **Streaming by default** โ SSE parsing, AbortController, and reactivity handled for you
|
|
42
|
+
- ๐ **Multi-provider** โ OpenAI, Azure OpenAI, DeepSeek, Moonshot, Zhipu, Ollama, vLLM, any OpenAI-compatible API
|
|
43
|
+
- ๐งฐ **Tool calling helpers** โ register local handlers and let `useChat` continue the model round-trip
|
|
44
|
+
- ๐ **TypeScript first** โ strict mode, no `any` leaks, full IDE autocomplete
|
|
45
|
+
- โก **Tiny** โ zero runtime deps beyond Vue itself
|
|
46
|
+
- ๐งช **Tested** โ Vitest + happy-dom, with fake providers you can copy
|
|
47
|
+
- ๐ฆ **Tree-shakable** โ ESM and CJS, named exports, no side effects
|
|
48
|
+
|
|
49
|
+
## Installation
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
pnpm add vue-ai-hooks
|
|
53
|
+
# or
|
|
54
|
+
npm install vue-ai-hooks
|
|
55
|
+
# or
|
|
56
|
+
yarn add vue-ai-hooks
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Peer dependency: `vue@^3.4.0`.
|
|
60
|
+
|
|
61
|
+
## Quick start
|
|
62
|
+
|
|
63
|
+
### Streaming chat
|
|
64
|
+
|
|
65
|
+
```vue
|
|
66
|
+
<script setup lang="ts">
|
|
67
|
+
import { useChat, openai } from 'vue-ai-hooks'
|
|
68
|
+
|
|
69
|
+
const { messages, input, append, isLoading, stop, error } = useChat({
|
|
70
|
+
provider: openai({ apiKey: import.meta.env.VITE_OPENAI_KEY })
|
|
71
|
+
})
|
|
72
|
+
</script>
|
|
73
|
+
|
|
74
|
+
<template>
|
|
75
|
+
<div v-for="m in messages" :key="m.id" :class="m.role">
|
|
76
|
+
{{ m.content }}
|
|
77
|
+
</div>
|
|
78
|
+
<textarea v-model="input" />
|
|
79
|
+
<button :disabled="isLoading" @click="append(input); input = ''">Send</button>
|
|
80
|
+
<button :disabled="!isLoading" @click="stop">Stop</button>
|
|
81
|
+
</template>
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Single-shot completion
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import { useCompletion, openai } from 'vue-ai-hooks'
|
|
88
|
+
|
|
89
|
+
const { completion, complete } = useCompletion({
|
|
90
|
+
provider: openai({ apiKey: '...' })
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
await complete('Write a haiku about TypeScript:')
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Embeddings
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { useEmbedding, openai } from 'vue-ai-hooks'
|
|
100
|
+
|
|
101
|
+
const { embed, embeddings } = useEmbedding({
|
|
102
|
+
provider: openai({ apiKey: '...' })
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const result = await embed(['hello world', 'goodbye world'])
|
|
106
|
+
console.log(result.embeddings) // number[][]
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Using a non-OpenAI provider
|
|
110
|
+
|
|
111
|
+
Every provider implements the same `ChatProvider` interface, so the hooks
|
|
112
|
+
don't care which model is on the other end:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
import { useChat, openaiCompatible } from 'vue-ai-hooks'
|
|
116
|
+
|
|
117
|
+
useChat({
|
|
118
|
+
provider: openaiCompatible({
|
|
119
|
+
apiKey: 'sk-...',
|
|
120
|
+
baseURL: 'https://api.deepseek.com/v1'
|
|
121
|
+
})
|
|
122
|
+
})
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Add a new provider in one file by implementing `ChatProvider`:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
// src/providers/anthropic.ts
|
|
129
|
+
import type { ChatProvider } from 'vue-ai-hooks'
|
|
130
|
+
// ... implement chat / completion / embedding
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Then open a PR โ the hook layer stays untouched.
|
|
134
|
+
|
|
135
|
+
## API reference
|
|
136
|
+
|
|
137
|
+
### `useChat(options)`
|
|
138
|
+
|
|
139
|
+
Returns a reactive bundle for managing a streaming chat conversation.
|
|
140
|
+
|
|
141
|
+
| Return | Type | Description |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| `messages` | `Ref<Message[]>` | Full message history (user, assistant, system, tool) |
|
|
144
|
+
| `input` | `Ref<string>` | Bound to your composer; not auto-cleared |
|
|
145
|
+
| `isLoading` | `Ref<boolean>` | True while a stream is in flight |
|
|
146
|
+
| `error` | `Ref<Error \| null>` | Last error, cleared on next `append` |
|
|
147
|
+
| `append(content, opts?)` | `(string \| Message, Partial<ChatRequest>) => Promise<void>` | Send a message and stream the reply |
|
|
148
|
+
| `reload()` | `() => Promise<void>` | Re-run the last assistant turn |
|
|
149
|
+
| `stop()` | `() => void` | Abort the in-flight stream |
|
|
150
|
+
| `setMessages(messages)` | `(Message[]) => void` | Replace history (e.g. on restore) |
|
|
151
|
+
| `clear()` | `() => void` | Reset to empty state |
|
|
152
|
+
| `abortController` | `Ref<AbortController \| null>` | Exposed for advanced use cases |
|
|
153
|
+
|
|
154
|
+
### `useCompletion(options)` / `useEmbedding(options)`
|
|
155
|
+
|
|
156
|
+
Same shape, scoped to single-shot completions and embedding vectors respectively.
|
|
157
|
+
See [the source](./src/composables/) for full type definitions.
|
|
158
|
+
|
|
159
|
+
## Examples
|
|
160
|
+
|
|
161
|
+
Three runnable examples live in [`examples/`](./examples):
|
|
162
|
+
|
|
163
|
+
- `examples/chat` โ minimal streaming chat UI
|
|
164
|
+
- `examples/completion` โ single-shot completion form
|
|
165
|
+
- `examples/embedding` โ pairwise cosine similarity heatmap
|
|
166
|
+
|
|
167
|
+
To run them:
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
pnpm install
|
|
171
|
+
echo "VITE_OPENAI_KEY=sk-..." > .env
|
|
172
|
+
pnpm example:chat
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Project status
|
|
176
|
+
|
|
177
|
+
This is **v0.2.0** โ a working foundation, not feature-complete. What's in:
|
|
178
|
+
|
|
179
|
+
- โ
Chat with streaming, abort, message history
|
|
180
|
+
- โ
Single-shot completion
|
|
181
|
+
- โ
Embedding
|
|
182
|
+
- โ
OpenAI + OpenAI-compatible provider
|
|
183
|
+
- โ
Anthropic Claude provider
|
|
184
|
+
- โ
Multimodal image input
|
|
185
|
+
- โ
localStorage persistence
|
|
186
|
+
- โ
Tool-calling helpers
|
|
187
|
+
- โ
Tests, CI, examples
|
|
188
|
+
|
|
189
|
+
What we're planning next:
|
|
190
|
+
|
|
191
|
+
- ๐ Vue DevTools tab for inspecting streams
|
|
192
|
+
- ๐ More providers and production hardening
|
|
193
|
+
|
|
194
|
+
## Contributing
|
|
195
|
+
|
|
196
|
+
Contributions are very welcome. See [`CONTRIBUTING.md`](./CONTRIBUTING.md) for
|
|
197
|
+
the workflow, and the [open issues](../../issues) for things that need hands.
|
|
198
|
+
|
|
199
|
+
Adding a new provider is the single best first contribution โ it's one file,
|
|
200
|
+
a small interface, and high-value. See [`src/providers/openai.ts`](./src/providers/openai.ts)
|
|
201
|
+
for the reference implementation.
|
|
202
|
+
|
|
203
|
+
## License
|
|
204
|
+
|
|
205
|
+
[MIT](./LICENSE)
|
package/README.zh-CN.md
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# vue-ai-hooks
|
|
2
|
+
|
|
3
|
+
[English](./README.md) | ็ฎไฝไธญๆ
|
|
4
|
+
|
|
5
|
+
> ็จไบๆๅปบ AI ๅบ็จ็ Vue 3 ็ปๅๅผๅฝๆฐๅบใ
|
|
6
|
+
> ๆตๅผไผๅ
ใๅค Providerใๅฎๆด็ฑปๅๆฏๆใ
|
|
7
|
+
|
|
8
|
+
[](LICENSE)
|
|
9
|
+
[](https://vuejs.org)
|
|
10
|
+
[](https://www.typescriptlang.org)
|
|
11
|
+
[](CONTRIBUTING.md)
|
|
12
|
+
|
|
13
|
+
`vue-ai-hooks` ๆไฝ ๅจ [VueUse](https://vueuse.org) ๆ [Axios](https://axios-http.com) ไธญ็ๆ็ๅผๅไฝ้ชๅธฆๅฐ LLM ๅบ็จ้ใๅฎๆไพไธไธช็ปๅๅผๅฝๆฐใๅฏๆๆ Provider๏ผๅนถๅธฎไฝ ๅค็ Server-Sent Events ๆตๅผๅๅบใๆฏๆ OpenAI ไปฅๅไปปไฝ OpenAI-compatible ๆๅก๏ผไพๅฆ DeepSeekใMoonshotใๆบ่ฐฑใOllama ็ OpenAI shimใvLLM ็ญใ
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { useChat, openai } from 'vue-ai-hooks'
|
|
17
|
+
|
|
18
|
+
const { messages, input, append, isLoading, stop } = useChat({
|
|
19
|
+
provider: openai({ apiKey: import.meta.env.VITE_OPENAI_KEY })
|
|
20
|
+
})
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## ไธบไปไน้่ฆๅฎ
|
|
24
|
+
|
|
25
|
+
ๅฝๅ Vue ไธญ็ AI ๅผๅไฝ้ชไป็ถๆฏ่พๅๆฃ๏ผ
|
|
26
|
+
|
|
27
|
+
| ๅบ | ๅ่ |
|
|
28
|
+
|---|---|
|
|
29
|
+
| **Vercel AI SDK** | React ไผๅ
๏ผVue ๆฏๆ้ๅฎๆนไธ้ๅธธๆปๅ |
|
|
30
|
+
| **LangChain.js** | ๅ่ฝๅผบๅคงไฝๅ้๏ผ้พๅผๆฝ่ฑก่พๅค๏ผ้ญๆณไนๅค |
|
|
31
|
+
| **็ดๆฅ fetch + ๆๅ SSE** | ๅฏ่ก๏ผไฝๆฏไธช้กน็ฎ้ฝ่ฆ้ๅคๅฎ็ฐไธญๆญขใ้่ฏๅ็ถๆ็ฎก็ |
|
|
32
|
+
| **vue-ai-hooks** | ไธๆณจใๆกๆถๅ็๏ผๅนถๆ็น็้จๅๅค็ๅฅฝ |
|
|
33
|
+
|
|
34
|
+
## ็นๆง
|
|
35
|
+
|
|
36
|
+
- **ไธไธช็ปๅๅผๅฝๆฐ๏ผไธๅฅๅฟๆบๆจกๅ**๏ผ`useChat`ใ`useCompletion`ใ`useEmbedding`
|
|
37
|
+
- **้ป่ฎคๆฏๆๆตๅผๅๅบ**๏ผSSE ่งฃๆใAbortController ๅๅๅบๅผ็ถๆ้ฝๅทฒๅค็ๅฅฝ
|
|
38
|
+
- **ๅค Provider**๏ผOpenAIใAzure OpenAIใDeepSeekใMoonshotใๆบ่ฐฑใOllamaใvLLM๏ผไปฅๅไปปไฝ OpenAI-compatible API
|
|
39
|
+
- **Tool calling helper**๏ผๆณจๅๆฌๅฐ handler ๅ๏ผ`useChat` ไผ่ชๅจๆง่กๅทฅๅ
ทๅนถ็ปง็ปญๆจกๅ่ฝฎๆฌก
|
|
40
|
+
- **TypeScript ไผๅ
**๏ผไธฅๆ ผๆจกๅผใๆ `any` ๆณๆผใๅฎๆด IDE ่ชๅจ่กฅๅ
จ
|
|
41
|
+
- **ๅฐ่่ฝป**๏ผ้ค Vue ๆฌ่บซๅคๆฒกๆ่ฟ่กๆถไพ่ต
|
|
42
|
+
- **ๅทฒๆต่ฏ**๏ผVitest + happy-dom๏ผๅนถๆไพๅฏๅคๅถ็ fake provider
|
|
43
|
+
- **ๅฏ tree-shaking**๏ผESM ๅ CJSใๅฝๅๅฏผๅบใๆ ๅฏไฝ็จ
|
|
44
|
+
|
|
45
|
+
## ๅฎ่ฃ
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pnpm add vue-ai-hooks
|
|
49
|
+
# ๆ
|
|
50
|
+
npm install vue-ai-hooks
|
|
51
|
+
# ๆ
|
|
52
|
+
yarn add vue-ai-hooks
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Peer dependency๏ผ`vue@^3.4.0`ใ
|
|
56
|
+
|
|
57
|
+
## ๅฟซ้ๅผๅง
|
|
58
|
+
|
|
59
|
+
### ๆตๅผ่ๅคฉ
|
|
60
|
+
|
|
61
|
+
```vue
|
|
62
|
+
<script setup lang="ts">
|
|
63
|
+
import { useChat, openai } from 'vue-ai-hooks'
|
|
64
|
+
|
|
65
|
+
const { messages, input, append, isLoading, stop, error } = useChat({
|
|
66
|
+
provider: openai({ apiKey: import.meta.env.VITE_OPENAI_KEY })
|
|
67
|
+
})
|
|
68
|
+
</script>
|
|
69
|
+
|
|
70
|
+
<template>
|
|
71
|
+
<div v-for="m in messages" :key="m.id" :class="m.role">
|
|
72
|
+
{{ m.content }}
|
|
73
|
+
</div>
|
|
74
|
+
<textarea v-model="input" />
|
|
75
|
+
<button :disabled="isLoading" @click="append(input); input = ''">Send</button>
|
|
76
|
+
<button :disabled="!isLoading" @click="stop">Stop</button>
|
|
77
|
+
</template>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### ๅๆฌก่กฅๅ
จ
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
import { useCompletion, openai } from 'vue-ai-hooks'
|
|
84
|
+
|
|
85
|
+
const { completion, complete } = useCompletion({
|
|
86
|
+
provider: openai({ apiKey: '...' })
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
await complete('Write a haiku about TypeScript:')
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### Embeddings
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { useEmbedding, openai } from 'vue-ai-hooks'
|
|
96
|
+
|
|
97
|
+
const { embed, embeddings } = useEmbedding({
|
|
98
|
+
provider: openai({ apiKey: '...' })
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
const result = await embed(['hello world', 'goodbye world'])
|
|
102
|
+
console.log(result.embeddings) // number[][]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## ไฝฟ็จ้ OpenAI Provider
|
|
106
|
+
|
|
107
|
+
ๆฏไธช Provider ้ฝๅฎ็ฐๅไธไธช `ChatProvider` ๆฅๅฃ๏ผๆไปฅ็ปๅๅผๅฝๆฐไธๅ
ณๅฟๅฆไธ็ซฏๅ
ทไฝไฝฟ็จๅชไธชๆจกๅ๏ผ
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
import { useChat, openaiCompatible } from 'vue-ai-hooks'
|
|
111
|
+
|
|
112
|
+
useChat({
|
|
113
|
+
provider: openaiCompatible({
|
|
114
|
+
apiKey: 'sk-...',
|
|
115
|
+
baseURL: 'https://api.deepseek.com/v1'
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
ๆฐๅข Provider ๅช้่ฆๅฎ็ฐ `ChatProvider`๏ผ
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
// src/providers/anthropic.ts
|
|
124
|
+
import type { ChatProvider } from 'vue-ai-hooks'
|
|
125
|
+
// ... implement chat / completion / embedding
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
็ถๅๆไบค PR๏ผ็ปๅๅผๅฝๆฐๅฑไธ้่ฆๆนๅจใ
|
|
129
|
+
|
|
130
|
+
## ๆๆกฃ
|
|
131
|
+
|
|
132
|
+
- [English docs](./docs/index.md)
|
|
133
|
+
- [ไธญๆๆๆกฃ](./docs/zh/index.md)
|
|
134
|
+
|
|
135
|
+
## ็คบไพ
|
|
136
|
+
|
|
137
|
+
ไธไธชๅฏ่ฟ่ก็คบไพไฝไบ [`examples/`](./examples)๏ผ
|
|
138
|
+
|
|
139
|
+
- `examples/chat`๏ผๆๅฐๆตๅผ่ๅคฉ UI
|
|
140
|
+
- `examples/completion`๏ผๅๆฌก่กฅๅ
จ่กจๅ
|
|
141
|
+
- `examples/embedding`๏ผๆๅฏนไฝๅผฆ็ธไผผๅบฆ็ญๅๅพ
|
|
142
|
+
|
|
143
|
+
่ฟ่กๆนๅผ๏ผ
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
pnpm install
|
|
147
|
+
echo "VITE_OPENAI_KEY=sk-..." > .env
|
|
148
|
+
pnpm example:chat
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## ้กน็ฎ็ถๆ
|
|
152
|
+
|
|
153
|
+
่ฟๆฏ **v0.2.0**๏ผๆฏไธไธชๅฏๅทฅไฝ็ๅบ็ก็ๆฌ๏ผไฝ่ฟไธๆฏๅ่ฝๅฎๅค็ๆฌใ็ฎๅๅทฒๅ
ๅซ๏ผ
|
|
154
|
+
|
|
155
|
+
- Chat ๆตๅผๅๅบใไธญๆญขใๆถๆฏๅๅฒ
|
|
156
|
+
- ๅๆฌก่กฅๅ
จ
|
|
157
|
+
- Embedding
|
|
158
|
+
- OpenAI + OpenAI-compatible Provider
|
|
159
|
+
- Anthropic Claude Provider
|
|
160
|
+
- ๅคๆจกๆๅพ็่พๅ
ฅ
|
|
161
|
+
- localStorage ๆไน
ๅ
|
|
162
|
+
- Tool-calling helper
|
|
163
|
+
- ๆต่ฏใCIใ็คบไพ
|
|
164
|
+
|
|
165
|
+
ไธไธๆญฅ่ฎกๅ๏ผ
|
|
166
|
+
|
|
167
|
+
- ็จไบๆฃๆฅๆต็ Vue DevTools tab
|
|
168
|
+
- ๆดๅค Provider ๅ็ไบง็บงๅขๅผบ
|
|
169
|
+
|
|
170
|
+
## ่ดก็ฎ
|
|
171
|
+
|
|
172
|
+
ๆฌข่ฟ่ดก็ฎใ่ฏทๆฅ็ [`CONTRIBUTING.md`](./CONTRIBUTING.md) ไบ่งฃๅทฅไฝๆต๏ผไนๅฏไปฅๆฅ็ [open issues](../../issues) ไธญๅพ
ๅค็็ไบ้กนใ
|
|
173
|
+
|
|
174
|
+
ๆฐๅข Provider ๆฏๆ้ๅไฝไธบ็ฌฌไธๆฌก่ดก็ฎ็ๆนๅ๏ผๅช้่ฆไธไธชๆไปถใไธไธชๅฐๆฅๅฃ๏ผ่ไธไปทๅผๅพ้ซใๅ่ๅฎ็ฐ่ง [`src/providers/openai.ts`](./src/providers/openai.ts)ใ
|
|
175
|
+
|
|
176
|
+
## License
|
|
177
|
+
|
|
178
|
+
[MIT](./LICENSE)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ToolCall } from '../types';
|
|
2
|
+
export interface ToolCallDelta {
|
|
3
|
+
index?: number;
|
|
4
|
+
id?: string;
|
|
5
|
+
type?: 'function';
|
|
6
|
+
function?: {
|
|
7
|
+
name?: string;
|
|
8
|
+
arguments?: string;
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
/** Merge streaming tool_call deltas (OpenAI format) into a stable array. */
|
|
12
|
+
export declare function mergeDeltas(existing: ToolCall[] | undefined, delta: ToolCallDelta[] | undefined): ToolCall[];
|
|
13
|
+
//# sourceMappingURL=_tc_merge.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_tc_merge.d.ts","sourceRoot":"","sources":["../../src/composables/_tc_merge.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAExC,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,IAAI,CAAC,EAAE,UAAU,CAAA;IACjB,QAAQ,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;CACjD;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,QAAQ,EAAE,QAAQ,EAAE,GAAG,SAAS,EAAE,KAAK,EAAE,aAAa,EAAE,GAAG,SAAS,GAAG,QAAQ,EAAE,CAkB5G"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { type Ref } from 'vue';
|
|
2
|
+
import type { ChatProvider } from '../providers/types';
|
|
3
|
+
import type { ChatRequest, Message, Tool, ToolCall } from '../types';
|
|
4
|
+
export interface ToolCallHandlerContext {
|
|
5
|
+
toolCall: ToolCall;
|
|
6
|
+
messages: Message[];
|
|
7
|
+
}
|
|
8
|
+
export type ToolCallHandler = (args: unknown, context: ToolCallHandlerContext) => unknown | Promise<unknown>;
|
|
9
|
+
export interface UseChatOptions {
|
|
10
|
+
provider?: ChatProvider;
|
|
11
|
+
initialMessages?: Message[];
|
|
12
|
+
defaultRequest?: Partial<ChatRequest>;
|
|
13
|
+
id?: string;
|
|
14
|
+
persist?: {
|
|
15
|
+
key: string;
|
|
16
|
+
version?: number;
|
|
17
|
+
};
|
|
18
|
+
tools?: Tool[];
|
|
19
|
+
toolChoice?: ChatRequest['toolChoice'];
|
|
20
|
+
toolHandlers?: Record<string, ToolCallHandler>;
|
|
21
|
+
maxToolRoundtrips?: number;
|
|
22
|
+
onUpdate?: (m: Message) => void;
|
|
23
|
+
onFinish?: (m: Message) => void;
|
|
24
|
+
onError?: (e: Error) => void;
|
|
25
|
+
}
|
|
26
|
+
export interface UseChatReturn {
|
|
27
|
+
messages: Ref<Message[]>;
|
|
28
|
+
input: Ref<string>;
|
|
29
|
+
isLoading: Ref<boolean>;
|
|
30
|
+
error: Ref<Error | null>;
|
|
31
|
+
append: (c: string | Message, o?: Partial<ChatRequest>) => Promise<void>;
|
|
32
|
+
reload: () => Promise<void>;
|
|
33
|
+
stop: () => void;
|
|
34
|
+
setMessages: (m: Message[]) => void;
|
|
35
|
+
clear: () => void;
|
|
36
|
+
abortController: Ref<AbortController | null>;
|
|
37
|
+
}
|
|
38
|
+
export declare function useChat(options: UseChatOptions): UseChatReturn;
|
|
39
|
+
//# sourceMappingURL=useChat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useChat.d.ts","sourceRoot":"","sources":["../../src/composables/useChat.ts"],"names":[],"mappings":"AAEA,OAAO,EAAmB,KAAK,GAAG,EAAE,MAAM,KAAK,CAAA;AAE/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,OAAO,EAAe,IAAI,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAA;AAIjF,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,QAAQ,CAAA;IAClB,QAAQ,EAAE,OAAO,EAAE,CAAA;CACpB;AAED,MAAM,MAAM,eAAe,GAAG,CAC5B,IAAI,EAAE,OAAO,EACb,OAAO,EAAE,sBAAsB,KAC5B,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAA;AAE/B,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,YAAY,CAAA;IACvB,eAAe,CAAC,EAAE,OAAO,EAAE,CAAA;IAC3B,cAAc,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,CAAA;IACrC,EAAE,CAAC,EAAE,MAAM,CAAA;IACX,OAAO,CAAC,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAA;IAC3C,KAAK,CAAC,EAAE,IAAI,EAAE,CAAA;IACd,UAAU,CAAC,EAAE,WAAW,CAAC,YAAY,CAAC,CAAA;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAA;IAC9C,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IAC/B,QAAQ,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IAC/B,OAAO,CAAC,EAAE,CAAC,CAAC,EAAE,KAAK,KAAK,IAAI,CAAA;CAC7B;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAA;IACxB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAClB,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IACvB,KAAK,EAAE,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAA;IACxB,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,GAAG,OAAO,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACxE,MAAM,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC3B,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,WAAW,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,KAAK,IAAI,CAAA;IACnC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,eAAe,EAAE,GAAG,CAAC,eAAe,GAAG,IAAI,CAAC,CAAA;CAC7C;AAID,wBAAgB,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,aAAa,CA+O9D"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { type Ref } from 'vue';
|
|
2
|
+
import type { ChatProvider } from '../providers/types';
|
|
3
|
+
import type { CompletionRequest } from '../types';
|
|
4
|
+
export interface UseCompletionOptions {
|
|
5
|
+
provider: ChatProvider;
|
|
6
|
+
initialCompletion?: string;
|
|
7
|
+
defaultRequest?: Partial<CompletionRequest>;
|
|
8
|
+
onFinish?: (completion: string) => void;
|
|
9
|
+
onError?: (err: Error) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface UseCompletionReturn {
|
|
12
|
+
completion: Ref<string>;
|
|
13
|
+
input: Ref<string>;
|
|
14
|
+
isLoading: Ref<boolean>;
|
|
15
|
+
error: Ref<Error | null>;
|
|
16
|
+
complete: (prompt?: string, options?: Partial<CompletionRequest>) => Promise<string>;
|
|
17
|
+
stop: () => void;
|
|
18
|
+
setCompletion: (value: string) => void;
|
|
19
|
+
abortController: Ref<AbortController | null>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Vue 3 composable for single-shot streaming completions.
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* const { completion, input, complete } = useCompletion({
|
|
26
|
+
* provider: openai({ apiKey: import.meta.env.VITE_OPENAI_KEY })
|
|
27
|
+
* })
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export declare function useCompletion(options: UseCompletionOptions): UseCompletionReturn;
|
|
31
|
+
//# sourceMappingURL=useCompletion.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useCompletion.d.ts","sourceRoot":"","sources":["../../src/composables/useCompletion.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,GAAG,EAAE,MAAM,KAAK,CAAA;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAA;AAEjD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,EAAE,YAAY,CAAA;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAC1B,cAAc,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAC3C,QAAQ,CAAC,EAAE,CAAC,UAAU,EAAE,MAAM,KAAK,IAAI,CAAA;IACvC,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAClC,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IACvB,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;IAClB,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IACvB,KAAK,EAAE,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAA;IACxB,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,iBAAiB,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAA;IACpF,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB,aAAa,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;IACtC,eAAe,EAAE,GAAG,CAAC,eAAe,GAAG,IAAI,CAAC,CAAA;CAC7C;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,mBAAmB,CAqEhF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { type Ref } from 'vue';
|
|
2
|
+
import type { ChatProvider } from '../providers/types';
|
|
3
|
+
import type { EmbeddingRequest, EmbeddingResult } from '../types';
|
|
4
|
+
export interface UseEmbeddingOptions {
|
|
5
|
+
provider: ChatProvider;
|
|
6
|
+
defaultRequest?: Partial<EmbeddingRequest>;
|
|
7
|
+
onSuccess?: (result: EmbeddingResult) => void;
|
|
8
|
+
onError?: (err: Error) => void;
|
|
9
|
+
}
|
|
10
|
+
export interface UseEmbeddingReturn {
|
|
11
|
+
embeddings: Ref<number[][]>;
|
|
12
|
+
isLoading: Ref<boolean>;
|
|
13
|
+
error: Ref<Error | null>;
|
|
14
|
+
result: Ref<EmbeddingResult | null>;
|
|
15
|
+
embed: (input: string | string[], options?: Partial<EmbeddingRequest>) => Promise<EmbeddingResult>;
|
|
16
|
+
abortController: Ref<AbortController | null>;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Vue 3 composable for generating text embeddings.
|
|
20
|
+
*
|
|
21
|
+
* ```ts
|
|
22
|
+
* const { embed, embeddings, isLoading } = useEmbedding({
|
|
23
|
+
* provider: openai({ apiKey: import.meta.env.VITE_OPENAI_KEY })
|
|
24
|
+
* })
|
|
25
|
+
* const { embeddings: vecs } = await embed('hello world')
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export declare function useEmbedding(options: UseEmbeddingOptions): UseEmbeddingReturn;
|
|
29
|
+
//# sourceMappingURL=useEmbedding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useEmbedding.d.ts","sourceRoot":"","sources":["../../src/composables/useEmbedding.ts"],"names":[],"mappings":"AAAA,OAAO,EAAmB,KAAK,GAAG,EAAE,MAAM,KAAK,CAAA;AAC/C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACtD,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAEjE,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,YAAY,CAAA;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAA;IAC1C,SAAS,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,KAAK,IAAI,CAAA;IAC7C,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;CAC/B;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAA;IAC3B,SAAS,EAAE,GAAG,CAAC,OAAO,CAAC,CAAA;IACvB,KAAK,EAAE,GAAG,CAAC,KAAK,GAAG,IAAI,CAAC,CAAA;IACxB,MAAM,EAAE,GAAG,CAAC,eAAe,GAAG,IAAI,CAAC,CAAA;IACnC,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,gBAAgB,CAAC,KAAK,OAAO,CAAC,eAAe,CAAC,CAAA;IAClG,eAAe,EAAE,GAAG,CAAC,eAAe,GAAG,IAAI,CAAC,CAAA;CAC7C;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,kBAAkB,CA6C7E"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { type Ref } from 'vue';
|
|
2
|
+
/**
|
|
3
|
+
* Options for `usePersist` โ drop-in localStorage persistence for a single
|
|
4
|
+
* reactive ref.
|
|
5
|
+
*/
|
|
6
|
+
export interface UsePersistOptions<T> {
|
|
7
|
+
/** localStorage key. Required. */
|
|
8
|
+
key: string;
|
|
9
|
+
/**
|
|
10
|
+
* Bump this when the shape of `T` changes incompatibly. The persisted
|
|
11
|
+
* payload is stored under `${key}:v${version}` so old data is naturally
|
|
12
|
+
* ignored. Omit to skip versioning.
|
|
13
|
+
*/
|
|
14
|
+
version?: number;
|
|
15
|
+
/**
|
|
16
|
+
* Custom serializer. Receives the current value, returns the JSON-safe
|
|
17
|
+
* representation. Default: identity.
|
|
18
|
+
*/
|
|
19
|
+
serialize?: (value: T) => unknown;
|
|
20
|
+
/**
|
|
21
|
+
* Custom deserializer. Receives the parsed JSON, returns the value (or null
|
|
22
|
+
* to discard it). Default: identity.
|
|
23
|
+
*/
|
|
24
|
+
deserialize?: (raw: unknown) => T | null;
|
|
25
|
+
/**
|
|
26
|
+
* Override the storage. Default: `window.localStorage` when available.
|
|
27
|
+
* Pass an in-memory shim in tests.
|
|
28
|
+
*/
|
|
29
|
+
storage?: Storage | null;
|
|
30
|
+
/**
|
|
31
|
+
* Ref to write errors to. Errors during load are silently skipped (the
|
|
32
|
+
* caller can decide to surface them); errors during save are written here.
|
|
33
|
+
*/
|
|
34
|
+
onError?: (err: Error) => void;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Wire a ref up to localStorage. Hydrates on creation, writes on every
|
|
38
|
+
* mutation. Returns a `clear()` function that removes the storage entry.
|
|
39
|
+
*
|
|
40
|
+
* ```ts
|
|
41
|
+
* const messages = ref<Message[]>([])
|
|
42
|
+
* const { clear } = usePersist(messages, { key: 'my-chat' })
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare function usePersist<T>(source: Ref<T>, options: UsePersistOptions<T>): {
|
|
46
|
+
clear: () => void;
|
|
47
|
+
};
|
|
48
|
+
//# sourceMappingURL=usePersist.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"usePersist.d.ts","sourceRoot":"","sources":["../../src/composables/usePersist.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,GAAG,EAAE,MAAM,KAAK,CAAA;AAErC;;;GAGG;AACH,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,kCAAkC;IAClC,GAAG,EAAE,MAAM,CAAA;IACX;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB;;;OAGG;IACH,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,OAAO,CAAA;IACjC;;;OAGG;IACH,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,CAAC,GAAG,IAAI,CAAA;IACxC;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAA;IACxB;;;OAGG;IACH,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAA;CAC/B;AAED;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,iBAAiB,CAAC,CAAC,CAAC,GAAG;IAC5E,KAAK,EAAE,MAAM,IAAI,CAAA;CAClB,CAiDA"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";var Z=Object.defineProperty;var q=(u,m,s)=>m in u?Z(u,m,{enumerable:!0,configurable:!0,writable:!0,value:s}):u[m]=s;var B=(u,m,s)=>q(u,typeof m!="symbol"?m+"":m,s);Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const P=require("vue");function F(u,m){var g,i,b,l;const s=u?[...u]:[];for(const a of m??[]){const f=a.index;f!==void 0&&(s[f]?(a.id&&(s[f].id=a.id),(b=a.function)!=null&&b.name&&(s[f].function.name+=a.function.name),(l=a.function)!=null&&l.arguments&&(s[f].function.arguments+=a.function.arguments)):s[f]={id:a.id??"",type:"function",function:{name:((g=a.function)==null?void 0:g.name)??"",arguments:((i=a.function)==null?void 0:i.arguments)??""}})}return s}function X(u,m){const{key:s,version:g,serialize:i,deserialize:b,storage:l,onError:a}=m,f=g!==void 0?`${s}:v${g}`:s,p=l!==void 0?l:typeof window<"u"?window.localStorage:null;if(p)try{const k=p.getItem(f);if(k!==null){const x=JSON.parse(k),t=b?b(x):x;t!=null&&(u.value=t)}}catch{}p&&P.watch(u,k=>{try{const x=i?i(k):k;p.setItem(f,JSON.stringify(x))}catch(x){a==null||a(x instanceof Error?x:new Error(String(x)))}},{deep:!0});function n(){if(p)try{p.removeItem(f)}catch{}}return{clear:n}}let G=0;function W(u="msg"){return G+=1,`${u}_${Date.now().toString(36)}_${G.toString(36)}`}class M extends Error{constructor(s,g={}){super(s);B(this,"cause");B(this,"status");this.name="AiHooksError",this.cause=g.cause,this.status=g.status}}const ee=[];function te(u){const{provider:m,initialMessages:s=ee,defaultRequest:g={},onUpdate:i,onFinish:b,onError:l,persist:a,tools:f,toolChoice:p,toolHandlers:n,maxToolRoundtrips:k=1}=u;if(!m)throw new Error("useChat requires a provider option");const x=m,t=P.ref([...s]),d=P.ref(""),h=P.ref(!1),T=P.ref(null),_=P.shallowRef(null),e=a?X(t,{key:a.key,version:a.version,onError:o=>{T.value=o}}):null;function c(o){t.value=[...o]}function v(){_.value&&_.value.abort(),_.value=null,t.value=[],T.value=null,h.value=!1,d.value="",e==null||e.clear()}function r(){_.value&&_.value.abort(),_.value=null,h.value=!1}function A(){return{id:W("assistant"),role:"assistant",content:"",createdAt:new Date}}function $(o){return o instanceof Error?o:new Error(String(o))}function y(o){const w=$(o);return T.value=w,l==null||l(w),w}function D(o){const w=o.function.arguments.trim();if(!w)return{};try{return JSON.parse(w)}catch(C){throw new M(`Invalid JSON arguments for tool "${o.function.name}"`,{cause:C})}}function L(o){return typeof o=="string"?o:o===void 0?"":JSON.stringify(o)}async function S(o){const w=[];for(const C of o){const R=n==null?void 0:n[C.function.name];if(!R)throw new M(`No tool handler registered for "${C.function.name}"`);const E=await R(D(C),{toolCall:C,messages:[...t.value]});w.push({id:W("tool"),role:"tool",content:L(E),toolCallId:C.id,createdAt:new Date})}return w}async function O(o,w){var R;const C=new AbortController;_.value=C,h.value=!0,T.value=null;try{const E={...g,...f&&!w.tools?{tools:f}:{},...p&&!w.toolChoice?{toolChoice:p}:{},...w,signal:C.signal},I=await x.chat(E);for await(const J of I){if(J.content){o.content+=J.content;const N=t.value.findIndex(U=>U.id===o.id);N>=0&&(t.value=[...t.value.slice(0,N),{...o},...t.value.slice(N+1)]),i==null||i({...o})}if((R=J.toolCalls)!=null&&R.length){o.toolCalls=F(o.toolCalls,J.toolCalls);const N=t.value.findIndex(U=>U.id===o.id);N>=0&&(t.value=[...t.value.slice(0,N),{...o},...t.value.slice(N+1)])}if(J.finishReason){o.metadata={...o.metadata??{},finishReason:J.finishReason};const N=t.value.findIndex(U=>U.id===o.id);N>=0&&(t.value=[...t.value.slice(0,N),{...o},...t.value.slice(N+1)])}}b==null||b({...o})}catch(E){const I=$(E);if(I.name==="AbortError"||C.signal.aborted){b==null||b({...o});return}throw T.value=I,l==null||l(I),I}finally{_.value=null,h.value=!1}}async function j(o,w,C){await O(o,w);const R=o.toolCalls;if(!n||!(R!=null&&R.length))return;if(C<=0)throw y(new M("Maximum tool roundtrips exceeded"));h.value=!0;let E;try{E=await S(R)}catch(J){throw h.value=!1,y(J)}const I=A();t.value=[...t.value,...E,I],await j(I,{messages:t.value.filter(J=>J.id!==I.id)},C-1)}async function z(o,w={}){const C=typeof o=="string"?{id:W("user"),role:"user",content:o,createdAt:new Date}:{...o,id:o.id||W(o.role)},R=A();t.value=[...t.value,C,R],await j(R,{messages:t.value.filter(E=>E.id!==R.id),...w},k)}async function H(){var R;const o=(R=[...t.value].map((E,I)=>({m:E,i:I})).reverse().find(({m:E})=>E.role==="assistant"))==null?void 0:R.i;if(o===void 0)throw new M("reload() called with no assistant message to re-run");const w=t.value.slice(0,o),C=A();t.value=[...w,C],await j(C,{messages:w},k)}return{messages:t,input:d,isLoading:h,error:T,append:z,reload:H,stop:r,setMessages:c,clear:v,abortController:_}}function oe(u){const{provider:m,initialCompletion:s="",defaultRequest:g={},onFinish:i,onError:b}=u,l=P.ref(s),a=P.ref(""),f=P.ref(!1),p=P.ref(null),n=P.shallowRef(null);function k(){n.value&&n.value.abort(),n.value=null,f.value=!1}function x(d){l.value=d}async function t(d,h={}){const T=d??a.value;if(!T)throw new Error("complete() requires a prompt (either as argument or via input.value)");const _=new AbortController;n.value=_,f.value=!0,p.value=null,l.value="";try{const e=await m.completion({...g,...h,prompt:T,signal:_.signal,stream:!0});for await(const c of e)l.value+=c;return i==null||i(l.value),l.value}catch(e){const c=e instanceof Error?e:new Error(String(e));if(c.name==="AbortError"||_.signal.aborted)return i==null||i(l.value),l.value;throw p.value=c,b==null||b(c),c}finally{n.value=null,f.value=!1}}return{completion:l,input:a,isLoading:f,error:p,complete:t,stop:k,setCompletion:x,abortController:n}}function ne(u){const{provider:m,defaultRequest:s={},onSuccess:g,onError:i}=u,b=P.ref([]),l=P.ref(!1),a=P.ref(null),f=P.shallowRef(null),p=P.shallowRef(null);async function n(k,x={}){const t=new AbortController;p.value=t,l.value=!0,a.value=null;try{const d=await m.embedding({...s,...x,input:k,signal:t.signal});return b.value=d.embeddings,f.value=d,g==null||g(d),d}catch(d){const h=d instanceof Error?d:new Error(String(d));throw a.value=h,i==null||i(h),h}finally{p.value=null,l.value=!1}}return{embeddings:b,isLoading:l,error:a,result:f,embed:n,abortController:p}}async function*V(u,m){if(!u.body)throw new Error("Response has no body");const s=u.body.getReader(),g=new TextDecoder("utf-8");let i="";try{for(;;){if(m!=null&&m.aborted){await s.cancel();return}const{done:b,value:l}=await s.read();if(b)break;i+=g.decode(l,{stream:!0}).replace(/\r\n/g,`
|
|
2
|
+
`).replace(/\r/g,`
|
|
3
|
+
`);let a;for(;(a=i.indexOf(`
|
|
4
|
+
|
|
5
|
+
`))!==-1;){const f=i.slice(0,a);i=i.slice(a+2);for(const p of f.split(`
|
|
6
|
+
`)){if(!p.startsWith("data:"))continue;const n=p.slice(5).trim();if(n==="[DONE]")return;if(n)try{yield JSON.parse(n)}catch{}}}}}finally{s.releaseLock()}}async function K(u,m={}){const{timeoutMs:s,signal:g,fetcher:i,...b}=m,l=i??(typeof globalThis.fetch=="function"?globalThis.fetch.bind(globalThis):void 0);if(!l)throw new M("No fetch implementation available");const a=new AbortController,f=()=>a.abort();g&&(g.aborted?a.abort():g.addEventListener("abort",f,{once:!0}));let p;s&&s>0&&(p=setTimeout(()=>a.abort(),s));try{const n=await l(u,{...b,signal:a.signal});if(!n.ok){let k;try{k=await n.json()}catch{k=await n.text().catch(()=>{})}throw new M(`Request failed with status ${n.status} ${n.statusText}`,{status:n.status,cause:k})}return n}catch(n){throw n instanceof M?n:(n==null?void 0:n.name)==="AbortError"?new M("Request aborted",{cause:n}):new M("Network error",{cause:n})}finally{p&&clearTimeout(p),g&&g.removeEventListener("abort",f)}}function Y(u){const{apiKey:m,baseURL:s,headers:g={},defaultModel:i,chatPath:b="/chat/completions",completionPath:l="/completions",embeddingPath:a="/embeddings",fetch:f}=u,p={"Content-Type":"application/json",Authorization:`Bearer ${m}`,...g},n=(x,t)=>{const d=x.replace(/\/+$/,""),h=t.startsWith("/")?t:`/${t}`;return`${d}${h}`};function k(x){return x.map(t=>{var h;const d={role:t.role,content:t.content};return t.name&&(d.name=t.name),t.toolCallId&&(d.tool_call_id=t.toolCallId),(h=t.toolCalls)!=null&&h.length&&(d.tool_calls=t.toolCalls),d})}return{id:"openai-compatible",async chat(x){const{messages:t,model:d=i,temperature:h,maxTokens:T,topP:_,frequencyPenalty:e,presencePenalty:c,stop:v,tools:r,toolChoice:A,user:$,stream:y=!0,signal:D,headers:L}=x,S={model:d,messages:k(t),stream:y};h!==void 0&&(S.temperature=h),T!==void 0&&(S.max_tokens=T),_!==void 0&&(S.top_p=_),e!==void 0&&(S.frequency_penalty=e),c!==void 0&&(S.presence_penalty=c),v!==void 0&&(S.stop=v),r&&(S.tools=r),A&&(S.tool_choice=A),$&&(S.user=$);const O=await K(n(s,b),{method:"POST",headers:{...p,...L},body:JSON.stringify(S),signal:D,fetcher:f});if(!y){const j=await O.json();return async function*(){var z,H,o,w,C;yield{content:((o=(H=(z=j.choices)==null?void 0:z[0])==null?void 0:H.message)==null?void 0:o.content)??"",finishReason:((C=(w=j.choices)==null?void 0:w[0])==null?void 0:C.finish_reason)??"stop",usage:j.usage}}()}return async function*(){var j,z,H;for await(const o of V(O,D)){const w=(j=o.choices)==null?void 0:j[0];if(!w)continue;const C=o.usage;yield{content:(z=w.delta)==null?void 0:z.content,toolCalls:(H=w.delta)==null?void 0:H.tool_calls,finishReason:w.finish_reason??void 0,usage:C}}}()},async completion(x){const{prompt:t,model:d=i,temperature:h,maxTokens:T,topP:_,frequencyPenalty:e,presencePenalty:c,stop:v,stream:r=!0,signal:A,headers:$}=x,y={model:d,prompt:t,stream:r};h!==void 0&&(y.temperature=h),T!==void 0&&(y.max_tokens=T),_!==void 0&&(y.top_p=_),e!==void 0&&(y.frequency_penalty=e),c!==void 0&&(y.presence_penalty=c),v!==void 0&&(y.stop=v);const D=await K(n(s,l),{method:"POST",headers:{...p,...$},body:JSON.stringify(y),signal:A,fetcher:f});if(!r){const L=await D.json();return async function*(){var S,O;yield((O=(S=L.choices)==null?void 0:S[0])==null?void 0:O.text)??""}()}return async function*(){var L;for await(const S of V(D,A)){const O=(L=S.choices)==null?void 0:L[0];O!=null&&O.text&&(yield O.text)}}()},async embedding(x){const{input:t,model:d=i,user:h,signal:T,headers:_}=x,e={input:t};d&&(e.model=d),h&&(e.user=h);const v=await(await K(n(s,a),{method:"POST",headers:{...p,..._},body:JSON.stringify(e),signal:T,fetcher:f})).json();return{embeddings:v.data.map(r=>r.embedding),model:v.model,usage:{promptTokens:v.usage.prompt_tokens,totalTokens:v.usage.total_tokens}}}}}function ae(u){return Y({baseURL:"https://api.openai.com/v1",defaultModel:"gpt-4o-mini",...u})}function Q(u){switch(u){case"end_turn":case"stop_sequence":return"stop";case"max_tokens":return"length";case"tool_use":return"tool_calls";default:return"stop"}}function se(u){const{apiKey:m,baseURL:s="https://api.anthropic.com",defaultModel:g="claude-3-5-sonnet-20241022",maxTokens:i=1024,anthropicVersion:b="2023-06-01",headers:l={},fetch:a}=u,f={"Content-Type":"application/json","x-api-key":m,"anthropic-version":b,...l},p=e=>{const c=s.replace(/\/+$/,""),v=e.startsWith("/")?e:`/${e}`;return`${c}${v}`};function n(e){return typeof e=="string"?e:e.map(c=>k(c))}function k(e){if(e.type==="text")return{type:"text",text:e.text};const c=e.image_url.url;if(c.startsWith("data:")){const v=c.match(/^data:([^;]+);base64,(.*)$/s);if(v)return{type:"image",source:{type:"base64",media_type:v[1],data:v[2]}}}return{type:"image",source:{type:"url",url:c}}}function x(e){const c=[],v=[];for(const r of e)r.role==="system"?typeof r.content=="string"&&r.content&&c.push(r.content):(r.role==="user"||r.role==="assistant")&&v.push({role:r.role,content:n(r.content)});return{system:c.length?c.join(`
|
|
7
|
+
|
|
8
|
+
`):void 0,messages:v}}function t(e){const{system:c,messages:v}=x(e.messages),r={model:e.model??g,messages:v,max_tokens:e.maxTokens??i,stream:e.stream??!0};return c&&(r.system=c),e.temperature!==void 0&&(r.temperature=e.temperature),e.topP!==void 0&&(r.top_p=e.topP),e.stop!==void 0&&(r.stop_sequences=Array.isArray(e.stop)?e.stop:[e.stop]),e.user&&(r.metadata={user_id:e.user}),r}async function d(e){const c=t({...e,stream:e.stream??!0}),v=await K(p("/v1/messages"),{method:"POST",headers:{...f,...e.headers},body:JSON.stringify(c),signal:e.signal,fetcher:a});if(e.stream===!1){const r=await v.json();return async function*(){const A=r.content.find($=>$.type==="text");yield{content:(A==null?void 0:A.text)??"",finishReason:Q(r.stop_reason),usage:r.usage?{promptTokens:r.usage.input_tokens,completionTokens:r.usage.output_tokens,totalTokens:r.usage.input_tokens+r.usage.output_tokens}:void 0}}()}return async function*(){var r;for await(const A of V(v,e.signal)){const $=A.type;if($==="content_block_delta"){const y=A.delta;(y==null?void 0:y.type)==="text_delta"&&typeof y.text=="string"&&(yield{content:y.text})}else if($==="message_delta"){const y=(r=A.delta)==null?void 0:r.stop_reason;y&&(yield{finishReason:Q(y)})}else if($==="error"){const y=A.error;throw new M(`Anthropic API error: ${(y==null?void 0:y.type)??"unknown"}: ${(y==null?void 0:y.message)??"unknown"}`,{cause:A})}}}()}async function h(e){return(async function*(){const c=await this.chat({...e,messages:[{id:"prompt",role:"user",content:e.prompt}]});for await(const v of c)v.content&&(yield v.content)}).call(_)}async function T(){throw new M("Anthropic has no embedding API",{status:501})}const _={id:"anthropic",chat:d,completion:h,embedding:T};return _}exports.AiHooksError=M;exports.anthropic=se;exports.openai=ae;exports.openaiCompatible=Y;exports.useChat=te;exports.useCompletion=oe;exports.useEmbedding=ne;exports.usePersist=X;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* vue-ai-hooks โ Vue 3 Composable library for AI applications.
|
|
3
|
+
*
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
export { useChat, type ToolCallHandler, type ToolCallHandlerContext, type UseChatOptions, type UseChatReturn } from './composables/useChat';
|
|
7
|
+
export { useCompletion, type UseCompletionOptions, type UseCompletionReturn } from './composables/useCompletion';
|
|
8
|
+
export { useEmbedding, type UseEmbeddingOptions, type UseEmbeddingReturn } from './composables/useEmbedding';
|
|
9
|
+
export { openai, openaiCompatible, type OpenAiLikeConfig } from './providers/openai';
|
|
10
|
+
export { anthropic, type AnthropicConfig } from './providers/anthropic';
|
|
11
|
+
export { type ChatProvider } from './providers/types';
|
|
12
|
+
export { usePersist, type UsePersistOptions } from './composables/usePersist';
|
|
13
|
+
export type { Message, MessageRole, MessageContent, ContentPart, TextPart, ImageUrlPart, Tool, ToolCall, ChatRequest, ChatChunk, CompletionRequest, EmbeddingRequest, EmbeddingResult } from './types';
|
|
14
|
+
export { AiHooksError } from './types';
|
|
15
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,EACL,OAAO,EACP,KAAK,eAAe,EACpB,KAAK,sBAAsB,EAC3B,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,uBAAuB,CAAA;AAC9B,OAAO,EACL,aAAa,EACb,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACzB,MAAM,6BAA6B,CAAA;AACpC,OAAO,EACL,YAAY,EACZ,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACxB,MAAM,4BAA4B,CAAA;AAGnC,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAA;AACpF,OAAO,EAAE,SAAS,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAA;AACvE,OAAO,EAAE,KAAK,YAAY,EAAE,MAAM,mBAAmB,CAAA;AAGrD,OAAO,EAAE,UAAU,EAAE,KAAK,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAG7E,YAAY,EACV,OAAO,EACP,WAAW,EACX,cAAc,EACd,WAAW,EACX,QAAQ,EACR,YAAY,EACZ,IAAI,EACJ,QAAQ,EACR,WAAW,EACX,SAAS,EACT,iBAAiB,EACjB,gBAAgB,EAChB,eAAe,EAChB,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA"}
|