feat: add new stores for cron, locale, providers, script, shared data, skills, and user info
- Implemented `cron` store to manage scheduled tasks with CRUD operations. - Created `locale` store for language settings with persistence and system language detection. - Added `providers` store to handle provider accounts and configurations with API interactions. - Developed `script` store for managing automation scripts, including recording and execution. - Introduced `sharedStore` for managing shared data across components. - Established `skills` store for fetching, installing, and managing skills from a marketplace. - Created `userinfo` store for user authentication and session management. chore: update path aliases from `@store` to `@stores` in TypeScript configuration and Vite config
This commit is contained in:
1199
src/stores/chat.ts
Normal file
1199
src/stores/chat.ts
Normal file
File diff suppressed because it is too large
Load Diff
145
src/stores/cron.ts
Normal file
145
src/stores/cron.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
/**
|
||||
* Cron State Store
|
||||
* Manages scheduled task state
|
||||
*/
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
import { hostApiFetch } from '@lib/host-api';
|
||||
import type { CronJob, CronJobCreateInput, CronJobUpdateInput } from '@lib/cron-types';
|
||||
|
||||
export const MOCK_CRON_JOBS: CronJob[] = [];
|
||||
|
||||
export const useCronStore = defineStore('cron', () => {
|
||||
const jobs = ref<CronJob[]>([]);
|
||||
const loading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
|
||||
const safeJobs = computed(() => (Array.isArray(jobs.value) ? jobs.value : []));
|
||||
const activeJobs = computed(() => safeJobs.value.filter((j) => j.enabled));
|
||||
const pausedJobs = computed(() => safeJobs.value.filter((j) => !j.enabled));
|
||||
const failedJobs = computed(() => safeJobs.value.filter((j) => j.lastRun && !j.lastRun.success));
|
||||
|
||||
const fetchJobs = async () => {
|
||||
const currentJobs = safeJobs.value;
|
||||
if (currentJobs.length === 0) {
|
||||
loading.value = true;
|
||||
}
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
const result = await hostApiFetch<CronJob[]>('/api/cron/jobs');
|
||||
jobs.value = result;
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : String(err);
|
||||
// Fallback to mock data on error for demo/development
|
||||
jobs.value = MOCK_CRON_JOBS;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const createJob = async (input: CronJobCreateInput) => {
|
||||
const job: CronJob = {
|
||||
id: `local-${Date.now()}`,
|
||||
...input,
|
||||
enabled: input.enabled ?? true,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
try {
|
||||
const result = await hostApiFetch<CronJob>('/api/cron/jobs', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
jobs.value = [...safeJobs.value, result];
|
||||
return result;
|
||||
} catch (err) {
|
||||
console.warn('Failed to create cron job via API, using local fallback:', err);
|
||||
jobs.value = [...safeJobs.value, job];
|
||||
return job;
|
||||
}
|
||||
};
|
||||
|
||||
const updateJob = async (id: string, input: CronJobUpdateInput) => {
|
||||
try {
|
||||
const updatedJob = await hostApiFetch<CronJob>(`/api/cron/jobs/${encodeURIComponent(id)}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(input),
|
||||
});
|
||||
jobs.value = safeJobs.value.map((job) => (job.id === id ? updatedJob : job));
|
||||
} catch (err) {
|
||||
console.warn('Failed to update cron job via API, using local fallback:', err);
|
||||
jobs.value = safeJobs.value.map((job) =>
|
||||
job.id === id
|
||||
? { ...job, ...input, updatedAt: new Date().toISOString() }
|
||||
: job,
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const deleteJob = async (id: string) => {
|
||||
try {
|
||||
await hostApiFetch(`/api/cron/jobs/${encodeURIComponent(id)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn('Failed to delete cron job via API, using local fallback:', err);
|
||||
}
|
||||
jobs.value = safeJobs.value.filter((job) => job.id !== id);
|
||||
};
|
||||
|
||||
const toggleJob = async (id: string, enabled: boolean) => {
|
||||
try {
|
||||
await hostApiFetch('/api/cron/toggle', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ id, enabled }),
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn('Failed to toggle cron job via API, using local fallback:', err);
|
||||
}
|
||||
jobs.value = safeJobs.value.map((job) =>
|
||||
job.id === id ? { ...job, enabled, updatedAt: new Date().toISOString() } : job,
|
||||
);
|
||||
};
|
||||
|
||||
const triggerJob = async (id: string) => {
|
||||
try {
|
||||
const result = await hostApiFetch('/api/cron/trigger', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ id }),
|
||||
});
|
||||
console.log('Cron trigger result:', result);
|
||||
} catch (err) {
|
||||
console.warn('Failed to trigger cron job via API, using local fallback:', err);
|
||||
}
|
||||
// Update lastRun locally as fallback
|
||||
jobs.value = safeJobs.value.map((job) =>
|
||||
job.id === id
|
||||
? {
|
||||
...job,
|
||||
lastRun: {
|
||||
time: new Date().toISOString(),
|
||||
success: true,
|
||||
},
|
||||
updatedAt: new Date().toISOString(),
|
||||
}
|
||||
: job,
|
||||
);
|
||||
};
|
||||
|
||||
return {
|
||||
jobs,
|
||||
loading,
|
||||
error,
|
||||
safeJobs,
|
||||
activeJobs,
|
||||
pausedJobs,
|
||||
failedJobs,
|
||||
fetchJobs,
|
||||
createJob,
|
||||
updateJob,
|
||||
deleteJob,
|
||||
toggleJob,
|
||||
triggerJob,
|
||||
};
|
||||
});
|
||||
129
src/stores/locale.ts
Normal file
129
src/stores/locale.ts
Normal file
@@ -0,0 +1,129 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { i18n, setLanguage, getLanguage, type LanguageCode } from '@src/i18n';
|
||||
import { SUPPORTED_LANGUAGES, SUPPORTED_LANGUAGE_CODES } from '@src/i18n/constants';
|
||||
import { resolveSupportedLanguage, detectSystemLanguage } from '@src/i18n/resolver';
|
||||
|
||||
// 持久化键
|
||||
const STORAGE_KEY = 'diona-language';
|
||||
|
||||
interface LocaleState {
|
||||
language: LanguageCode;
|
||||
initialized: boolean;
|
||||
}
|
||||
|
||||
export const useLocaleStore = defineStore('locale', {
|
||||
state: (): LocaleState => ({
|
||||
language: 'zh',
|
||||
initialized: false,
|
||||
}),
|
||||
|
||||
getters: {
|
||||
currentLanguage: (state) => state.language,
|
||||
isChinese: (state) => state.language === 'zh',
|
||||
isEnglish: (state) => state.language === 'en',
|
||||
isJapanese: (state) => state.language === 'ja',
|
||||
languageLabel: (state) => {
|
||||
const lang = SUPPORTED_LANGUAGES.find(l => l.code === state.language);
|
||||
return lang?.label || '中文';
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
/**
|
||||
* 初始化语言设置
|
||||
* 优先级:持久化设置 > 系统语言 > 默认中文
|
||||
*/
|
||||
async init() {
|
||||
if (this.initialized) return;
|
||||
|
||||
try {
|
||||
// 1. 尝试从 localStorage 读取持久化设置
|
||||
const saved = localStorage.getItem(STORAGE_KEY);
|
||||
let lang: LanguageCode = 'zh';
|
||||
|
||||
if (saved && SUPPORTED_LANGUAGE_CODES.includes(saved as LanguageCode)) {
|
||||
lang = saved as LanguageCode;
|
||||
} else {
|
||||
// 2. 检测系统语言
|
||||
lang = detectSystemLanguage();
|
||||
}
|
||||
|
||||
// 3. 应用语言设置
|
||||
await this.setLanguage(lang, false); // 不持久化,因为已经是持久化的值
|
||||
|
||||
this.initialized = true;
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize locale store:', error);
|
||||
// 降级处理:使用默认中文
|
||||
await this.setLanguage('zh', false);
|
||||
this.initialized = true;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 设置语言
|
||||
* @param language 目标语言代码
|
||||
* @param persist 是否持久化到 localStorage(默认为 true)
|
||||
*/
|
||||
async setLanguage(language: LanguageCode, persist: boolean = true) {
|
||||
// 验证语言代码有效性
|
||||
const resolvedLang = resolveSupportedLanguage(language);
|
||||
|
||||
if (resolvedLang === this.language) {
|
||||
return; // 语言未变化
|
||||
}
|
||||
|
||||
try {
|
||||
// 1. 更新 i18n 实例
|
||||
await setLanguage(resolvedLang, i18n);
|
||||
|
||||
// 2. 更新 store 状态
|
||||
this.language = resolvedLang;
|
||||
|
||||
// 3. 持久化到 localStorage
|
||||
if (persist) {
|
||||
localStorage.setItem(STORAGE_KEY, resolvedLang);
|
||||
}
|
||||
|
||||
// 4. 触发语言变化事件(供其他组件监听)
|
||||
window.dispatchEvent(new CustomEvent('language-changed', {
|
||||
detail: { language: resolvedLang }
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Failed to set language:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 切换语言(轮换支持的语言)
|
||||
*/
|
||||
async toggleLanguage() {
|
||||
const currentIndex = SUPPORTED_LANGUAGE_CODES.indexOf(this.language);
|
||||
const nextIndex = (currentIndex + 1) % SUPPORTED_LANGUAGE_CODES.length;
|
||||
const nextLanguage = SUPPORTED_LANGUAGE_CODES[nextIndex];
|
||||
await this.setLanguage(nextLanguage);
|
||||
},
|
||||
|
||||
/**
|
||||
* 重置为系统语言
|
||||
*/
|
||||
async resetToSystemLanguage() {
|
||||
const systemLang = detectSystemLanguage();
|
||||
await this.setLanguage(systemLang);
|
||||
},
|
||||
|
||||
/**
|
||||
* 同步 i18n 实例与 store 状态(用于外部更改的情况)
|
||||
*/
|
||||
syncWithI18n() {
|
||||
const currentLang = getLanguage();
|
||||
if (currentLang !== this.language) {
|
||||
this.language = currentLang as LanguageCode;
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 导出类型
|
||||
export type { LanguageCode };
|
||||
343
src/stores/providers.ts
Normal file
343
src/stores/providers.ts
Normal file
@@ -0,0 +1,343 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import { hostApiFetch } from '@lib/host-api';
|
||||
import { fetchProviderSnapshot } from '@lib/provider-accounts';
|
||||
import type {
|
||||
ProviderAccount,
|
||||
ProviderConfig,
|
||||
ProviderVendorInfo,
|
||||
ProviderWithKeyInfo,
|
||||
} from '@lib/providers';
|
||||
|
||||
export const useProviderStore = defineStore('providers', () => {
|
||||
const statuses = ref<ProviderWithKeyInfo[]>([]);
|
||||
const accounts = ref<ProviderAccount[]>([]);
|
||||
const vendors = ref<ProviderVendorInfo[]>([]);
|
||||
const defaultAccountId = ref<string | null>(null);
|
||||
const loading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
|
||||
const init = async () => {
|
||||
await refreshProviderSnapshot();
|
||||
|
||||
// 自动兜底:如果有账户但没设默认,自动将第一个设为默认
|
||||
if (accounts.value.length > 0 && !defaultAccountId.value) {
|
||||
try {
|
||||
await setDefaultAccount(accounts.value[0].id);
|
||||
} catch (err) {
|
||||
console.error('Auto-set default account failed:', err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const refreshProviderSnapshot = async () => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
const snapshot = await fetchProviderSnapshot();
|
||||
|
||||
statuses.value = snapshot.statuses ?? [];
|
||||
accounts.value = snapshot.accounts ?? [];
|
||||
vendors.value = snapshot.vendors ?? [];
|
||||
defaultAccountId.value = snapshot.defaultAccountId ?? null;
|
||||
loading.value = false;
|
||||
} catch (err) {
|
||||
error.value = String(err);
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const fetchProviders = async () => {
|
||||
await refreshProviderSnapshot();
|
||||
};
|
||||
|
||||
const addProvider = async (config: Omit<ProviderConfig, 'createdAt' | 'updatedAt'>, apiKey?: string) => {
|
||||
try {
|
||||
const fullConfig: ProviderConfig = {
|
||||
...config,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const result = await hostApiFetch<{ success: boolean; error?: string }>('/api/providers', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ config: fullConfig, apiKey }),
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to save provider');
|
||||
}
|
||||
|
||||
await refreshProviderSnapshot();
|
||||
} catch (err) {
|
||||
console.error('Failed to add provider:', err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const createAccount = async (account: ProviderAccount, apiKey?: string) => {
|
||||
try {
|
||||
const result = await hostApiFetch<{ success: boolean; error?: string }>('/api/provider-accounts', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ account, apiKey }),
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to create provider account');
|
||||
}
|
||||
|
||||
await refreshProviderSnapshot();
|
||||
} catch (err) {
|
||||
console.error('Failed to add account:', err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const addAccount = async (account: ProviderAccount, apiKey?: string) => {
|
||||
return createAccount(account, apiKey);
|
||||
};
|
||||
|
||||
const updateProvider = async (providerId: string, updates: Partial<ProviderConfig>, apiKey?: string) => {
|
||||
try {
|
||||
const existing = statuses.value.find((p) => p.id === providerId);
|
||||
if (!existing) {
|
||||
throw new Error('Provider not found');
|
||||
}
|
||||
|
||||
const { hasKey: _hasKey, keyMasked: _keyMasked, ...providerConfig } = existing;
|
||||
|
||||
const updatedConfig: ProviderConfig = {
|
||||
...providerConfig,
|
||||
...updates,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
|
||||
const result = await hostApiFetch<{ success: boolean; error?: string }>(`/api/providers/${encodeURIComponent(providerId)}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ updates: updatedConfig, apiKey }),
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to update provider');
|
||||
}
|
||||
|
||||
await refreshProviderSnapshot();
|
||||
} catch (err) {
|
||||
console.error('Failed to update provider:', err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const updateAccount = async (accountId: string, updates: Partial<ProviderAccount>, apiKey?: string) => {
|
||||
try {
|
||||
const result = await hostApiFetch<{ success: boolean; error?: string }>(`/api/provider-accounts/${encodeURIComponent(accountId)}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ updates, apiKey }),
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to update provider account');
|
||||
}
|
||||
|
||||
await refreshProviderSnapshot();
|
||||
} catch (err) {
|
||||
console.error('Failed to update account:', err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const deleteProvider = async (providerId: string) => {
|
||||
try {
|
||||
const result = await hostApiFetch<{ success: boolean; error?: string }>(`/api/providers/${encodeURIComponent(providerId)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to delete provider');
|
||||
}
|
||||
|
||||
await refreshProviderSnapshot();
|
||||
} catch (err) {
|
||||
console.error('Failed to delete provider:', err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const removeAccount = async (accountId: string) => {
|
||||
try {
|
||||
const result = await hostApiFetch<{ success: boolean; error?: string }>(`/api/provider-accounts/${encodeURIComponent(accountId)}`, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to delete provider account');
|
||||
}
|
||||
|
||||
await refreshProviderSnapshot();
|
||||
} catch (err) {
|
||||
console.error('Failed to delete account:', err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const deleteAccount = async (accountId: string) => {
|
||||
return removeAccount(accountId);
|
||||
};
|
||||
|
||||
const setApiKey = async (providerId: string, apiKey: string) => {
|
||||
try {
|
||||
const result = await hostApiFetch<{ success: boolean; error?: string }>(`/api/providers/${encodeURIComponent(providerId)}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ updates: {}, apiKey }),
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to set API key');
|
||||
}
|
||||
|
||||
await refreshProviderSnapshot();
|
||||
} catch (err) {
|
||||
console.error('Failed to set API key:', err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const updateProviderWithKey = async (providerId: string, updates: Partial<ProviderConfig>, apiKey?: string) => {
|
||||
try {
|
||||
const result = await hostApiFetch<{ success: boolean; error?: string }>(`/api/providers/${encodeURIComponent(providerId)}`, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ updates, apiKey }),
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to update provider');
|
||||
}
|
||||
|
||||
await refreshProviderSnapshot();
|
||||
} catch (err) {
|
||||
console.error('Failed to update provider with key:', err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const deleteApiKey = async (providerId: string) => {
|
||||
try {
|
||||
const result = await hostApiFetch<{ success: boolean; error?: string }>(
|
||||
`/api/providers/${encodeURIComponent(providerId)}?apiKeyOnly=1`,
|
||||
{ method: 'DELETE' },
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to delete API key');
|
||||
}
|
||||
|
||||
await refreshProviderSnapshot();
|
||||
} catch (err) {
|
||||
console.error('Failed to delete API key:', err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const setDefaultProvider = async (providerId: string) => {
|
||||
try {
|
||||
const result = await hostApiFetch<{ success: boolean; error?: string }>('/api/providers/default', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ providerId }),
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to set default provider');
|
||||
}
|
||||
|
||||
await refreshProviderSnapshot();
|
||||
} catch (err) {
|
||||
console.error('Failed to set default provider:', err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const setDefaultAccount = async (accountId: string) => {
|
||||
try {
|
||||
const result = await hostApiFetch<{ success: boolean; error?: string }>('/api/provider-accounts/default', {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify({ accountId }),
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
throw new Error(result.error || 'Failed to set default provider account');
|
||||
}
|
||||
|
||||
await refreshProviderSnapshot();
|
||||
} catch (err) {
|
||||
console.error('Failed to set default account:', err);
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const validateAccountApiKey = async (
|
||||
providerId: string,
|
||||
apiKey: string,
|
||||
options?: { baseUrl?: string; apiProtocol?: ProviderAccount['apiProtocol'] }
|
||||
) => {
|
||||
try {
|
||||
const result = await hostApiFetch<{ valid: boolean; error?: string }>('/api/providers/validate', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ providerId, apiKey, options }),
|
||||
});
|
||||
return result;
|
||||
} catch (err) {
|
||||
return { valid: false, error: String(err) };
|
||||
}
|
||||
};
|
||||
|
||||
const validateApiKey = async (
|
||||
providerId: string,
|
||||
apiKey: string,
|
||||
options?: { baseUrl?: string; apiProtocol?: ProviderAccount['apiProtocol'] }
|
||||
) => {
|
||||
return validateAccountApiKey(providerId, apiKey, options);
|
||||
};
|
||||
|
||||
const getAccountApiKey = async (providerId: string) => {
|
||||
try {
|
||||
const result = await hostApiFetch<{ apiKey: string | null }>(`/api/providers/${encodeURIComponent(providerId)}/api-key`);
|
||||
return result.apiKey;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
const getApiKey = async (providerId: string) => {
|
||||
return getAccountApiKey(providerId);
|
||||
};
|
||||
|
||||
return {
|
||||
statuses,
|
||||
accounts,
|
||||
vendors,
|
||||
defaultAccountId,
|
||||
loading,
|
||||
error,
|
||||
init,
|
||||
refreshProviderSnapshot,
|
||||
fetchProviders,
|
||||
addProvider,
|
||||
createAccount,
|
||||
addAccount,
|
||||
updateProvider,
|
||||
updateAccount,
|
||||
deleteProvider,
|
||||
removeAccount,
|
||||
deleteAccount,
|
||||
setApiKey,
|
||||
updateProviderWithKey,
|
||||
deleteApiKey,
|
||||
setDefaultProvider,
|
||||
setDefaultAccount,
|
||||
validateAccountApiKey,
|
||||
validateApiKey,
|
||||
getAccountApiKey,
|
||||
getApiKey,
|
||||
};
|
||||
});
|
||||
176
src/stores/script.ts
Normal file
176
src/stores/script.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import { ref, computed } from 'vue';
|
||||
import { scriptApi } from '@lib/script-api';
|
||||
import type {
|
||||
AutomationScript,
|
||||
ScriptSaveInput,
|
||||
ScriptExecutionResult,
|
||||
ScriptRecordingStatus,
|
||||
} from '@lib/script-types';
|
||||
|
||||
export const useScriptStore = defineStore('script', () => {
|
||||
const scripts = ref<AutomationScript[]>([]);
|
||||
const loading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
const recordingStatus = ref<ScriptRecordingStatus>('idle');
|
||||
const executionLogs = ref<Record<string, ScriptExecutionResult>>({});
|
||||
|
||||
const safeScripts = computed(() => (Array.isArray(scripts.value) ? scripts.value : []));
|
||||
const enabledScripts = computed(() => safeScripts.value.filter((s) => s.enabled));
|
||||
const scriptsByChannel = computed(() => {
|
||||
const map = new Map<string, AutomationScript[]>();
|
||||
for (const script of safeScripts.value) {
|
||||
const list = map.get(script.channel) || [];
|
||||
list.push(script);
|
||||
map.set(script.channel, list);
|
||||
}
|
||||
return map;
|
||||
});
|
||||
|
||||
const fetchScripts = async () => {
|
||||
const currentScripts = safeScripts.value;
|
||||
if (currentScripts.length === 0) {
|
||||
loading.value = true;
|
||||
}
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
const result = await scriptApi.list();
|
||||
scripts.value = result;
|
||||
} catch (err) {
|
||||
error.value = err instanceof Error ? err.message : String(err);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
const saveScript = async (input: ScriptSaveInput) => {
|
||||
try {
|
||||
const result = await scriptApi.save(input);
|
||||
if (input.id) {
|
||||
scripts.value = safeScripts.value.map((s) => (s.id === input.id ? result : s));
|
||||
} else {
|
||||
scripts.value = [...safeScripts.value, result];
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
error.value = msg;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const deleteScript = async (id: string) => {
|
||||
try {
|
||||
await scriptApi.delete(id);
|
||||
scripts.value = safeScripts.value.filter((s) => s.id !== id);
|
||||
delete executionLogs.value[id];
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
error.value = msg;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const toggleScript = async (id: string, enabled: boolean) => {
|
||||
try {
|
||||
await scriptApi.toggle(id, enabled);
|
||||
scripts.value = safeScripts.value.map((s) =>
|
||||
s.id === id ? { ...s, enabled, updatedAt: new Date().toISOString() } : s,
|
||||
);
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
error.value = msg;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const runScript = async (id: string) => {
|
||||
try {
|
||||
const result = await scriptApi.run(id);
|
||||
executionLogs.value = { ...executionLogs.value, [id]: result };
|
||||
// 更新本地 lastRun
|
||||
scripts.value = safeScripts.value.map((s) =>
|
||||
s.id === id
|
||||
? {
|
||||
...s,
|
||||
lastRun: {
|
||||
time: new Date().toISOString(),
|
||||
success: result.success,
|
||||
error: result.error,
|
||||
},
|
||||
updatedAt: new Date().toISOString(),
|
||||
}
|
||||
: s,
|
||||
);
|
||||
return result;
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
error.value = msg;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const startRecording = async (url?: string) => {
|
||||
recordingStatus.value = 'recording';
|
||||
try {
|
||||
const result = await scriptApi.startRecording(url);
|
||||
if (!result.success) {
|
||||
recordingStatus.value = 'idle';
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
recordingStatus.value = 'idle';
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const stopRecording = async () => {
|
||||
try {
|
||||
const result = await scriptApi.stopRecording();
|
||||
recordingStatus.value = 'stopped';
|
||||
return result;
|
||||
} catch (err) {
|
||||
recordingStatus.value = 'idle';
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const resetRecording = () => {
|
||||
recordingStatus.value = 'idle';
|
||||
};
|
||||
|
||||
const codegen = async (id: string, url?: string) => {
|
||||
try {
|
||||
const result = await scriptApi.codegen(id, url);
|
||||
if (!result.success) {
|
||||
error.value = result.error || 'Codegen failed';
|
||||
}
|
||||
return result;
|
||||
} catch (err) {
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
error.value = msg;
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
return {
|
||||
scripts,
|
||||
loading,
|
||||
error,
|
||||
recordingStatus,
|
||||
executionLogs,
|
||||
safeScripts,
|
||||
enabledScripts,
|
||||
scriptsByChannel,
|
||||
fetchScripts,
|
||||
saveScript,
|
||||
deleteScript,
|
||||
toggleScript,
|
||||
runScript,
|
||||
startRecording,
|
||||
stopRecording,
|
||||
resetRecording,
|
||||
codegen,
|
||||
};
|
||||
});
|
||||
25
src/stores/sharedStore.ts
Normal file
25
src/stores/sharedStore.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { isEqual } from 'lodash-es'
|
||||
|
||||
export const useSharedStore = defineStore('shared', {
|
||||
state: () => ({
|
||||
sharedData: {},
|
||||
}),
|
||||
|
||||
actions: {
|
||||
updateSharedData(data: any) {
|
||||
this.sharedData = Object.assign(this.sharedData, data)
|
||||
|
||||
const cloneData = JSON.parse(JSON.stringify(this.sharedData))
|
||||
|
||||
if (!isEqual(this.sharedData, data)) {
|
||||
// 同步状态到主进程
|
||||
try {
|
||||
|
||||
} catch (error) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
123
src/stores/skills.ts
Normal file
123
src/stores/skills.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
import { defineStore } from 'pinia';
|
||||
import type { Skill, MarketplaceSkill } from '@src/lib/skills-types';
|
||||
import {
|
||||
apiFetchSkills,
|
||||
apiSearchSkills,
|
||||
apiInstallSkill,
|
||||
apiUninstallSkill,
|
||||
apiUpdateSkillConfig,
|
||||
} from '@src/lib/skills-api';
|
||||
|
||||
const INSTALL_ERROR_CODES = new Set(['installTimeoutError', 'installRateLimitError']);
|
||||
const FETCH_ERROR_CODES = new Set(['fetchTimeoutError', 'fetchRateLimitError', 'timeoutError', 'rateLimitError']);
|
||||
const SEARCH_ERROR_CODES = new Set(['searchTimeoutError', 'searchRateLimitError', 'timeoutError', 'rateLimitError']);
|
||||
|
||||
export const useSkillsStore = defineStore('skills', {
|
||||
state: () => ({
|
||||
skills: [] as Skill[],
|
||||
searchResults: [] as MarketplaceSkill[],
|
||||
loading: false,
|
||||
searching: false,
|
||||
searchError: null as string | null,
|
||||
installing: {} as Record<string, boolean>,
|
||||
error: null as string | null,
|
||||
}),
|
||||
|
||||
getters: {
|
||||
sourceStats: (state) => {
|
||||
const safeSkills = Array.isArray(state.skills) ? state.skills : [];
|
||||
return {
|
||||
all: safeSkills.length,
|
||||
builtIn: safeSkills.filter(s => s.isBundled).length,
|
||||
marketplace: safeSkills.filter(s => !s.isBundled).length,
|
||||
};
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
async fetchSkills() {
|
||||
if (this.skills.length === 0) {
|
||||
this.loading = true;
|
||||
}
|
||||
this.error = null;
|
||||
try {
|
||||
this.skills = await apiFetchSkills();
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch skills:', error);
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
this.error = FETCH_ERROR_CODES.has(msg) ? msg : msg;
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
},
|
||||
|
||||
async searchSkills(query: string) {
|
||||
this.searching = true;
|
||||
this.searchError = null;
|
||||
try {
|
||||
this.searchResults = await apiSearchSkills(query);
|
||||
} catch (error) {
|
||||
const msg = error instanceof Error ? error.message : String(error);
|
||||
this.searchError = SEARCH_ERROR_CODES.has(msg) ? msg : msg;
|
||||
} finally {
|
||||
this.searching = false;
|
||||
}
|
||||
},
|
||||
|
||||
async installSkill(slug: string, version?: string) {
|
||||
this.installing[slug] = true;
|
||||
try {
|
||||
await apiInstallSkill(slug, version);
|
||||
await this.fetchSkills();
|
||||
} finally {
|
||||
delete this.installing[slug];
|
||||
}
|
||||
},
|
||||
|
||||
async uninstallSkill(slug: string) {
|
||||
this.installing[slug] = true;
|
||||
try {
|
||||
await apiUninstallSkill(slug);
|
||||
await this.fetchSkills();
|
||||
} finally {
|
||||
delete this.installing[slug];
|
||||
}
|
||||
},
|
||||
|
||||
async enableSkill(skillId: string) {
|
||||
const skill = this.skills.find(s => s.id === skillId);
|
||||
if (!skill) return;
|
||||
try {
|
||||
await apiUpdateSkillConfig(skillId, { enabled: true });
|
||||
skill.enabled = true;
|
||||
} catch (error) {
|
||||
console.error('Failed to enable skill:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
async disableSkill(skillId: string) {
|
||||
const skill = this.skills.find(s => s.id === skillId);
|
||||
if (!skill) return;
|
||||
if (skill.isCore) {
|
||||
throw new Error('Cannot disable core skill');
|
||||
}
|
||||
try {
|
||||
await apiUpdateSkillConfig(skillId, { enabled: false });
|
||||
skill.enabled = false;
|
||||
} catch (error) {
|
||||
console.error('Failed to disable skill:', error);
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
|
||||
updateSkill(skillId: string, updates: Partial<Skill>) {
|
||||
const skill = this.skills.find(s => s.id === skillId);
|
||||
if (skill) {
|
||||
Object.assign(skill, updates);
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export { INSTALL_ERROR_CODES, FETCH_ERROR_CODES, SEARCH_ERROR_CODES };
|
||||
73
src/stores/userinfo.ts
Normal file
73
src/stores/userinfo.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { authOauth2TokenUsingPost } from "@src/api"
|
||||
import { Session } from '@utils/storage'
|
||||
import { encryption } from '@utils/other'
|
||||
|
||||
export const useUserStore = defineStore('userInfo', {
|
||||
state: () => ({
|
||||
token: Session.get('token'),
|
||||
}),
|
||||
|
||||
actions: {
|
||||
/**
|
||||
* 登录方法
|
||||
* @function login
|
||||
* @async
|
||||
* @param {Object} data - 登录数据
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async login(data: any) {
|
||||
data.grant_type = 'password';
|
||||
data.scope = 'server';
|
||||
|
||||
// const { VITE_OAUTH2_PASSWORD_CLIENT, VITE_PWD_ENC_KEY } = (import.meta as any).env
|
||||
// const basicAuth = 'Basic ' + window.btoa(VITE_OAUTH2_PASSWORD_CLIENT);
|
||||
|
||||
// Session.set('basicAuth', basicAuth);
|
||||
|
||||
// let encPassword = data.password;
|
||||
// 密码加密
|
||||
// if (VITE_PWD_ENC_KEY) {
|
||||
// encPassword = encryption(data.password, VITE_PWD_ENC_KEY);
|
||||
// }
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
authOauth2TokenUsingPost({
|
||||
body: { clientId: '', ...data },
|
||||
options: {
|
||||
headers: {
|
||||
isToken: true,
|
||||
// Authorization: basicAuth,
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
'Authorization': 'Basic Y3VzdG9tUEM6Y3VzdG9tUEM='
|
||||
}
|
||||
}
|
||||
})
|
||||
.then((res: any) => {
|
||||
// 存储token 信息
|
||||
Session.set('token', res.access_token);
|
||||
Session.set('refresh_token', res.refresh_token);
|
||||
resolve(res)
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
// 退出系统
|
||||
logOut() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// logout(this.token).then(() => {
|
||||
// this.token = ''
|
||||
// this.roles = []
|
||||
// this.permissions = []
|
||||
// removeToken()
|
||||
// resolve()
|
||||
// }).catch(error => {
|
||||
// reject(error)
|
||||
// })
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user