@h-ai/cli 0.1.0-alpha5
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 +202 -0
- package/README.md +167 -0
- package/dist/chunk-2H7UOFLK.js +10 -0
- package/dist/chunk-2H7UOFLK.js.map +1 -0
- package/dist/chunk-LANIDJC7.js +659 -0
- package/dist/chunk-LANIDJC7.js.map +1 -0
- package/dist/chunk-YV7WPUI6.js +206 -0
- package/dist/chunk-YV7WPUI6.js.map +1 -0
- package/dist/config-templates-S5RI2MC6.js +5 -0
- package/dist/config-templates-S5RI2MC6.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1645 -0
- package/dist/index.js.map +1 -0
- package/dist/skill-templates-N5DU5FNX.js +5 -0
- package/dist/skill-templates-N5DU5FNX.js.map +1 -0
- package/dist/src-XJ6YXNAF.js +15030 -0
- package/dist/src-XJ6YXNAF.js.map +1 -0
- package/package.json +59 -0
- package/templates/README.md +126 -0
- package/templates/apps/admin/messages/en-US.json +33 -0
- package/templates/apps/admin/messages/zh-CN.json +33 -0
- package/templates/apps/admin/src/routes/+layout.svelte.hbs +30 -0
- package/templates/apps/admin/src/routes/+page.svelte.hbs +35 -0
- package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +44 -0
- package/templates/apps/admin/src/routes/admin/+page.svelte.hbs +45 -0
- package/templates/apps/admin/src/routes/admin/settings/+page.svelte.hbs +37 -0
- package/templates/apps/admin/src/routes/api/health/+server.ts +12 -0
- package/templates/apps/android-app/.env.hbs +3 -0
- package/templates/apps/android-app/capacitor.config.ts.hbs +12 -0
- package/templates/apps/android-app/messages/en-US.json +24 -0
- package/templates/apps/android-app/messages/zh-CN.json +24 -0
- package/templates/apps/android-app/src/lib/api.ts.hbs +30 -0
- package/templates/apps/android-app/src/lib/capacitor.ts +21 -0
- package/templates/apps/android-app/src/routes/+layout.svelte.hbs +42 -0
- package/templates/apps/android-app/src/routes/+layout.ts +7 -0
- package/templates/apps/android-app/src/routes/+page.svelte.hbs +27 -0
- package/templates/apps/api/src/routes/+layout.svelte +14 -0
- package/templates/apps/api/src/routes/+page.svelte +11 -0
- package/templates/apps/api/src/routes/api/v1/health/+server.ts +14 -0
- package/templates/apps/api/src/routes/api/v1/items/+server.ts +37 -0
- package/templates/apps/api/src/routes/api/v1/items/[id]/+server.ts +30 -0
- package/templates/apps/h5/messages/en-US.json +37 -0
- package/templates/apps/h5/messages/zh-CN.json +37 -0
- package/templates/apps/h5/src/routes/+layout.svelte.hbs +57 -0
- package/templates/apps/h5/src/routes/+page.svelte.hbs +27 -0
- package/templates/apps/h5/src/routes/discover/+page.svelte.hbs +27 -0
- package/templates/apps/h5/src/routes/profile/+page.svelte.hbs +27 -0
- package/templates/apps/website/messages/en-US.json +32 -0
- package/templates/apps/website/messages/zh-CN.json +32 -0
- package/templates/apps/website/src/routes/+layout.svelte.hbs +66 -0
- package/templates/apps/website/src/routes/+page.svelte.hbs +33 -0
- package/templates/apps/website/src/routes/about/+page.svelte.hbs +27 -0
- package/templates/apps/website/src/routes/api/health/+server.ts +12 -0
- package/templates/apps/website/src/routes/contact/+page.svelte.hbs +59 -0
- package/templates/apps/website/src/routes/services/+page.svelte.hbs +63 -0
- package/templates/base/e2e/helpers.ts +49 -0
- package/templates/base/package.json.hbs +67 -0
- package/templates/base/playwright.config.ts +32 -0
- package/templates/base/src/app.css +2 -0
- package/templates/base/src/app.d.ts.hbs +18 -0
- package/templates/base/src/app.html.hbs +16 -0
- package/templates/base/src/hooks.server.ts.hbs +123 -0
- package/templates/base/src/lib/server/init.ts.hbs +190 -0
- package/templates/base/svelte.config.js.hbs +30 -0
- package/templates/base/tsconfig.json +8 -0
- package/templates/base/vite.config.ts.hbs +27 -0
- package/templates/features/ai/routes/api/chat/+server.ts +26 -0
- package/templates/features/datapipe/routes/api/datapipe/pipeline/+server.ts +41 -0
- package/templates/features/iam/routes-admin/(auth)/+layout.svelte.hbs +21 -0
- package/templates/features/iam/routes-admin/(auth)/auth/login/+page.svelte.hbs +98 -0
- package/templates/features/iam/routes-admin/(auth)/auth/register/+page.svelte.hbs +124 -0
- package/templates/features/iam/routes-h5/auth/login/+page.svelte.hbs +98 -0
- package/templates/features/iam/routes-h5/auth/register/+page.svelte.hbs +114 -0
- package/templates/features/iam/routes-shared/api/auth/login/+server.ts +32 -0
- package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +15 -0
- package/templates/features/iam/routes-shared/api/auth/me/+server.ts +12 -0
- package/templates/features/iam/routes-shared/api/auth/register/+server.ts +26 -0
- package/templates/features/payment/routes/api/v1/payment/create/+server.ts +30 -0
- package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +42 -0
- package/templates/features/payment/routes/api/v1/payment/query/[orderNo]/+server.ts +25 -0
- package/templates/features/storage/routes/api/upload/+server.ts +35 -0
- package/templates/features/vecdb/routes/api/vecdb/search/+server.ts +36 -0
- package/templates/i18n/project.inlang/settings.json +11 -0
- package/templates/skills/AGENTS.md +26 -0
- package/templates/skills/CLAUDE.md +12 -0
- package/templates/skills/copilot-instructions.md +61 -0
- package/templates/skills/hai-ai/SKILL.md +659 -0
- package/templates/skills/hai-api-client/SKILL.md +346 -0
- package/templates/skills/hai-app-create/SKILL.md +371 -0
- package/templates/skills/hai-app-review/SKILL.md +156 -0
- package/templates/skills/hai-app-tests/SKILL.md +468 -0
- package/templates/skills/hai-audit/SKILL.md +141 -0
- package/templates/skills/hai-build/SKILL.md +319 -0
- package/templates/skills/hai-cache/SKILL.md +184 -0
- package/templates/skills/hai-capacitor/SKILL.md +389 -0
- package/templates/skills/hai-core/SKILL.md +544 -0
- package/templates/skills/hai-crypto/SKILL.md +183 -0
- package/templates/skills/hai-datapipe/SKILL.md +214 -0
- package/templates/skills/hai-deploy/SKILL.md +210 -0
- package/templates/skills/hai-iam/SKILL.md +355 -0
- package/templates/skills/hai-kit/SKILL.md +319 -0
- package/templates/skills/hai-payment/SKILL.md +289 -0
- package/templates/skills/hai-reach/SKILL.md +325 -0
- package/templates/skills/hai-reldb/SKILL.md +459 -0
- package/templates/skills/hai-scheduler/SKILL.md +242 -0
- package/templates/skills/hai-storage/SKILL.md +230 -0
- package/templates/skills/hai-ui/SKILL.md +275 -0
- package/templates/skills/hai-vecdb/SKILL.md +273 -0
- package/templates/skills/opencode.json +11 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2E 测试 - 共用工具函数
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { APIRequestContext, Page } from '@playwright/test'
|
|
6
|
+
|
|
7
|
+
/** 生成唯一测试用户 */
|
|
8
|
+
export function uniqueUser(prefix = 'e2e') {
|
|
9
|
+
const safePrefix = (prefix.replace(/\W/g, '') || 'e2e').slice(0, 8)
|
|
10
|
+
const entropy = `${Date.now().toString(36)}${Math.random().toString(36).slice(2, 6)}`
|
|
11
|
+
const id = entropy.slice(-10)
|
|
12
|
+
const username = `${safePrefix}_${id}`.slice(0, 20)
|
|
13
|
+
return {
|
|
14
|
+
username,
|
|
15
|
+
email: `${safePrefix}_${id}@test.local`,
|
|
16
|
+
password: 'Test1234!@',
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 通过 API 注册用户 */
|
|
21
|
+
export async function registerViaApi(
|
|
22
|
+
request: APIRequestContext,
|
|
23
|
+
user: ReturnType<typeof uniqueUser>,
|
|
24
|
+
) {
|
|
25
|
+
return request.post('/api/auth/register', {
|
|
26
|
+
data: {
|
|
27
|
+
username: user.username,
|
|
28
|
+
email: user.email,
|
|
29
|
+
password: user.password,
|
|
30
|
+
confirmPassword: user.password,
|
|
31
|
+
},
|
|
32
|
+
})
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** 注册并登录,返回已认证的 Page */
|
|
36
|
+
export async function registerAndLogin(
|
|
37
|
+
page: Page,
|
|
38
|
+
request: APIRequestContext,
|
|
39
|
+
prefix = 'e2e',
|
|
40
|
+
) {
|
|
41
|
+
const user = uniqueUser(prefix)
|
|
42
|
+
await registerViaApi(request, user)
|
|
43
|
+
await page.goto('/login')
|
|
44
|
+
await page.fill('[name="username"]', user.username)
|
|
45
|
+
await page.fill('[name="password"]', user.password)
|
|
46
|
+
await page.click('button[type="submit"]')
|
|
47
|
+
await page.waitForURL('/')
|
|
48
|
+
return user
|
|
49
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{projectName}}",
|
|
3
|
+
"version": "0.1.0-alpha5",
|
|
4
|
+
"private": true,
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "vite dev",
|
|
9
|
+
"build": "vite build",
|
|
10
|
+
"preview": "vite preview",
|
|
11
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
|
|
12
|
+
"lint": "eslint .",
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test:e2e": "playwright test"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@h-ai/core": "^0.1.0-alpha5",
|
|
18
|
+
"@h-ai/kit": "^0.1.0-alpha5"{{#if hasUi}},
|
|
19
|
+
"@h-ai/ui": "^0.1.0-alpha5"{{/if}}{{#if features.db}},
|
|
20
|
+
"@h-ai/reldb": "^0.1.0-alpha5"{{/if}}{{#if features.cache}},
|
|
21
|
+
"@h-ai/cache": "^0.1.0-alpha5"{{/if}}{{#if features.iam}},
|
|
22
|
+
"@h-ai/iam": "^0.1.0-alpha5"{{/if}}{{#if features.crypto}},
|
|
23
|
+
"@h-ai/crypto": "^0.1.0-alpha5"{{/if}}{{#if features.storage}},
|
|
24
|
+
"@h-ai/storage": "^0.1.0-alpha5"{{/if}}{{#if features.ai}},
|
|
25
|
+
"@h-ai/ai": "^0.1.0-alpha5"{{/if}}{{#if features.audit}},
|
|
26
|
+
"@h-ai/audit": "^0.1.0-alpha5"{{/if}}{{#if features.reach}},
|
|
27
|
+
"@h-ai/reach": "^0.1.0-alpha5"{{/if}}{{#if features.payment}},
|
|
28
|
+
"@h-ai/payment": "^0.1.0-alpha5"{{/if}}{{#if features.vecdb}},
|
|
29
|
+
"@h-ai/vecdb": "^0.1.0-alpha5"{{/if}}{{#if features.datapipe}},
|
|
30
|
+
"@h-ai/datapipe": "^0.1.0-alpha5"{{/if}}{{#if features.scheduler}},
|
|
31
|
+
"@h-ai/scheduler": "^0.1.0-alpha5"{{/if}}{{#if features.deploy}},
|
|
32
|
+
"@h-ai/deploy": "^0.1.0-alpha5"{{/if}}{{#if features.api-client}},
|
|
33
|
+
"@h-ai/api-client": "^0.1.0-alpha5"{{/if}}{{#if features.capacitor}},
|
|
34
|
+
"@h-ai/capacitor": "^0.1.0-alpha5"{{/if}}{{#if isCapacitorApp}},
|
|
35
|
+
"@h-ai/api-client": "^0.1.0-alpha5",
|
|
36
|
+
"@h-ai/capacitor": "^0.1.0-alpha5"{{/if}},
|
|
37
|
+
"zod": "^3.23.0"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@playwright/test": "^1.50.0",
|
|
41
|
+
{{#if isCapacitorApp}}
|
|
42
|
+
"@capacitor/cli": "^7.0.0",
|
|
43
|
+
"@capacitor/core": "^7.0.0",
|
|
44
|
+
"@capacitor/android": "^7.0.0",
|
|
45
|
+
"@capacitor/preferences": "^7.0.0",
|
|
46
|
+
"@sveltejs/adapter-static": "^3.0.0",
|
|
47
|
+
{{else}}
|
|
48
|
+
"@sveltejs/adapter-auto": "^3.0.0",
|
|
49
|
+
{{/if}}
|
|
50
|
+
"@sveltejs/kit": "^2.0.0",
|
|
51
|
+
"@sveltejs/vite-plugin-svelte": "^4.0.0",
|
|
52
|
+
"@types/node": "^22.0.0",
|
|
53
|
+
"rimraf": "^6.0.0",
|
|
54
|
+
"svelte": "^5.0.0",
|
|
55
|
+
"svelte-check": "^4.0.0",
|
|
56
|
+
"typescript": "^5.7.0",
|
|
57
|
+
"vite": "^6.0.0",
|
|
58
|
+
"vitest": "^2.0.0"{{#if hasUi}},
|
|
59
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
60
|
+
"tailwindcss": "^4.0.0",
|
|
61
|
+
"daisyui": "^5.0.0",
|
|
62
|
+
"@iconify-json/tabler": "^1.2.0",
|
|
63
|
+
"@iconify/tailwind4": "^1.0.0"{{/if}}{{#if hasI18n}},
|
|
64
|
+
"@inlang/paraglide-js": "^2.0.0",
|
|
65
|
+
"@inlang/plugin-message-format": "^5.0.0"{{/if}}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import process from 'node:process'
|
|
2
|
+
import { defineConfig } from '@playwright/test'
|
|
3
|
+
|
|
4
|
+
const baseURL = process.env.BASE_URL || 'http://localhost:4173'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Playwright E2E 测试配置
|
|
8
|
+
*/
|
|
9
|
+
export default defineConfig({
|
|
10
|
+
testDir: './e2e',
|
|
11
|
+
fullyParallel: false,
|
|
12
|
+
forbidOnly: !!process.env.CI,
|
|
13
|
+
retries: process.env.CI ? 2 : 0,
|
|
14
|
+
workers: 2,
|
|
15
|
+
reporter: 'list',
|
|
16
|
+
timeout: 30_000,
|
|
17
|
+
|
|
18
|
+
use: {
|
|
19
|
+
baseURL,
|
|
20
|
+
channel: 'chrome',
|
|
21
|
+
trace: 'on-first-retry',
|
|
22
|
+
screenshot: 'only-on-failure',
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
webServer: {
|
|
26
|
+
command: 'pnpm build && pnpm preview --port 4173 --strictPort',
|
|
27
|
+
env: { HAI_E2E: '1' },
|
|
28
|
+
url: baseURL,
|
|
29
|
+
reuseExistingServer: false,
|
|
30
|
+
timeout: 180_000,
|
|
31
|
+
},
|
|
32
|
+
})
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/// <reference types="@sveltejs/kit" />
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
namespace App {
|
|
5
|
+
interface Locals {
|
|
6
|
+
/** 请求 ID */
|
|
7
|
+
requestId: string
|
|
8
|
+
/** 语言 */
|
|
9
|
+
locale: string
|
|
10
|
+
{{#if features.iam}}
|
|
11
|
+
/** 会话信息 */
|
|
12
|
+
session?: import('@h-ai/kit').SessionData
|
|
13
|
+
{{/if}}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export {}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
{{#if hasI18n}}
|
|
3
|
+
<html lang="%lang%">
|
|
4
|
+
{{else}}
|
|
5
|
+
<html lang="{{defaultLocale}}">
|
|
6
|
+
{{/if}}
|
|
7
|
+
<head>
|
|
8
|
+
<meta charset="utf-8" />
|
|
9
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
10
|
+
<link rel="icon" href="%sveltekit.assets%/favicon.png" />
|
|
11
|
+
%sveltekit.head%
|
|
12
|
+
</head>
|
|
13
|
+
<body data-sveltekit-preload-data="hover">
|
|
14
|
+
<div style="display: contents">%sveltekit.body%</div>
|
|
15
|
+
</body>
|
|
16
|
+
</html>
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* {{projectName}} - Server Hooks
|
|
4
|
+
* =============================================================================
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import type { Handle } from '@sveltejs/kit'
|
|
8
|
+
import { initApp } from '$lib/server/init.js'
|
|
9
|
+
import { core } from '@h-ai/core'
|
|
10
|
+
import { kit } from '@h-ai/kit'
|
|
11
|
+
{{#if features.iam}}
|
|
12
|
+
import { iam } from '@h-ai/iam'
|
|
13
|
+
{{/if}}
|
|
14
|
+
{{#if features.crypto}}
|
|
15
|
+
import { crypto } from '@h-ai/crypto'
|
|
16
|
+
{{/if}}
|
|
17
|
+
{{#if hasI18n}}
|
|
18
|
+
import { paraglideMiddleware } from '$lib/paraglide/server.js'
|
|
19
|
+
{{/if}}
|
|
20
|
+
|
|
21
|
+
{{#if features.crypto}}
|
|
22
|
+
// 初始化加密模块(同步)
|
|
23
|
+
crypto.init()
|
|
24
|
+
|
|
25
|
+
{{/if}}
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// 应用初始化
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
let appInitPromise: Promise<void> | null = null
|
|
31
|
+
|
|
32
|
+
async function ensureAppInitialized() {
|
|
33
|
+
if (!appInitPromise) {
|
|
34
|
+
appInitPromise = initApp().catch((err: unknown) => {
|
|
35
|
+
appInitPromise = null
|
|
36
|
+
throw err
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
await appInitPromise
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// =============================================================================
|
|
43
|
+
// 初始化 Handle
|
|
44
|
+
// =============================================================================
|
|
45
|
+
|
|
46
|
+
const initHandle: Handle = async ({ event, resolve }) => {
|
|
47
|
+
await ensureAppInitialized()
|
|
48
|
+
return resolve(event)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
{{#if hasI18n}}
|
|
52
|
+
// =============================================================================
|
|
53
|
+
// Paraglide i18n Middleware
|
|
54
|
+
// =============================================================================
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* i18n Handle - 使用 Paraglide middleware 处理语言
|
|
58
|
+
*
|
|
59
|
+
* API 请求跳过 paraglideMiddleware(不需要页面级 locale + 避免消耗 body)
|
|
60
|
+
*/
|
|
61
|
+
const i18nHandle: Handle = async ({ event, resolve }) => {
|
|
62
|
+
if (event.url.pathname.startsWith('/api/')) {
|
|
63
|
+
const locale = event.cookies.get('PARAGLIDE_LOCALE') ?? '{{defaultLocale}}'
|
|
64
|
+
event.locals.locale = locale
|
|
65
|
+
kit.i18n.setLocale(locale)
|
|
66
|
+
return resolve(event)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return paraglideMiddleware(event.request, async ({ locale }) => {
|
|
70
|
+
event.locals.locale = locale
|
|
71
|
+
kit.i18n.setLocale(locale)
|
|
72
|
+
return resolve(event, {
|
|
73
|
+
transformPageChunk: ({ html }) => html.replace('%lang%', locale),
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
{{/if}}
|
|
79
|
+
// =============================================================================
|
|
80
|
+
// hai Handle(会话验证 + 限流 + 日志)
|
|
81
|
+
// =============================================================================
|
|
82
|
+
|
|
83
|
+
const haiHandle = kit.createHandle({
|
|
84
|
+
{{#if features.iam}}
|
|
85
|
+
auth: {
|
|
86
|
+
verifyToken: async (token) => {
|
|
87
|
+
const result = await iam.auth.verifyToken(token)
|
|
88
|
+
return result.success ? result.data : null
|
|
89
|
+
},
|
|
90
|
+
operations: iam.auth,
|
|
91
|
+
loginUrl: '/auth/login',
|
|
92
|
+
protectedPaths: ['/admin/*', '/api/*'],
|
|
93
|
+
publicPaths: ['/api/auth/*', '/api/public/*', '/api/health'],
|
|
94
|
+
},
|
|
95
|
+
{{/if}}
|
|
96
|
+
rateLimit: { windowMs: 60000, maxRequests: 200 },
|
|
97
|
+
logging: { logBody: false },
|
|
98
|
+
onError: (error: unknown, _event) => {
|
|
99
|
+
core.logger.error('Request error:', { error })
|
|
100
|
+
return new Response(
|
|
101
|
+
JSON.stringify({
|
|
102
|
+
success: false,
|
|
103
|
+
error: {
|
|
104
|
+
code: 'INTERNAL_ERROR',
|
|
105
|
+
message: error instanceof Error ? error.message : 'Unknown error',
|
|
106
|
+
},
|
|
107
|
+
}),
|
|
108
|
+
{ status: 500, headers: { 'Content-Type': 'application/json' } },
|
|
109
|
+
)
|
|
110
|
+
},
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
// =============================================================================
|
|
114
|
+
// 组合 Handle
|
|
115
|
+
// =============================================================================
|
|
116
|
+
|
|
117
|
+
export const handle = kit.sequence(
|
|
118
|
+
initHandle,
|
|
119
|
+
{{#if hasI18n}}
|
|
120
|
+
i18nHandle,
|
|
121
|
+
{{/if}}
|
|
122
|
+
haiHandle,
|
|
123
|
+
)
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* =============================================================================
|
|
3
|
+
* {{projectName}} - 应用初始化
|
|
4
|
+
* =============================================================================
|
|
5
|
+
*
|
|
6
|
+
* 统一管理所有模块的初始化流程。
|
|
7
|
+
*
|
|
8
|
+
* 初始化顺序:
|
|
9
|
+
* 1. core.init — 加载配置文件(约定优于配置)
|
|
10
|
+
{{#if features.db}}
|
|
11
|
+
* 2. reldb.init — 数据库连接
|
|
12
|
+
{{/if}}
|
|
13
|
+
{{#if features.cache}}
|
|
14
|
+
* 3. cache.init — 缓存
|
|
15
|
+
{{/if}}
|
|
16
|
+
{{#if features.storage}}
|
|
17
|
+
* 4. storage.init — 文件存储(可选)
|
|
18
|
+
{{/if}}
|
|
19
|
+
{{#if features.reach}}
|
|
20
|
+
* 5. reach.init — 触达服务(可选)
|
|
21
|
+
{{/if}}
|
|
22
|
+
{{#if features.iam}}
|
|
23
|
+
* 6. iam.init — 身份认证
|
|
24
|
+
{{/if}}
|
|
25
|
+
{{#if features.ai}}
|
|
26
|
+
* 7. ai.init — AI 集成(可选)
|
|
27
|
+
{{/if}}
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { core } from '@h-ai/core'
|
|
31
|
+
{{#if features.db}}
|
|
32
|
+
import { reldb, ReldbConfigSchema } from '@h-ai/reldb'
|
|
33
|
+
{{/if}}
|
|
34
|
+
{{#if features.cache}}
|
|
35
|
+
import { cache, CacheConfigSchema } from '@h-ai/cache'
|
|
36
|
+
{{/if}}
|
|
37
|
+
{{#if features.iam}}
|
|
38
|
+
import type { IamConfigSettingsInput } from '@h-ai/iam'
|
|
39
|
+
import { iam } from '@h-ai/iam'
|
|
40
|
+
{{/if}}
|
|
41
|
+
{{#if features.storage}}
|
|
42
|
+
import type { StorageConfigInput } from '@h-ai/storage'
|
|
43
|
+
import { storage, StorageConfigSchema } from '@h-ai/storage'
|
|
44
|
+
{{/if}}
|
|
45
|
+
{{#if features.ai}}
|
|
46
|
+
import type { AIConfigInput } from '@h-ai/ai'
|
|
47
|
+
import { ai } from '@h-ai/ai'
|
|
48
|
+
{{/if}}
|
|
49
|
+
{{#if features.reach}}
|
|
50
|
+
import type { ReachConfigInput } from '@h-ai/reach'
|
|
51
|
+
import { reach } from '@h-ai/reach'
|
|
52
|
+
{{/if}}
|
|
53
|
+
{{#if features.audit}}
|
|
54
|
+
import { audit } from '@h-ai/audit'
|
|
55
|
+
{{/if}}
|
|
56
|
+
|
|
57
|
+
{{#if features.db}}
|
|
58
|
+
type DbConfigInput = Parameters<typeof reldb.init>[0]
|
|
59
|
+
{{/if}}
|
|
60
|
+
{{#if features.cache}}
|
|
61
|
+
type CacheConfigInput = Parameters<typeof cache.init>[0]
|
|
62
|
+
{{/if}}
|
|
63
|
+
|
|
64
|
+
let initialized = false
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 初始化应用
|
|
68
|
+
*/
|
|
69
|
+
export async function initApp(): Promise<void> {
|
|
70
|
+
if (initialized)
|
|
71
|
+
return
|
|
72
|
+
|
|
73
|
+
// 1. 加载配置
|
|
74
|
+
core.init({
|
|
75
|
+
configDir: './config',
|
|
76
|
+
logging: { level: 'info' },
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
{{#if features.db}}
|
|
80
|
+
// ─── 配置校验 ───
|
|
81
|
+
const dbValidation = core.config.validate('db', ReldbConfigSchema)
|
|
82
|
+
if (!dbValidation.success) {
|
|
83
|
+
throw new Error(`DB config invalid: ${dbValidation.error.message}`)
|
|
84
|
+
}
|
|
85
|
+
{{/if}}
|
|
86
|
+
{{#if features.cache}}
|
|
87
|
+
const cacheValidation = core.config.validate('cache', CacheConfigSchema)
|
|
88
|
+
if (!cacheValidation.success) {
|
|
89
|
+
throw new Error(`Cache config invalid: ${cacheValidation.error.message}`)
|
|
90
|
+
}
|
|
91
|
+
{{/if}}
|
|
92
|
+
|
|
93
|
+
{{#if features.db}}
|
|
94
|
+
// ─── 获取配置 ───
|
|
95
|
+
const dbConfig = core.config.getOrThrow<DbConfigInput>('db')
|
|
96
|
+
{{/if}}
|
|
97
|
+
{{#if features.cache}}
|
|
98
|
+
const cacheConfig = core.config.getOrThrow<CacheConfigInput>('cache')
|
|
99
|
+
{{/if}}
|
|
100
|
+
{{#if features.iam}}
|
|
101
|
+
const iamConfig = core.config.getOrThrow<IamConfigSettingsInput>('iam')
|
|
102
|
+
{{/if}}
|
|
103
|
+
{{#if features.storage}}
|
|
104
|
+
const storageConfig = core.config.get<StorageConfigInput>('storage')
|
|
105
|
+
{{/if}}
|
|
106
|
+
{{#if features.ai}}
|
|
107
|
+
const aiConfig = core.config.get<AIConfigInput>('ai')
|
|
108
|
+
{{/if}}
|
|
109
|
+
{{#if features.reach}}
|
|
110
|
+
const reachConfig = core.config.get<ReachConfigInput>('reach')
|
|
111
|
+
{{/if}}
|
|
112
|
+
|
|
113
|
+
{{#if features.db}}
|
|
114
|
+
// ─── 确保数据目录存在(SQLite) ───
|
|
115
|
+
if (dbConfig.type === 'sqlite') {
|
|
116
|
+
const path = await import('node:path')
|
|
117
|
+
const fs = await import('node:fs')
|
|
118
|
+
const dbDir = path.dirname(dbConfig.database)
|
|
119
|
+
if (!fs.existsSync(dbDir)) {
|
|
120
|
+
fs.mkdirSync(dbDir, { recursive: true })
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ─── 初始化数据库 ───
|
|
125
|
+
const dbResult = await reldb.init(dbConfig)
|
|
126
|
+
if (!dbResult.success) {
|
|
127
|
+
throw new Error(`Database initialization failed: ${dbResult.error.message}`)
|
|
128
|
+
}
|
|
129
|
+
{{/if}}
|
|
130
|
+
|
|
131
|
+
{{#if features.cache}}
|
|
132
|
+
// ─── 初始化缓存 ───
|
|
133
|
+
const cacheResult = await cache.init(cacheConfig)
|
|
134
|
+
if (!cacheResult.success) {
|
|
135
|
+
throw new Error(`Cache initialization failed: ${cacheResult.error.message}`)
|
|
136
|
+
}
|
|
137
|
+
{{/if}}
|
|
138
|
+
|
|
139
|
+
{{#if features.storage}}
|
|
140
|
+
// ─── 初始化存储(可选) ───
|
|
141
|
+
if (storageConfig) {
|
|
142
|
+
const storageValidation = core.config.validate('storage', StorageConfigSchema)
|
|
143
|
+
if (storageValidation.success) {
|
|
144
|
+
const storageResult = await storage.init(storageConfig)
|
|
145
|
+
if (!storageResult.success) {
|
|
146
|
+
core.logger.warn('Storage initialization failed', { error: storageResult.error.message })
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
{{/if}}
|
|
151
|
+
|
|
152
|
+
{{#if features.reach}}
|
|
153
|
+
// ─── 初始化触达服务(可选) ───
|
|
154
|
+
if (reachConfig) {
|
|
155
|
+
const reachResult = await reach.init(reachConfig)
|
|
156
|
+
if (!reachResult.success) {
|
|
157
|
+
core.logger.warn('Reach initialization failed', { error: reachResult.error.message })
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
{{/if}}
|
|
161
|
+
|
|
162
|
+
{{#if features.iam}}
|
|
163
|
+
// ─── 初始化 IAM ───
|
|
164
|
+
const iamResult = await iam.init(iamConfig)
|
|
165
|
+
if (!iamResult.success) {
|
|
166
|
+
throw new Error(`IAM initialization failed: ${iamResult.error.message}`)
|
|
167
|
+
}
|
|
168
|
+
{{/if}}
|
|
169
|
+
|
|
170
|
+
{{#if features.audit}}
|
|
171
|
+
// ─── 初始化审计 ───
|
|
172
|
+
const auditResult = await audit.init()
|
|
173
|
+
if (!auditResult.success) {
|
|
174
|
+
core.logger.warn('Audit initialization failed', { error: auditResult.error.message })
|
|
175
|
+
}
|
|
176
|
+
{{/if}}
|
|
177
|
+
|
|
178
|
+
{{#if features.ai}}
|
|
179
|
+
// ─── 初始化 AI(可选) ───
|
|
180
|
+
if (aiConfig?.llm?.apiKey) {
|
|
181
|
+
const aiResult = await ai.init(aiConfig)
|
|
182
|
+
if (!aiResult.success) {
|
|
183
|
+
core.logger.warn('AI initialization failed', { error: aiResult.error.message })
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
{{/if}}
|
|
187
|
+
|
|
188
|
+
initialized = true
|
|
189
|
+
core.logger.info('{{projectName}} initialized.')
|
|
190
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Svelte 配置
|
|
3
|
+
*/
|
|
4
|
+
{{#if isCapacitorApp}}
|
|
5
|
+
import adapter from '@sveltejs/adapter-static'
|
|
6
|
+
{{else}}
|
|
7
|
+
import adapter from '@sveltejs/adapter-auto'
|
|
8
|
+
{{/if}}
|
|
9
|
+
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'
|
|
10
|
+
|
|
11
|
+
/** @type {import('@sveltejs/kit').Config} */
|
|
12
|
+
const config = {
|
|
13
|
+
preprocess: vitePreprocess(),
|
|
14
|
+
compilerOptions: {
|
|
15
|
+
runes: true,
|
|
16
|
+
},
|
|
17
|
+
kit: {
|
|
18
|
+
{{#if isCapacitorApp}}
|
|
19
|
+
adapter: adapter({
|
|
20
|
+
pages: 'build',
|
|
21
|
+
assets: 'build',
|
|
22
|
+
fallback: 'index.html',
|
|
23
|
+
}),
|
|
24
|
+
{{else}}
|
|
25
|
+
adapter: adapter(),
|
|
26
|
+
{{/if}}
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default config
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* {{projectName}} - Vite 配置
|
|
3
|
+
*/
|
|
4
|
+
import { sveltekit } from '@sveltejs/kit/vite'
|
|
5
|
+
{{#if hasUi}}
|
|
6
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
7
|
+
{{/if}}
|
|
8
|
+
{{#if hasI18n}}
|
|
9
|
+
import { paraglideVitePlugin } from '@inlang/paraglide-js'
|
|
10
|
+
{{/if}}
|
|
11
|
+
import { defineConfig } from 'vite'
|
|
12
|
+
|
|
13
|
+
export default defineConfig({
|
|
14
|
+
plugins: [
|
|
15
|
+
sveltekit(),
|
|
16
|
+
{{#if hasUi}}
|
|
17
|
+
tailwindcss(),
|
|
18
|
+
{{/if}}
|
|
19
|
+
{{#if hasI18n}}
|
|
20
|
+
paraglideVitePlugin({
|
|
21
|
+
project: './project.inlang',
|
|
22
|
+
outdir: './src/lib/paraglide',
|
|
23
|
+
strategy: ['cookie', 'baseLocale'],
|
|
24
|
+
}),
|
|
25
|
+
{{/if}}
|
|
26
|
+
],
|
|
27
|
+
})
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Chat API — ai 功能示例
|
|
3
|
+
*/
|
|
4
|
+
import type { RequestHandler } from './$types'
|
|
5
|
+
import { ai } from '@h-ai/ai'
|
|
6
|
+
import { kit } from '@h-ai/kit'
|
|
7
|
+
|
|
8
|
+
export const POST: RequestHandler = async ({ request, locals }) => {
|
|
9
|
+
const { message } = await request.json()
|
|
10
|
+
|
|
11
|
+
if (!message) {
|
|
12
|
+
return kit.response.badRequest('message is required')
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const result = await ai.llm.ask(message, {
|
|
16
|
+
systemPrompt: 'You are a helpful assistant.',
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
if (!result.success) {
|
|
20
|
+
return kit.response.internalError(result.error.message)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return kit.response.ok({
|
|
24
|
+
reply: result.data,
|
|
25
|
+
}, locals.requestId)
|
|
26
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataPipe Pipeline API — datapipe 功能示例
|
|
3
|
+
*/
|
|
4
|
+
import type { RequestEvent } from '@sveltejs/kit'
|
|
5
|
+
import { datapipe } from '@h-ai/datapipe'
|
|
6
|
+
import { kit } from '@h-ai/kit'
|
|
7
|
+
|
|
8
|
+
export async function POST({ request, locals }: RequestEvent) {
|
|
9
|
+
const body = await request.json().catch(() => null) as { content?: string } | null
|
|
10
|
+
const content = body?.content?.trim()
|
|
11
|
+
|
|
12
|
+
if (!content) {
|
|
13
|
+
return kit.response.badRequest('content is required')
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const cleanedResult = datapipe.clean(content, {
|
|
17
|
+
removeHtml: true,
|
|
18
|
+
removeUrls: false,
|
|
19
|
+
normalizeWhitespace: true,
|
|
20
|
+
})
|
|
21
|
+
if (!cleanedResult.success) {
|
|
22
|
+
return kit.response.internalError(cleanedResult.error.message)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const chunksResult = datapipe.chunk(cleanedResult.data, {
|
|
26
|
+
mode: 'paragraph',
|
|
27
|
+
maxSize: 500,
|
|
28
|
+
overlap: 50,
|
|
29
|
+
})
|
|
30
|
+
if (!chunksResult.success) {
|
|
31
|
+
return kit.response.internalError(chunksResult.error.message)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const chunks = chunksResult.data
|
|
35
|
+
|
|
36
|
+
return kit.response.ok({
|
|
37
|
+
cleaned: cleanedResult.data,
|
|
38
|
+
chunkCount: chunks.length,
|
|
39
|
+
chunks: chunks.slice(0, 5),
|
|
40
|
+
}, locals.requestId)
|
|
41
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{{!-- 认证布局(登录/注册共用) --}}
|
|
2
|
+
<script lang="ts">
|
|
3
|
+
/**
|
|
4
|
+
* 认证布局(登录/注册共用)
|
|
5
|
+
*/
|
|
6
|
+
import type { Snippet } from 'svelte'
|
|
7
|
+
|
|
8
|
+
interface Props {
|
|
9
|
+
children: Snippet
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let { children }: Props = $props()
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<div class="min-h-screen flex items-center justify-center bg-base-200">
|
|
16
|
+
<div class="card w-full max-w-md bg-base-100 shadow-xl">
|
|
17
|
+
<div class="card-body">
|
|
18
|
+
{@render children()}
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|