@yh-ui/hooks 0.1.16 → 0.1.21
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 +289 -0
- package/dist/index.cjs +147 -1459
- package/dist/index.d.ts +18 -698
- package/dist/index.mjs +15 -1426
- package/dist/storage/index.cjs +128 -0
- package/dist/storage/index.d.ts +26 -0
- package/dist/storage/index.mjs +112 -0
- package/dist/use-ai/index.cjs +11 -0
- package/dist/use-ai/index.d.ts +1 -0
- package/dist/use-ai/index.mjs +1 -0
- package/dist/use-ai/use-ai-conversations.cjs +73 -6
- package/dist/use-ai/use-ai-conversations.d.ts +34 -4
- package/dist/use-ai/use-ai-conversations.mjs +71 -4
- package/dist/use-ai/use-ai-persistence.cjs +160 -0
- package/dist/use-ai/use-ai-persistence.d.ts +62 -0
- package/dist/use-ai/use-ai-persistence.mjs +154 -0
- package/dist/use-ai/use-ai-voice.cjs +19 -18
- package/dist/use-ai/use-ai-voice.mjs +19 -18
- package/dist/use-countdown/index.cjs +51 -0
- package/dist/use-countdown/index.d.ts +17 -0
- package/dist/use-countdown/index.mjs +40 -0
- package/dist/use-sku/index.cjs +62 -0
- package/dist/use-sku/index.d.ts +30 -0
- package/dist/use-sku/index.mjs +58 -0
- package/dist/use-virtual-scroll/index.cjs +17 -26
- package/dist/use-virtual-scroll/index.d.ts +5 -9
- package/dist/use-virtual-scroll/index.mjs +18 -23
- package/package.json +6 -4
- package/dist/index.d.cts +0 -699
- package/dist/index.d.mts +0 -699
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useAiPersistence = useAiPersistence;
|
|
7
|
+
var _vue = require("vue");
|
|
8
|
+
var _storage = require("../storage/index.cjs");
|
|
9
|
+
function useAiPersistence(options = {}) {
|
|
10
|
+
const {
|
|
11
|
+
storage = new _storage.IndexedDBAdapter(),
|
|
12
|
+
conversationKey = "ai-conversations",
|
|
13
|
+
autoSave = true
|
|
14
|
+
} = options;
|
|
15
|
+
const conversations = (0, _vue.ref)([]);
|
|
16
|
+
const currentConversationId = (0, _vue.ref)(null);
|
|
17
|
+
const isLoading = (0, _vue.ref)(false);
|
|
18
|
+
const isSaving = (0, _vue.ref)(false);
|
|
19
|
+
const error = (0, _vue.ref)(null);
|
|
20
|
+
const loadConversations = async () => {
|
|
21
|
+
isLoading.value = true;
|
|
22
|
+
error.value = null;
|
|
23
|
+
try {
|
|
24
|
+
const data = await storage.get(conversationKey);
|
|
25
|
+
conversations.value = data || [];
|
|
26
|
+
if (conversations.value.length > 0) {
|
|
27
|
+
const lastConversation = conversations.value[conversations.value.length - 1];
|
|
28
|
+
currentConversationId.value = lastConversation.id;
|
|
29
|
+
}
|
|
30
|
+
} catch (e) {
|
|
31
|
+
error.value = e instanceof Error ? e : new Error("Failed to load conversations");
|
|
32
|
+
} finally {
|
|
33
|
+
isLoading.value = false;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
const saveConversations = async () => {
|
|
37
|
+
isSaving.value = true;
|
|
38
|
+
error.value = null;
|
|
39
|
+
try {
|
|
40
|
+
await storage.set(conversationKey, conversations.value);
|
|
41
|
+
} catch (e) {
|
|
42
|
+
error.value = e instanceof Error ? e : new Error("Failed to save conversations");
|
|
43
|
+
} finally {
|
|
44
|
+
isSaving.value = false;
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
const createConversation = (title = "New Conversation") => {
|
|
48
|
+
const now = Date.now();
|
|
49
|
+
const conversation = {
|
|
50
|
+
id: `conv-${now}`,
|
|
51
|
+
title,
|
|
52
|
+
messages: [],
|
|
53
|
+
createdAt: now,
|
|
54
|
+
updatedAt: now
|
|
55
|
+
};
|
|
56
|
+
conversations.value.push(conversation);
|
|
57
|
+
currentConversationId.value = conversation.id;
|
|
58
|
+
if (autoSave) {
|
|
59
|
+
saveConversations();
|
|
60
|
+
}
|
|
61
|
+
return conversation;
|
|
62
|
+
};
|
|
63
|
+
const deleteConversation = id => {
|
|
64
|
+
const index = conversations.value.findIndex(c => c.id === id);
|
|
65
|
+
if (index !== -1) {
|
|
66
|
+
conversations.value.splice(index, 1);
|
|
67
|
+
if (currentConversationId.value === id) {
|
|
68
|
+
currentConversationId.value = conversations.value.length > 0 ? conversations.value[conversations.value.length - 1].id : null;
|
|
69
|
+
}
|
|
70
|
+
if (autoSave) {
|
|
71
|
+
saveConversations();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const getCurrentConversation = () => {
|
|
76
|
+
return conversations.value.find(c => c.id === currentConversationId.value);
|
|
77
|
+
};
|
|
78
|
+
const addMessage = message => {
|
|
79
|
+
const conversation = getCurrentConversation();
|
|
80
|
+
if (!conversation) return;
|
|
81
|
+
const newMessage = {
|
|
82
|
+
...message,
|
|
83
|
+
id: `msg-${Date.now()}`,
|
|
84
|
+
timestamp: Date.now()
|
|
85
|
+
};
|
|
86
|
+
conversation.messages.push(newMessage);
|
|
87
|
+
conversation.updatedAt = Date.now();
|
|
88
|
+
if (autoSave) {
|
|
89
|
+
saveConversations();
|
|
90
|
+
}
|
|
91
|
+
return newMessage;
|
|
92
|
+
};
|
|
93
|
+
const updateMessage = (messageId, updates) => {
|
|
94
|
+
const conversation = getCurrentConversation();
|
|
95
|
+
if (!conversation) return;
|
|
96
|
+
const messageIndex = conversation.messages.findIndex(m => m.id === messageId);
|
|
97
|
+
if (messageIndex !== -1) {
|
|
98
|
+
conversation.messages[messageIndex] = {
|
|
99
|
+
...conversation.messages[messageIndex],
|
|
100
|
+
...updates
|
|
101
|
+
};
|
|
102
|
+
conversation.updatedAt = Date.now();
|
|
103
|
+
if (autoSave) {
|
|
104
|
+
saveConversations();
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
const clearCurrentConversation = () => {
|
|
109
|
+
const conversation = getCurrentConversation();
|
|
110
|
+
if (!conversation) return;
|
|
111
|
+
conversation.messages = [];
|
|
112
|
+
conversation.updatedAt = Date.now();
|
|
113
|
+
if (autoSave) {
|
|
114
|
+
saveConversations();
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
const exportConversations = () => {
|
|
118
|
+
return JSON.stringify(conversations.value, null, 2);
|
|
119
|
+
};
|
|
120
|
+
const importConversations = async json => {
|
|
121
|
+
try {
|
|
122
|
+
const imported = JSON.parse(json);
|
|
123
|
+
if (!Array.isArray(imported)) {
|
|
124
|
+
throw new Error("Invalid format: expected array");
|
|
125
|
+
}
|
|
126
|
+
conversations.value = imported;
|
|
127
|
+
await saveConversations();
|
|
128
|
+
return true;
|
|
129
|
+
} catch (e) {
|
|
130
|
+
error.value = e instanceof Error ? e : new Error("Failed to import conversations");
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const setCurrentConversation = id => {
|
|
135
|
+
if (conversations.value.some(c => c.id === id)) {
|
|
136
|
+
currentConversationId.value = id;
|
|
137
|
+
}
|
|
138
|
+
};
|
|
139
|
+
(0, _vue.onMounted)(() => {
|
|
140
|
+
loadConversations();
|
|
141
|
+
});
|
|
142
|
+
return {
|
|
143
|
+
conversations,
|
|
144
|
+
currentConversationId,
|
|
145
|
+
isLoading,
|
|
146
|
+
isSaving,
|
|
147
|
+
error,
|
|
148
|
+
loadConversations,
|
|
149
|
+
saveConversations,
|
|
150
|
+
createConversation,
|
|
151
|
+
deleteConversation,
|
|
152
|
+
getCurrentConversation,
|
|
153
|
+
addMessage,
|
|
154
|
+
updateMessage,
|
|
155
|
+
clearCurrentConversation,
|
|
156
|
+
exportConversations,
|
|
157
|
+
importConversations,
|
|
158
|
+
setCurrentConversation
|
|
159
|
+
};
|
|
160
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { type StorageAdapter } from '../storage';
|
|
2
|
+
export interface ConversationMessage {
|
|
3
|
+
id: string;
|
|
4
|
+
role: 'user' | 'assistant' | 'system';
|
|
5
|
+
content: string;
|
|
6
|
+
timestamp: number;
|
|
7
|
+
metadata?: Record<string, unknown>;
|
|
8
|
+
}
|
|
9
|
+
export interface Conversation {
|
|
10
|
+
id: string;
|
|
11
|
+
title: string;
|
|
12
|
+
messages: ConversationMessage[];
|
|
13
|
+
createdAt: number;
|
|
14
|
+
updatedAt: number;
|
|
15
|
+
}
|
|
16
|
+
export interface UseAiPersistenceOptions {
|
|
17
|
+
storage?: StorageAdapter;
|
|
18
|
+
conversationKey?: string;
|
|
19
|
+
autoSave?: boolean;
|
|
20
|
+
}
|
|
21
|
+
export declare function useAiPersistence(options?: UseAiPersistenceOptions): {
|
|
22
|
+
conversations: import("vue").Ref<{
|
|
23
|
+
id: string;
|
|
24
|
+
title: string;
|
|
25
|
+
messages: {
|
|
26
|
+
id: string;
|
|
27
|
+
role: "user" | "assistant" | "system";
|
|
28
|
+
content: string;
|
|
29
|
+
timestamp: number;
|
|
30
|
+
metadata?: Record<string, unknown> | undefined;
|
|
31
|
+
}[];
|
|
32
|
+
createdAt: number;
|
|
33
|
+
updatedAt: number;
|
|
34
|
+
}[], Conversation[] | {
|
|
35
|
+
id: string;
|
|
36
|
+
title: string;
|
|
37
|
+
messages: {
|
|
38
|
+
id: string;
|
|
39
|
+
role: "user" | "assistant" | "system";
|
|
40
|
+
content: string;
|
|
41
|
+
timestamp: number;
|
|
42
|
+
metadata?: Record<string, unknown> | undefined;
|
|
43
|
+
}[];
|
|
44
|
+
createdAt: number;
|
|
45
|
+
updatedAt: number;
|
|
46
|
+
}[]>;
|
|
47
|
+
currentConversationId: import("vue").Ref<string | null, string | null>;
|
|
48
|
+
isLoading: import("vue").Ref<boolean, boolean>;
|
|
49
|
+
isSaving: import("vue").Ref<boolean, boolean>;
|
|
50
|
+
error: import("vue").Ref<Error | null, Error | null>;
|
|
51
|
+
loadConversations: () => Promise<void>;
|
|
52
|
+
saveConversations: () => Promise<void>;
|
|
53
|
+
createConversation: (title?: string) => Conversation;
|
|
54
|
+
deleteConversation: (id: string) => void;
|
|
55
|
+
getCurrentConversation: () => Conversation | undefined;
|
|
56
|
+
addMessage: (message: Omit<ConversationMessage, "id" | "timestamp">) => ConversationMessage | undefined;
|
|
57
|
+
updateMessage: (messageId: string, updates: Partial<ConversationMessage>) => void;
|
|
58
|
+
clearCurrentConversation: () => void;
|
|
59
|
+
exportConversations: () => string;
|
|
60
|
+
importConversations: (json: string) => Promise<boolean>;
|
|
61
|
+
setCurrentConversation: (id: string) => void;
|
|
62
|
+
};
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { ref, onMounted } from "vue";
|
|
2
|
+
import { IndexedDBAdapter } from "../storage/index.mjs";
|
|
3
|
+
export function useAiPersistence(options = {}) {
|
|
4
|
+
const {
|
|
5
|
+
storage = new IndexedDBAdapter(),
|
|
6
|
+
conversationKey = "ai-conversations",
|
|
7
|
+
autoSave = true
|
|
8
|
+
} = options;
|
|
9
|
+
const conversations = ref([]);
|
|
10
|
+
const currentConversationId = ref(null);
|
|
11
|
+
const isLoading = ref(false);
|
|
12
|
+
const isSaving = ref(false);
|
|
13
|
+
const error = ref(null);
|
|
14
|
+
const loadConversations = async () => {
|
|
15
|
+
isLoading.value = true;
|
|
16
|
+
error.value = null;
|
|
17
|
+
try {
|
|
18
|
+
const data = await storage.get(conversationKey);
|
|
19
|
+
conversations.value = data || [];
|
|
20
|
+
if (conversations.value.length > 0) {
|
|
21
|
+
const lastConversation = conversations.value[conversations.value.length - 1];
|
|
22
|
+
currentConversationId.value = lastConversation.id;
|
|
23
|
+
}
|
|
24
|
+
} catch (e) {
|
|
25
|
+
error.value = e instanceof Error ? e : new Error("Failed to load conversations");
|
|
26
|
+
} finally {
|
|
27
|
+
isLoading.value = false;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const saveConversations = async () => {
|
|
31
|
+
isSaving.value = true;
|
|
32
|
+
error.value = null;
|
|
33
|
+
try {
|
|
34
|
+
await storage.set(conversationKey, conversations.value);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
error.value = e instanceof Error ? e : new Error("Failed to save conversations");
|
|
37
|
+
} finally {
|
|
38
|
+
isSaving.value = false;
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
const createConversation = (title = "New Conversation") => {
|
|
42
|
+
const now = Date.now();
|
|
43
|
+
const conversation = {
|
|
44
|
+
id: `conv-${now}`,
|
|
45
|
+
title,
|
|
46
|
+
messages: [],
|
|
47
|
+
createdAt: now,
|
|
48
|
+
updatedAt: now
|
|
49
|
+
};
|
|
50
|
+
conversations.value.push(conversation);
|
|
51
|
+
currentConversationId.value = conversation.id;
|
|
52
|
+
if (autoSave) {
|
|
53
|
+
saveConversations();
|
|
54
|
+
}
|
|
55
|
+
return conversation;
|
|
56
|
+
};
|
|
57
|
+
const deleteConversation = (id) => {
|
|
58
|
+
const index = conversations.value.findIndex((c) => c.id === id);
|
|
59
|
+
if (index !== -1) {
|
|
60
|
+
conversations.value.splice(index, 1);
|
|
61
|
+
if (currentConversationId.value === id) {
|
|
62
|
+
currentConversationId.value = conversations.value.length > 0 ? conversations.value[conversations.value.length - 1].id : null;
|
|
63
|
+
}
|
|
64
|
+
if (autoSave) {
|
|
65
|
+
saveConversations();
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const getCurrentConversation = () => {
|
|
70
|
+
return conversations.value.find((c) => c.id === currentConversationId.value);
|
|
71
|
+
};
|
|
72
|
+
const addMessage = (message) => {
|
|
73
|
+
const conversation = getCurrentConversation();
|
|
74
|
+
if (!conversation) return;
|
|
75
|
+
const newMessage = {
|
|
76
|
+
...message,
|
|
77
|
+
id: `msg-${Date.now()}`,
|
|
78
|
+
timestamp: Date.now()
|
|
79
|
+
};
|
|
80
|
+
conversation.messages.push(newMessage);
|
|
81
|
+
conversation.updatedAt = Date.now();
|
|
82
|
+
if (autoSave) {
|
|
83
|
+
saveConversations();
|
|
84
|
+
}
|
|
85
|
+
return newMessage;
|
|
86
|
+
};
|
|
87
|
+
const updateMessage = (messageId, updates) => {
|
|
88
|
+
const conversation = getCurrentConversation();
|
|
89
|
+
if (!conversation) return;
|
|
90
|
+
const messageIndex = conversation.messages.findIndex((m) => m.id === messageId);
|
|
91
|
+
if (messageIndex !== -1) {
|
|
92
|
+
conversation.messages[messageIndex] = {
|
|
93
|
+
...conversation.messages[messageIndex],
|
|
94
|
+
...updates
|
|
95
|
+
};
|
|
96
|
+
conversation.updatedAt = Date.now();
|
|
97
|
+
if (autoSave) {
|
|
98
|
+
saveConversations();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const clearCurrentConversation = () => {
|
|
103
|
+
const conversation = getCurrentConversation();
|
|
104
|
+
if (!conversation) return;
|
|
105
|
+
conversation.messages = [];
|
|
106
|
+
conversation.updatedAt = Date.now();
|
|
107
|
+
if (autoSave) {
|
|
108
|
+
saveConversations();
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
const exportConversations = () => {
|
|
112
|
+
return JSON.stringify(conversations.value, null, 2);
|
|
113
|
+
};
|
|
114
|
+
const importConversations = async (json) => {
|
|
115
|
+
try {
|
|
116
|
+
const imported = JSON.parse(json);
|
|
117
|
+
if (!Array.isArray(imported)) {
|
|
118
|
+
throw new Error("Invalid format: expected array");
|
|
119
|
+
}
|
|
120
|
+
conversations.value = imported;
|
|
121
|
+
await saveConversations();
|
|
122
|
+
return true;
|
|
123
|
+
} catch (e) {
|
|
124
|
+
error.value = e instanceof Error ? e : new Error("Failed to import conversations");
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const setCurrentConversation = (id) => {
|
|
129
|
+
if (conversations.value.some((c) => c.id === id)) {
|
|
130
|
+
currentConversationId.value = id;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
onMounted(() => {
|
|
134
|
+
loadConversations();
|
|
135
|
+
});
|
|
136
|
+
return {
|
|
137
|
+
conversations,
|
|
138
|
+
currentConversationId,
|
|
139
|
+
isLoading,
|
|
140
|
+
isSaving,
|
|
141
|
+
error,
|
|
142
|
+
loadConversations,
|
|
143
|
+
saveConversations,
|
|
144
|
+
createConversation,
|
|
145
|
+
deleteConversation,
|
|
146
|
+
getCurrentConversation,
|
|
147
|
+
addMessage,
|
|
148
|
+
updateMessage,
|
|
149
|
+
clearCurrentConversation,
|
|
150
|
+
exportConversations,
|
|
151
|
+
importConversations,
|
|
152
|
+
setCurrentConversation
|
|
153
|
+
};
|
|
154
|
+
}
|
|
@@ -40,11 +40,12 @@ function useAiVoice(options = {}) {
|
|
|
40
40
|
if (e.data.size > 0) chunks.push(e.data);
|
|
41
41
|
};
|
|
42
42
|
recorder.onstop = () => {
|
|
43
|
-
|
|
43
|
+
const blob = chunks.length > 0 ? new Blob(chunks, {
|
|
44
44
|
type: "audio/webm"
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
}) : null;
|
|
46
|
+
audioBlob.value = blob;
|
|
47
|
+
if (!isRecording.value) {
|
|
48
|
+
options.onStop?.(transcript.value, blob);
|
|
48
49
|
}
|
|
49
50
|
};
|
|
50
51
|
mediaRecorder.value = recorder;
|
|
@@ -124,7 +125,7 @@ function useAiVoice(options = {}) {
|
|
|
124
125
|
}
|
|
125
126
|
};
|
|
126
127
|
const start = async () => {
|
|
127
|
-
if (
|
|
128
|
+
if (stream.value) return;
|
|
128
129
|
try {
|
|
129
130
|
transcript.value = "";
|
|
130
131
|
interimTranscript.value = "";
|
|
@@ -151,31 +152,27 @@ function useAiVoice(options = {}) {
|
|
|
151
152
|
}
|
|
152
153
|
};
|
|
153
154
|
const stop = () => {
|
|
154
|
-
if (!
|
|
155
|
+
if (!stream.value) return;
|
|
155
156
|
isRecording.value = false;
|
|
156
|
-
if (
|
|
157
|
-
|
|
158
|
-
|
|
157
|
+
if (mediaRecorder.value && mediaRecorder.value.state !== "inactive") {
|
|
158
|
+
try {
|
|
159
|
+
mediaRecorder.value.stop();
|
|
160
|
+
} catch {}
|
|
159
161
|
}
|
|
160
162
|
if (recognition.value) {
|
|
161
163
|
try {
|
|
162
164
|
recognition.value.stop();
|
|
163
165
|
} catch {}
|
|
164
166
|
}
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
} catch {}
|
|
167
|
+
if (stream.value) {
|
|
168
|
+
stream.value.getTracks().forEach(track => track.stop());
|
|
169
|
+
stream.value = null;
|
|
169
170
|
}
|
|
170
171
|
cleanup();
|
|
171
172
|
};
|
|
172
173
|
const cancel = () => {
|
|
173
|
-
if (!isRecording.value) return;
|
|
174
|
+
if (!isRecording.value && !stream.value) return;
|
|
174
175
|
isRecording.value = false;
|
|
175
|
-
if (stream.value) {
|
|
176
|
-
stream.value.getTracks().forEach(track => track.stop());
|
|
177
|
-
stream.value = null;
|
|
178
|
-
}
|
|
179
176
|
if (recognition.value) {
|
|
180
177
|
try {
|
|
181
178
|
recognition.value.abort();
|
|
@@ -186,6 +183,10 @@ function useAiVoice(options = {}) {
|
|
|
186
183
|
mediaRecorder.value.stop();
|
|
187
184
|
} catch {}
|
|
188
185
|
}
|
|
186
|
+
if (stream.value) {
|
|
187
|
+
stream.value.getTracks().forEach(track => track.stop());
|
|
188
|
+
stream.value = null;
|
|
189
|
+
}
|
|
189
190
|
cleanup();
|
|
190
191
|
};
|
|
191
192
|
const cleanup = () => {
|
|
@@ -34,9 +34,10 @@ export function useAiVoice(options = {}) {
|
|
|
34
34
|
if (e.data.size > 0) chunks.push(e.data);
|
|
35
35
|
};
|
|
36
36
|
recorder.onstop = () => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
const blob = chunks.length > 0 ? new Blob(chunks, { type: "audio/webm" }) : null;
|
|
38
|
+
audioBlob.value = blob;
|
|
39
|
+
if (!isRecording.value) {
|
|
40
|
+
options.onStop?.(transcript.value, blob);
|
|
40
41
|
}
|
|
41
42
|
};
|
|
42
43
|
mediaRecorder.value = recorder;
|
|
@@ -117,7 +118,7 @@ export function useAiVoice(options = {}) {
|
|
|
117
118
|
}
|
|
118
119
|
};
|
|
119
120
|
const start = async () => {
|
|
120
|
-
if (
|
|
121
|
+
if (stream.value) return;
|
|
121
122
|
try {
|
|
122
123
|
transcript.value = "";
|
|
123
124
|
interimTranscript.value = "";
|
|
@@ -142,33 +143,29 @@ export function useAiVoice(options = {}) {
|
|
|
142
143
|
}
|
|
143
144
|
};
|
|
144
145
|
const stop = () => {
|
|
145
|
-
if (!
|
|
146
|
+
if (!stream.value) return;
|
|
146
147
|
isRecording.value = false;
|
|
147
|
-
if (
|
|
148
|
-
stream.value.getTracks().forEach((track) => track.stop());
|
|
149
|
-
stream.value = null;
|
|
150
|
-
}
|
|
151
|
-
if (recognition.value) {
|
|
148
|
+
if (mediaRecorder.value && mediaRecorder.value.state !== "inactive") {
|
|
152
149
|
try {
|
|
153
|
-
|
|
150
|
+
mediaRecorder.value.stop();
|
|
154
151
|
} catch {
|
|
155
152
|
}
|
|
156
153
|
}
|
|
157
|
-
if (
|
|
154
|
+
if (recognition.value) {
|
|
158
155
|
try {
|
|
159
|
-
|
|
156
|
+
recognition.value.stop();
|
|
160
157
|
} catch {
|
|
161
158
|
}
|
|
162
159
|
}
|
|
163
|
-
cleanup();
|
|
164
|
-
};
|
|
165
|
-
const cancel = () => {
|
|
166
|
-
if (!isRecording.value) return;
|
|
167
|
-
isRecording.value = false;
|
|
168
160
|
if (stream.value) {
|
|
169
161
|
stream.value.getTracks().forEach((track) => track.stop());
|
|
170
162
|
stream.value = null;
|
|
171
163
|
}
|
|
164
|
+
cleanup();
|
|
165
|
+
};
|
|
166
|
+
const cancel = () => {
|
|
167
|
+
if (!isRecording.value && !stream.value) return;
|
|
168
|
+
isRecording.value = false;
|
|
172
169
|
if (recognition.value) {
|
|
173
170
|
try {
|
|
174
171
|
recognition.value.abort();
|
|
@@ -181,6 +178,10 @@ export function useAiVoice(options = {}) {
|
|
|
181
178
|
} catch {
|
|
182
179
|
}
|
|
183
180
|
}
|
|
181
|
+
if (stream.value) {
|
|
182
|
+
stream.value.getTracks().forEach((track) => track.stop());
|
|
183
|
+
stream.value = null;
|
|
184
|
+
}
|
|
184
185
|
cleanup();
|
|
185
186
|
};
|
|
186
187
|
const cleanup = () => {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useCountdown = useCountdown;
|
|
7
|
+
var _vue = require("vue");
|
|
8
|
+
function useCountdown(options) {
|
|
9
|
+
const {
|
|
10
|
+
time,
|
|
11
|
+
interval = 1e3,
|
|
12
|
+
onFinish,
|
|
13
|
+
onChange
|
|
14
|
+
} = options;
|
|
15
|
+
const remain = (0, _vue.ref)(time);
|
|
16
|
+
const timer = (0, _vue.ref)(null);
|
|
17
|
+
const isRunning = (0, _vue.computed)(() => timer.value !== null);
|
|
18
|
+
const stop = () => {
|
|
19
|
+
if (timer.value) {
|
|
20
|
+
clearInterval(timer.value);
|
|
21
|
+
timer.value = null;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
const start = () => {
|
|
25
|
+
if (isRunning.value) return;
|
|
26
|
+
if (remain.value <= 0) return;
|
|
27
|
+
timer.value = setInterval(() => {
|
|
28
|
+
remain.value -= interval;
|
|
29
|
+
if (onChange) onChange(remain.value);
|
|
30
|
+
if (remain.value <= 0) {
|
|
31
|
+
remain.value = 0;
|
|
32
|
+
stop();
|
|
33
|
+
if (onFinish) onFinish();
|
|
34
|
+
}
|
|
35
|
+
}, interval);
|
|
36
|
+
};
|
|
37
|
+
const reset = newTime => {
|
|
38
|
+
stop();
|
|
39
|
+
remain.value = newTime !== void 0 ? newTime : time;
|
|
40
|
+
};
|
|
41
|
+
(0, _vue.onUnmounted)(() => {
|
|
42
|
+
stop();
|
|
43
|
+
});
|
|
44
|
+
return {
|
|
45
|
+
remain,
|
|
46
|
+
isRunning,
|
|
47
|
+
start,
|
|
48
|
+
stop,
|
|
49
|
+
reset
|
|
50
|
+
};
|
|
51
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface CountdownOptions {
|
|
2
|
+
time: number;
|
|
3
|
+
interval?: number;
|
|
4
|
+
onFinish?: () => void;
|
|
5
|
+
onChange?: (current: number) => void;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 倒计时逻辑 Hook
|
|
9
|
+
* @param options
|
|
10
|
+
*/
|
|
11
|
+
export declare function useCountdown(options: CountdownOptions): {
|
|
12
|
+
remain: import("vue").Ref<number, number>;
|
|
13
|
+
isRunning: import("vue").ComputedRef<boolean>;
|
|
14
|
+
start: () => void;
|
|
15
|
+
stop: () => void;
|
|
16
|
+
reset: (newTime?: number) => void;
|
|
17
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { ref, computed, onUnmounted } from "vue";
|
|
2
|
+
export function useCountdown(options) {
|
|
3
|
+
const { time, interval = 1e3, onFinish, onChange } = options;
|
|
4
|
+
const remain = ref(time);
|
|
5
|
+
const timer = ref(null);
|
|
6
|
+
const isRunning = computed(() => timer.value !== null);
|
|
7
|
+
const stop = () => {
|
|
8
|
+
if (timer.value) {
|
|
9
|
+
clearInterval(timer.value);
|
|
10
|
+
timer.value = null;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const start = () => {
|
|
14
|
+
if (isRunning.value) return;
|
|
15
|
+
if (remain.value <= 0) return;
|
|
16
|
+
timer.value = setInterval(() => {
|
|
17
|
+
remain.value -= interval;
|
|
18
|
+
if (onChange) onChange(remain.value);
|
|
19
|
+
if (remain.value <= 0) {
|
|
20
|
+
remain.value = 0;
|
|
21
|
+
stop();
|
|
22
|
+
if (onFinish) onFinish();
|
|
23
|
+
}
|
|
24
|
+
}, interval);
|
|
25
|
+
};
|
|
26
|
+
const reset = (newTime) => {
|
|
27
|
+
stop();
|
|
28
|
+
remain.value = newTime !== void 0 ? newTime : time;
|
|
29
|
+
};
|
|
30
|
+
onUnmounted(() => {
|
|
31
|
+
stop();
|
|
32
|
+
});
|
|
33
|
+
return {
|
|
34
|
+
remain,
|
|
35
|
+
isRunning,
|
|
36
|
+
start,
|
|
37
|
+
stop,
|
|
38
|
+
reset
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.useSKU = useSKU;
|
|
7
|
+
var _vue = require("vue");
|
|
8
|
+
function useSKU(specs, skus, initialSelection = []) {
|
|
9
|
+
const selectedValueIds = (0, _vue.ref)(initialSelection);
|
|
10
|
+
const pathDict = (0, _vue.computed)(() => {
|
|
11
|
+
const dict = {};
|
|
12
|
+
skus.forEach(sku => {
|
|
13
|
+
if (sku.stock <= 0) return;
|
|
14
|
+
const powerSet = getPowerSet(sku.specValueIds);
|
|
15
|
+
powerSet.forEach(path => {
|
|
16
|
+
const key = path.join(",");
|
|
17
|
+
dict[key] = (dict[key] || 0) + sku.stock;
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
return dict;
|
|
21
|
+
});
|
|
22
|
+
const isValueSelectable = (specIndex, valueId) => {
|
|
23
|
+
const tempSelected = [...selectedValueIds.value];
|
|
24
|
+
if (tempSelected[specIndex] === valueId) {
|
|
25
|
+
tempSelected[specIndex] = "";
|
|
26
|
+
} else {
|
|
27
|
+
tempSelected[specIndex] = valueId;
|
|
28
|
+
}
|
|
29
|
+
const query = tempSelected.filter(v => !!v).sort((a, b) => String(a).localeCompare(String(b))).join(",");
|
|
30
|
+
if (!query) return true;
|
|
31
|
+
return !!pathDict.value[query];
|
|
32
|
+
};
|
|
33
|
+
const toggleValue = (specIndex, valueId) => {
|
|
34
|
+
if (selectedValueIds.value[specIndex] === valueId) {
|
|
35
|
+
selectedValueIds.value[specIndex] = "";
|
|
36
|
+
} else {
|
|
37
|
+
selectedValueIds.value[specIndex] = valueId;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const selectedSku = (0, _vue.computed)(() => {
|
|
41
|
+
const completeSelection = selectedValueIds.value.every(v => !!v);
|
|
42
|
+
if (!completeSelection || selectedValueIds.value.length < specs.length) return null;
|
|
43
|
+
const targetKey = [...selectedValueIds.value].sort((a, b) => String(a).localeCompare(String(b))).join(",");
|
|
44
|
+
return skus.find(sku => [...sku.specValueIds].sort((a, b) => String(a).localeCompare(String(b))).join(",") === targetKey) || null;
|
|
45
|
+
});
|
|
46
|
+
function getPowerSet(arr) {
|
|
47
|
+
const result = [[]];
|
|
48
|
+
for (const item of arr) {
|
|
49
|
+
const size = result.length;
|
|
50
|
+
for (let i = 0; i < size; i++) {
|
|
51
|
+
result.push([...result[i], item]);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return result.filter(v => v.length > 0).map(v => [...v].sort((a, b) => String(a).localeCompare(String(b))));
|
|
55
|
+
}
|
|
56
|
+
return {
|
|
57
|
+
selectedValueIds,
|
|
58
|
+
isValueSelectable,
|
|
59
|
+
selectedSku,
|
|
60
|
+
toggleValue
|
|
61
|
+
};
|
|
62
|
+
}
|