@minus-ai/create-skill 0.1.0-beta.2 → 0.1.0-beta.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/index.mjs +73 -17
- package/package.json +2 -2
- package/templates/README.md.tpl +7 -5
- package/templates/asin/main.tsx.tpl +5 -6
- package/templates/asin/pipeline.py.tpl +1 -0
- package/templates/conftest.py.tpl +5 -0
- package/templates/custom/main.tsx.tpl +6 -7
- package/templates/custom/pipeline.py.tpl +1 -0
- package/templates/default/main.tsx.tpl +3 -4
- package/templates/default/pipeline.py.tpl +2 -2
- package/templates/file/main.tsx.tpl +7 -8
- package/templates/file/pipeline.py.tpl +1 -0
- package/templates/frontend-package.json.tpl +5 -3
- package/templates/keyword/main.tsx.tpl +5 -6
- package/templates/keyword/pipeline.py.tpl +1 -0
- package/templates/main.tsx.tpl +8 -9
- package/templates/pipeline-reference.py.tpl +16 -0
- package/templates/pipeline.py.tpl +1 -0
- package/templates/pnpm-workspace.yaml.tpl +13 -0
- package/templates/pyproject.toml.tpl +11 -0
- package/templates/root-package.json.tpl +12 -8
- package/templates/test_pipeline.py.tpl +35 -0
- package/templates/tsconfig.json.tpl +6 -2
- package/templates/vite.config.ts.tpl +1 -1
package/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { createInterface } from 'node:readline/promises'
|
|
4
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs'
|
|
4
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'node:fs'
|
|
5
5
|
import { execSync } from 'node:child_process'
|
|
6
6
|
import { createServer } from 'node:net'
|
|
7
7
|
import { join, dirname } from 'node:path'
|
|
@@ -15,6 +15,46 @@ const CREDENTIALS_FILE = join(CREDENTIALS_DIR, 'credentials.json')
|
|
|
15
15
|
|
|
16
16
|
const DEFAULT_PLATFORM_URL = 'http://47.107.144.22:18990'
|
|
17
17
|
|
|
18
|
+
// create-skill 内 node 大版本的唯一写死处。模板里的 engines.node / volta.node /
|
|
19
|
+
// @types/node 全部由它派生,不再各自硬编码。与 minus-plugin lib/toolchain.sh 的
|
|
20
|
+
// NODE_TARGET 同义——两仓各自持有同一 major(create-skill 是独立 npm 包,发布时读不到
|
|
21
|
+
// toolchain.sh,故不做运行时跨仓耦合)。升级大版本只改这一个值。
|
|
22
|
+
const NODE_MAJOR_FLOOR = 24
|
|
23
|
+
|
|
24
|
+
function nodeMajorOf(v) {
|
|
25
|
+
return parseInt(String(v).split('.')[0], 10) || 0
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 解析「生成项目实际会跑的那个 node」的确切 patch。Volta 不认 major-only/range,
|
|
29
|
+
// volta.node 必须是确切 patch,所以这里返回一个真实存在的 x.y.z。三档:
|
|
30
|
+
// 1. Volta 已装 ≥floor 的最新 node image —— 项目 dev(经 ~/.volta/bin/pnpm)真正会用
|
|
31
|
+
// 且已安装 → 零下载。
|
|
32
|
+
// 2. 没有合格 Volta image,但 create-skill 自身在跑的 node ≥floor —— pin 它(真实存在,
|
|
33
|
+
// 进项目时 Volta 可能下载,但非凭空写死)。
|
|
34
|
+
// 3. 哪里都找不到 ≥floor 的 node → 不编造版本,报错退出。
|
|
35
|
+
function resolveProjectNodeVersion() {
|
|
36
|
+
const imageDir = join(homedir(), '.volta', 'tools', 'image', 'node')
|
|
37
|
+
if (existsSync(imageDir)) {
|
|
38
|
+
const best = readdirSync(imageDir)
|
|
39
|
+
.filter(name => /^\d+\.\d+\.\d+$/.test(name) && nodeMajorOf(name) >= NODE_MAJOR_FLOOR)
|
|
40
|
+
.sort((a, b) => {
|
|
41
|
+
const pa = a.split('.').map(Number)
|
|
42
|
+
const pb = b.split('.').map(Number)
|
|
43
|
+
return pb[0] - pa[0] || pb[1] - pa[1] || pb[2] - pa[2]
|
|
44
|
+
})[0]
|
|
45
|
+
if (best) return best
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const running = process.versions.node
|
|
49
|
+
if (nodeMajorOf(running) >= NODE_MAJOR_FLOOR) return running
|
|
50
|
+
|
|
51
|
+
console.error(
|
|
52
|
+
`\n ✗ 未找到 Node ${NODE_MAJOR_FLOOR}+。请安装 Node ${NODE_MAJOR_FLOOR}(推荐 https://volta.sh:\n` +
|
|
53
|
+
` curl https://get.volta.sh | bash 后 volta install node@${NODE_MAJOR_FLOOR})后重试。`
|
|
54
|
+
)
|
|
55
|
+
process.exit(1)
|
|
56
|
+
}
|
|
57
|
+
|
|
18
58
|
function findAvailablePort(start = 4001) {
|
|
19
59
|
return new Promise(resolve => {
|
|
20
60
|
const server = createServer()
|
|
@@ -28,7 +68,7 @@ function findAvailablePort(start = 4001) {
|
|
|
28
68
|
// ── Naming helpers ──────────────────────────────────────
|
|
29
69
|
|
|
30
70
|
function idToFolder(id) {
|
|
31
|
-
return id.replace(/[
|
|
71
|
+
return id.trim().replace(/[\s/\\]+/g, '-')
|
|
32
72
|
}
|
|
33
73
|
|
|
34
74
|
function idToClassName(id) {
|
|
@@ -476,10 +516,15 @@ async function main() {
|
|
|
476
516
|
const port = String(await findAvailablePort(4001))
|
|
477
517
|
const apiKey = skillResult.apiKey
|
|
478
518
|
|
|
479
|
-
const
|
|
519
|
+
const nodeVersion = resolveProjectNodeVersion()
|
|
520
|
+
const vars = { skillId: skillResult.id, folder, className, displayName, description, namespace, port, nodeVersion, nodeMajor: String(NODE_MAJOR_FLOOR) }
|
|
480
521
|
|
|
481
522
|
// Backend files (pipeline.py from input-type subdir)
|
|
482
|
-
|
|
523
|
+
// 末尾统一追加 SDK 参考速查(纯注释单源模板),各场景模板不各自复制
|
|
524
|
+
writeOut(
|
|
525
|
+
join(targetDir, 'pipeline.py'),
|
|
526
|
+
render(readTemplate('pipeline.py.tpl', inputType), vars) + readTemplate('pipeline-reference.py.tpl')
|
|
527
|
+
)
|
|
483
528
|
writeOut(join(targetDir, 'server.py'), render(readTemplate('server.py.tpl'), vars))
|
|
484
529
|
writeOut(join(targetDir, 'pyproject.toml'), render(readTemplate('pyproject.toml.tpl'), vars))
|
|
485
530
|
writeOut(join(targetDir, '.env.example'), render(readTemplate('env.example.tpl'), vars))
|
|
@@ -504,6 +549,10 @@ async function main() {
|
|
|
504
549
|
- 输入组件:\`AmazonSearchBar\` + \`CountrySelect\`(来自 @minus/platform-widgets)
|
|
505
550
|
- 校验函数:\`validateAsins(value)\` — 校验 ASIN 格式,返回 { asins: string[], error? }
|
|
506
551
|
- 支持单个或多个 ASIN 输入(逗号/换行分隔)
|
|
552
|
+
- 数量限制:\`validateAsins(value, { min, max })\`
|
|
553
|
+
- 仅允许一个:\`validateAsins(value, { min: 1, max: 1 })\`
|
|
554
|
+
- 一个或多个:省略第二个参数
|
|
555
|
+
- 至少两个:\`validateAsins(value, { min: 2 })\`
|
|
507
556
|
- 修改输入验证规则:编辑 frontend/src/main.tsx 中的 handleSubmit 函数
|
|
508
557
|
- 修改 pipeline 步骤:编辑 pipeline.py 中的 step_N 方法`,
|
|
509
558
|
keyword: `## 模板能力(inputType: keyword)
|
|
@@ -552,10 +601,12 @@ ${templateDocs[inputType] || templateDocs.custom}
|
|
|
552
601
|
|
|
553
602
|
写代码前必须先了解 SDK 提供的能力,优先使用 SDK 已有的组件和方法,不要手写。
|
|
554
603
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
604
|
+
⛔ 下面凡是 \`${platformUrl}/runtime/...\` 的远程文档,**一律用 \`curl -sSL '<url>'\` 抓取,不要用 WebFetch**:平台文档是 http + 非标准端口地址,WebFetch 连不上(TCP 层 ECONNREFUSED,与 http/https 无关),会把可达的文档误判成"读不到"而停止写代码。\`curl -sSL\` 走系统网络栈并自动跟随重定向,是唯一可靠取法;只有 \`curl\` 真的失败(连不上 / 非 2xx)才说明文档不可达。
|
|
605
|
+
|
|
606
|
+
### 开发手册(索引)
|
|
607
|
+
写前端代码前,用 curl 抓取以下开发手册:
|
|
608
|
+
- \`curl -sSL '${platformUrl}/runtime/frontend-guide/doc.md'\`
|
|
609
|
+
索引按任务场景列出子文档路径(如 \`contract.md\`、\`step-summary.md\`)。根据当前任务 curl 对应子文档,不要一次性加载全部。
|
|
559
610
|
|
|
560
611
|
### 后端 SDK 参考
|
|
561
612
|
写 pipeline.py 前,读以下本地文件了解可用 API:
|
|
@@ -563,12 +614,20 @@ ${templateDocs[inputType] || templateDocs.custom}
|
|
|
563
614
|
- \`PipelineContext\`:读 \`.venv/**/minus_ai_sdk/pipeline/context.py\`
|
|
564
615
|
|
|
565
616
|
### 前端 SDK 参考
|
|
566
|
-
@minus/* 包通过平台 CDN
|
|
567
|
-
- Widget
|
|
568
|
-
|
|
617
|
+
@minus/* 包通过平台 CDN 加载,本地无源码。写前端代码前,用 curl 抓取以下文档了解可用 API:
|
|
618
|
+
- Widget 框架(索引):\`curl -sSL '${platformUrl}/runtime/widget-framework/docs.md'\`
|
|
619
|
+
索引按导出分类列出子文档路径(如 \`docs--step.md\`、\`docs--widget.md\`)。根据你要用的能力 curl 对应子文档,不要一次性加载全部。
|
|
620
|
+
- 平台组件(索引):\`curl -sSL '${platformUrl}/runtime/platform-widgets/docs.md'\`
|
|
621
|
+
索引列出所有组件摘要和子文档路径(如 \`docs--chart.md\`、\`docs--thumbgrid.md\`)。根据你要用的组件 curl 对应子文档,不要一次性加载全部。
|
|
622
|
+
- 工具函数(索引):\`curl -sSL '${platformUrl}/runtime/platform-utils/docs.md'\`
|
|
623
|
+
数值格式化工具(千分位、百分比、价格、评分等),直接 \`import { fmtNum, fmtPct, fmtPrice } from '@minus/platform-utils'\` 使用。函数增多后会拆为子文档,届时按索引 curl 对应子文档。
|
|
569
624
|
|
|
570
625
|
修改前端代码后,同步更新 \`frontend/src/locales/\` 下的多语言文件。
|
|
571
626
|
|
|
627
|
+
## 交互原则
|
|
628
|
+
- **尽量一次只问一个问题** — 用户没有研发背景,避免同时抛出多个不相关的问题,逐步引导
|
|
629
|
+
- 用简洁的日常语言解释技术概念,避免术语轰炸
|
|
630
|
+
|
|
572
631
|
## 开发约定
|
|
573
632
|
- 新增 pipeline 步骤:在 pipeline.py 中添加 \`async def step_N(self, ctx)\` 方法
|
|
574
633
|
- 新增前端步骤渲染:在 main.tsx 的 \`buildSteps\` 函数中添加步骤配置
|
|
@@ -579,9 +638,10 @@ ${templateDocs[inputType] || templateDocs.custom}
|
|
|
579
638
|
// README.md
|
|
580
639
|
writeOut(join(targetDir, 'README.md'), render(readTemplate('README.md.tpl'), vars))
|
|
581
640
|
|
|
582
|
-
// frontend/assets/ 和 tests/
|
|
641
|
+
// frontend/assets/ 和 tests/ 目录(pytest 骨架:新 skill 天生带测试)
|
|
583
642
|
writeOut(join(targetDir, 'frontend', 'assets', '.gitkeep'), '')
|
|
584
|
-
writeOut(join(targetDir, 'tests', '.
|
|
643
|
+
writeOut(join(targetDir, 'tests', 'conftest.py'), render(readTemplate('conftest.py.tpl'), vars))
|
|
644
|
+
writeOut(join(targetDir, 'tests', 'test_pipeline.py'), render(readTemplate('test_pipeline.py.tpl'), vars))
|
|
585
645
|
|
|
586
646
|
const rootPkgContent = render(readTemplate('root-package.json.tpl'), vars)
|
|
587
647
|
const fePkgContent = render(readTemplate('frontend-package.json.tpl'), vars)
|
|
@@ -595,10 +655,6 @@ ${templateDocs[inputType] || templateDocs.custom}
|
|
|
595
655
|
writeOut(join(targetDir, 'frontend/tsconfig.json'), render(readTemplate('tsconfig.json.tpl'), vars))
|
|
596
656
|
writeOut(join(targetDir, 'frontend/src/main.tsx'), render(readTemplate('main.tsx.tpl', inputType), vars))
|
|
597
657
|
writeOut(join(targetDir, 'frontend/src/locales/zh-CN.json'), render(readTemplate('zh-CN.json.tpl', inputType), vars))
|
|
598
|
-
writeOut(join(targetDir, 'frontend/src/locales/en-US.json'), render(readTemplate('en-US.json.tpl', inputType), vars))
|
|
599
|
-
|
|
600
|
-
// 类型声明文件(让 tsc build 能识别 @minus/* 模块)
|
|
601
|
-
writeOut(join(targetDir, 'frontend/src/minus-runtime.d.ts'), `declare module '@minus/*';\n`)
|
|
602
658
|
|
|
603
659
|
// 4. git init (skip if git not installed)
|
|
604
660
|
try {
|
package/package.json
CHANGED
package/templates/README.md.tpl
CHANGED
|
@@ -6,7 +6,7 @@ Minus Skill 项目,由 `create-skill` 脚手架生成。
|
|
|
6
6
|
|
|
7
7
|
| 依赖 | 最低版本 | 说明 |
|
|
8
8
|
|---|---|---|
|
|
9
|
-
| Node.js | >=
|
|
9
|
+
| Node.js | >= {{nodeMajor}} | 前端构建和开发服务 |
|
|
10
10
|
| pnpm | >= 9 | 包管理(全局 store 共享,多 skill 不重复下载) |
|
|
11
11
|
| Python | >= 3.12 | 后端 Pipeline 运行时 |
|
|
12
12
|
| uv | >= 0.4 | Python 包管理(全局缓存,秒级安装) |
|
|
@@ -45,7 +45,7 @@ pnpm install
|
|
|
45
45
|
```bash
|
|
46
46
|
uv venv .venv
|
|
47
47
|
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
48
|
-
uv pip install -e .
|
|
48
|
+
uv pip install -e . --reinstall-package minus-ai-sdk-python
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
## 配置
|
|
@@ -72,7 +72,7 @@ pnpm run dev
|
|
|
72
72
|
```
|
|
73
73
|
|
|
74
74
|
这会同时启动:
|
|
75
|
-
- **后端** — `uvicorn` 监听 `http://
|
|
75
|
+
- **后端** — `uvicorn` 监听 `http://127.0.0.1:{{port}}`,修改 `.py` 文件自动重载
|
|
76
76
|
- **前端** — `vite` 开发服务,修改前端代码自动热更新
|
|
77
77
|
|
|
78
78
|
单独启动前端:
|
|
@@ -107,10 +107,12 @@ pnpm run build
|
|
|
107
107
|
|
|
108
108
|
| 命令 | 说明 |
|
|
109
109
|
|---|---|
|
|
110
|
-
| `pnpm run dev` |
|
|
110
|
+
| `pnpm run dev` | 启动开发环境(mac/Linux,前后端) |
|
|
111
|
+
| `pnpm run dev:backend` | 仅启动后端(mac/Linux) |
|
|
112
|
+
| `pnpm run dev:win` | 启动开发环境(Windows,前后端) |
|
|
113
|
+
| `pnpm run dev:win:backend` | 仅启动后端(Windows) |
|
|
111
114
|
| `pnpm run build` | 构建前端产物 |
|
|
112
115
|
| `cd frontend && pnpm exec vite` | 仅启动前端 |
|
|
113
|
-
| `.venv/bin/uvicorn server:app --port {{port}} --reload --env-file .env.local` | 仅启动后端 |
|
|
114
116
|
|
|
115
117
|
## 进入开发模式(Claude Code)
|
|
116
118
|
|
|
@@ -4,7 +4,6 @@ import { FlowApp, I18nProvider, mergeMessages, useT, type StepConfig } from '@mi
|
|
|
4
4
|
import { AmazonSearchBar, CountrySelect, SearchSubmitButton, validateAsins, platformWidgetMessages } from '@minus/platform-widgets';
|
|
5
5
|
import { Toaster } from 'sonner';
|
|
6
6
|
import zhCN from './locales/zh-CN.json';
|
|
7
|
-
import enUS from './locales/en-US.json';
|
|
8
7
|
|
|
9
8
|
/*
|
|
10
9
|
* 防御性编码提示(适用于所有 step render 函数):
|
|
@@ -15,12 +14,12 @@ import enUS from './locales/en-US.json';
|
|
|
15
14
|
* - 嵌套:(data.obj as Record<string, unknown> | undefined)?.field ?? fallback
|
|
16
15
|
* 框架已有 Error Boundary 兜底,但不应依赖——优先在数据层做防御。
|
|
17
16
|
*/
|
|
18
|
-
function buildSteps(t: (k: string
|
|
17
|
+
function buildSteps(t: (k: string) => string): StepConfig[] {
|
|
19
18
|
return [
|
|
20
19
|
{
|
|
21
20
|
render: ({ data }) => (
|
|
22
21
|
<div className="minus-default-step-done">
|
|
23
|
-
{(data.text as string | undefined) ?? t('{{namespace}}.step.empty'
|
|
22
|
+
{(data.text as string | undefined) ?? t('{{namespace}}.step.empty')}
|
|
24
23
|
</div>
|
|
25
24
|
),
|
|
26
25
|
},
|
|
@@ -37,7 +36,7 @@ function Home({ title, description, useCases, tags, onStart }: { title: string;
|
|
|
37
36
|
async function handleSubmit() {
|
|
38
37
|
if (loading) return;
|
|
39
38
|
const { asins, error: asinError } = validateAsins(value);
|
|
40
|
-
if (asinError) { setError(t(asinError.key,
|
|
39
|
+
if (asinError) { setError(t(asinError.key, undefined, asinError.vars)); return; }
|
|
41
40
|
setError(null);
|
|
42
41
|
setLoading(true);
|
|
43
42
|
try { await onStart({ asins: asins.join(','), country }); } finally { setLoading(false); }
|
|
@@ -60,7 +59,7 @@ function Home({ title, description, useCases, tags, onStart }: { title: string;
|
|
|
60
59
|
<AmazonSearchBar
|
|
61
60
|
onSubmit={handleSubmit}
|
|
62
61
|
country={<CountrySelect onChange={setCountry} />}
|
|
63
|
-
input={<input type="text" value={value} onChange={(e) => { setValue(e.target.value); if (error) setError(null); }} placeholder={t('{{namespace}}.home.placeholder'
|
|
62
|
+
input={<input type="text" value={value} onChange={(e) => { setValue(e.target.value); if (error) setError(null); }} placeholder={t('{{namespace}}.home.placeholder')} spellCheck={false} disabled={loading} />}
|
|
64
63
|
submit={<SearchSubmitButton />}
|
|
65
64
|
error={error}
|
|
66
65
|
/>
|
|
@@ -71,7 +70,7 @@ function Home({ title, description, useCases, tags, onStart }: { title: string;
|
|
|
71
70
|
const rootEl = document.getElementById('root');
|
|
72
71
|
if (!rootEl) throw new Error('#root not found');
|
|
73
72
|
|
|
74
|
-
const skillMessages = mergeMessages(platformWidgetMessages, { 'zh-CN': zhCN as Record<string, string
|
|
73
|
+
const skillMessages = mergeMessages(platformWidgetMessages, { 'zh-CN': zhCN as Record<string, string> });
|
|
75
74
|
|
|
76
75
|
function SkillRoot() {
|
|
77
76
|
const t = useT();
|
|
@@ -4,7 +4,6 @@ import { FlowApp, I18nProvider, mergeMessages, useT, type StepConfig } from '@mi
|
|
|
4
4
|
import { platformWidgetMessages } from '@minus/platform-widgets';
|
|
5
5
|
import { Toaster } from 'sonner';
|
|
6
6
|
import zhCN from './locales/zh-CN.json';
|
|
7
|
-
import enUS from './locales/en-US.json';
|
|
8
7
|
|
|
9
8
|
/*
|
|
10
9
|
* 防御性编码提示(适用于所有 step render 函数):
|
|
@@ -15,12 +14,12 @@ import enUS from './locales/en-US.json';
|
|
|
15
14
|
* - 嵌套:(data.obj as Record<string, unknown> | undefined)?.field ?? fallback
|
|
16
15
|
* 框架已有 Error Boundary 兜底,但不应依赖——优先在数据层做防御。
|
|
17
16
|
*/
|
|
18
|
-
function buildSteps(t: (k: string
|
|
17
|
+
function buildSteps(t: (k: string) => string): StepConfig[] {
|
|
19
18
|
return [
|
|
20
19
|
{
|
|
21
20
|
render: ({ data }) => (
|
|
22
21
|
<div className="minus-default-step-done">
|
|
23
|
-
{(data.text as string) ?? t('{{namespace}}.step.empty'
|
|
22
|
+
{(data.text as string) ?? t('{{namespace}}.step.empty')}
|
|
24
23
|
</div>
|
|
25
24
|
),
|
|
26
25
|
},
|
|
@@ -54,11 +53,11 @@ function Home({ title, description, useCases, tags, onStart }: { title: string;
|
|
|
54
53
|
)}
|
|
55
54
|
<div style={{ background: '#fff', border: '1px solid #e5e7eb', borderRadius: 8, padding: 20, display: 'flex', flexDirection: 'column', gap: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.04)' }}>
|
|
56
55
|
<label className="minus-field">
|
|
57
|
-
<span>{t('{{namespace}}.home.fieldLabel'
|
|
58
|
-
<input type="text" value={value} onChange={(e) => setValue(e.target.value)} placeholder={t('{{namespace}}.home.placeholder'
|
|
56
|
+
<span>{t('{{namespace}}.home.fieldLabel')}</span>
|
|
57
|
+
<input type="text" value={value} onChange={(e) => setValue(e.target.value)} placeholder={t('{{namespace}}.home.placeholder')} onKeyDown={(e) => e.key === 'Enter' && handleSubmit()} />
|
|
59
58
|
</label>
|
|
60
59
|
<button className="minus-btn minus-btn-primary" onClick={handleSubmit} disabled={!value.trim() || loading}>
|
|
61
|
-
{loading ? t('{{namespace}}.home.processing'
|
|
60
|
+
{loading ? t('{{namespace}}.home.processing') : t('{{namespace}}.home.send')}
|
|
62
61
|
</button>
|
|
63
62
|
</div>
|
|
64
63
|
</div>
|
|
@@ -68,7 +67,7 @@ function Home({ title, description, useCases, tags, onStart }: { title: string;
|
|
|
68
67
|
const rootEl = document.getElementById('root');
|
|
69
68
|
if (!rootEl) throw new Error('#root not found');
|
|
70
69
|
|
|
71
|
-
const skillMessages = mergeMessages(platformWidgetMessages, { 'zh-CN': zhCN as Record<string, string
|
|
70
|
+
const skillMessages = mergeMessages(platformWidgetMessages, { 'zh-CN': zhCN as Record<string, string> });
|
|
72
71
|
|
|
73
72
|
function SkillRoot() {
|
|
74
73
|
const t = useT();
|
|
@@ -4,7 +4,6 @@ import { FlowApp, I18nProvider, mergeMessages, useT, type StepConfig } from '@mi
|
|
|
4
4
|
import { CompletionPanel, platformWidgetMessages } from '@minus/platform-widgets';
|
|
5
5
|
import { Toaster } from 'sonner';
|
|
6
6
|
import zhCN from './locales/zh-CN.json';
|
|
7
|
-
import enUS from './locales/en-US.json';
|
|
8
7
|
|
|
9
8
|
/*
|
|
10
9
|
* 防御性编码提示(适用于所有 step render 函数):
|
|
@@ -15,12 +14,12 @@ import enUS from './locales/en-US.json';
|
|
|
15
14
|
* - 嵌套:(data.obj as Record<string, unknown> | undefined)?.field ?? fallback
|
|
16
15
|
* 框架已有 Error Boundary 兜底,但不应依赖——优先在数据层做防御。
|
|
17
16
|
*/
|
|
18
|
-
function buildSteps(t: (k: string
|
|
17
|
+
function buildSteps(t: (k: string) => string): StepConfig[] {
|
|
19
18
|
return [
|
|
20
19
|
{
|
|
21
20
|
render: ({ data }) => (
|
|
22
21
|
<div className="minus-default-step-done">
|
|
23
|
-
{(data.text as string | undefined) ?? t('{{namespace}}.step.empty'
|
|
22
|
+
{(data.text as string | undefined) ?? t('{{namespace}}.step.empty')}
|
|
24
23
|
</div>
|
|
25
24
|
),
|
|
26
25
|
},
|
|
@@ -61,7 +60,7 @@ if (!rootEl) throw new Error('#root not found');
|
|
|
61
60
|
|
|
62
61
|
const skillMessages = mergeMessages(
|
|
63
62
|
platformWidgetMessages,
|
|
64
|
-
{ 'zh-CN': zhCN as Record<string, string
|
|
63
|
+
{ 'zh-CN': zhCN as Record<string, string> },
|
|
65
64
|
);
|
|
66
65
|
|
|
67
66
|
function SkillRoot() {
|
|
@@ -4,5 +4,5 @@ from minus_ai_sdk import Pipeline, PipelineContext, StepOutcome
|
|
|
4
4
|
class {{className}}(Pipeline):
|
|
5
5
|
|
|
6
6
|
async def step_1(self, ctx: PipelineContext) -> StepOutcome:
|
|
7
|
-
# TODO:
|
|
8
|
-
return StepOutcome.complete(payload={"text": "
|
|
7
|
+
# TODO: 实现「步骤1」的逻辑
|
|
8
|
+
return StepOutcome.complete(payload={"text": "步骤1完成"})
|
|
@@ -4,7 +4,6 @@ import { FlowApp, I18nProvider, mergeMessages, useT, type StepConfig } from '@mi
|
|
|
4
4
|
import { FilePicker, uploadFile, platformWidgetMessages } from '@minus/platform-widgets';
|
|
5
5
|
import { Toaster } from 'sonner';
|
|
6
6
|
import zhCN from './locales/zh-CN.json';
|
|
7
|
-
import enUS from './locales/en-US.json';
|
|
8
7
|
|
|
9
8
|
/*
|
|
10
9
|
* 防御性编码提示(适用于所有 step render 函数):
|
|
@@ -15,12 +14,12 @@ import enUS from './locales/en-US.json';
|
|
|
15
14
|
* - 嵌套:(data.obj as Record<string, unknown> | undefined)?.field ?? fallback
|
|
16
15
|
* 框架已有 Error Boundary 兜底,但不应依赖——优先在数据层做防御。
|
|
17
16
|
*/
|
|
18
|
-
function buildSteps(t: (k: string
|
|
17
|
+
function buildSteps(t: (k: string) => string): StepConfig[] {
|
|
19
18
|
return [
|
|
20
19
|
{
|
|
21
20
|
render: ({ data }) => (
|
|
22
21
|
<div className="minus-default-step-done">
|
|
23
|
-
{(data.text as string) ?? t('{{namespace}}.step.empty'
|
|
22
|
+
{(data.text as string) ?? t('{{namespace}}.step.empty')}
|
|
24
23
|
</div>
|
|
25
24
|
),
|
|
26
25
|
},
|
|
@@ -35,14 +34,14 @@ function Home({ title, description, useCases, tags, onStart }: { title: string;
|
|
|
35
34
|
|
|
36
35
|
async function handleSubmit() {
|
|
37
36
|
if (loading) return;
|
|
38
|
-
if (!file) { setError(t('{{namespace}}.home.fileEmpty'
|
|
37
|
+
if (!file) { setError(t('{{namespace}}.home.fileEmpty')); return; }
|
|
39
38
|
setError(null);
|
|
40
39
|
setLoading(true);
|
|
41
40
|
try {
|
|
42
41
|
const { fileId } = await uploadFile(file);
|
|
43
42
|
await onStart({ fileId, fileName: file.name });
|
|
44
43
|
} catch (e: any) {
|
|
45
|
-
setError(e?.message ?? t('{{namespace}}.home.uploadFailed'
|
|
44
|
+
setError(e?.message ?? t('{{namespace}}.home.uploadFailed'));
|
|
46
45
|
} finally {
|
|
47
46
|
setLoading(false);
|
|
48
47
|
}
|
|
@@ -64,12 +63,12 @@ function Home({ title, description, useCases, tags, onStart }: { title: string;
|
|
|
64
63
|
)}
|
|
65
64
|
<div style={{ background: '#fff', border: '1px solid #e5e7eb', borderRadius: 8, padding: 20, display: 'flex', flexDirection: 'column', gap: 16, boxShadow: '0 1px 2px rgba(0,0,0,0.04)' }}>
|
|
66
65
|
<label className="minus-field">
|
|
67
|
-
<span>{t('{{namespace}}.home.fieldLabel'
|
|
66
|
+
<span>{t('{{namespace}}.home.fieldLabel')}</span>
|
|
68
67
|
<FilePicker value={file} onChange={(f) => { setFile(f); if (error) setError(null); }} disabled={loading} />
|
|
69
68
|
</label>
|
|
70
69
|
{error && <p style={{ margin: 0, fontSize: 13, color: 'var(--minus-color-danger, #ef4444)' }}>{error}</p>}
|
|
71
70
|
<button className="minus-btn minus-btn-primary" onClick={handleSubmit} disabled={!file || loading}>
|
|
72
|
-
{loading ? t('{{namespace}}.home.processing'
|
|
71
|
+
{loading ? t('{{namespace}}.home.processing') : t('{{namespace}}.home.send')}
|
|
73
72
|
</button>
|
|
74
73
|
</div>
|
|
75
74
|
</div>
|
|
@@ -79,7 +78,7 @@ function Home({ title, description, useCases, tags, onStart }: { title: string;
|
|
|
79
78
|
const rootEl = document.getElementById('root');
|
|
80
79
|
if (!rootEl) throw new Error('#root not found');
|
|
81
80
|
|
|
82
|
-
const skillMessages = mergeMessages(platformWidgetMessages, { 'zh-CN': zhCN as Record<string, string
|
|
81
|
+
const skillMessages = mergeMessages(platformWidgetMessages, { 'zh-CN': zhCN as Record<string, string> });
|
|
83
82
|
|
|
84
83
|
function SkillRoot() {
|
|
85
84
|
const t = useT();
|
|
@@ -5,14 +5,16 @@
|
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"build": "tsc -b && vite build",
|
|
8
|
-
"
|
|
8
|
+
"check": "tsc --noEmit",
|
|
9
|
+
"dev": "vite build --watch",
|
|
10
|
+
"sync:types": "sync-types"
|
|
9
11
|
},
|
|
10
12
|
"dependencies": {
|
|
11
13
|
"sonner": "^2.0.7"
|
|
12
14
|
},
|
|
13
15
|
"devDependencies": {
|
|
14
|
-
"@minus-ai/dev-vite-plugin": "^0.1.0-beta.
|
|
15
|
-
"@types/node": "^
|
|
16
|
+
"@minus-ai/dev-vite-plugin": "^0.1.0-beta.18",
|
|
17
|
+
"@types/node": "^{{nodeMajor}}.0.0",
|
|
16
18
|
"@types/react": "^18.3.3",
|
|
17
19
|
"@types/react-dom": "^18.3.0",
|
|
18
20
|
"@vitejs/plugin-react": "^4.3.1",
|
|
@@ -4,7 +4,6 @@ import { FlowApp, I18nProvider, mergeMessages, useT, type StepConfig } from '@mi
|
|
|
4
4
|
import { AmazonSearchBar, CountrySelect, SearchSubmitButton, validateKeywords, platformWidgetMessages } from '@minus/platform-widgets';
|
|
5
5
|
import { Toaster } from 'sonner';
|
|
6
6
|
import zhCN from './locales/zh-CN.json';
|
|
7
|
-
import enUS from './locales/en-US.json';
|
|
8
7
|
|
|
9
8
|
/*
|
|
10
9
|
* 防御性编码提示(适用于所有 step render 函数):
|
|
@@ -15,12 +14,12 @@ import enUS from './locales/en-US.json';
|
|
|
15
14
|
* - 嵌套:(data.obj as Record<string, unknown> | undefined)?.field ?? fallback
|
|
16
15
|
* 框架已有 Error Boundary 兜底,但不应依赖——优先在数据层做防御。
|
|
17
16
|
*/
|
|
18
|
-
function buildSteps(t: (k: string
|
|
17
|
+
function buildSteps(t: (k: string) => string): StepConfig[] {
|
|
19
18
|
return [
|
|
20
19
|
{
|
|
21
20
|
render: ({ data }) => (
|
|
22
21
|
<div className="minus-default-step-done">
|
|
23
|
-
{(data.text as string) ?? t('{{namespace}}.step.empty'
|
|
22
|
+
{(data.text as string) ?? t('{{namespace}}.step.empty')}
|
|
24
23
|
</div>
|
|
25
24
|
),
|
|
26
25
|
},
|
|
@@ -37,7 +36,7 @@ function Home({ title, description, useCases, tags, onStart }: { title: string;
|
|
|
37
36
|
async function handleSubmit() {
|
|
38
37
|
if (loading) return;
|
|
39
38
|
const { keywords, error: kwError } = validateKeywords(value);
|
|
40
|
-
if (kwError) { setError(t(kwError.key,
|
|
39
|
+
if (kwError) { setError(t(kwError.key, undefined, kwError.vars)); return; }
|
|
41
40
|
setError(null);
|
|
42
41
|
setLoading(true);
|
|
43
42
|
try { await onStart({ keywords: keywords.join(','), country }); } finally { setLoading(false); }
|
|
@@ -60,7 +59,7 @@ function Home({ title, description, useCases, tags, onStart }: { title: string;
|
|
|
60
59
|
<AmazonSearchBar
|
|
61
60
|
onSubmit={handleSubmit}
|
|
62
61
|
country={<CountrySelect onChange={setCountry} />}
|
|
63
|
-
input={<textarea value={value} onChange={(e) => { setValue(e.target.value); if (error) setError(null); }} onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSubmit(); } }} placeholder={t('{{namespace}}.home.placeholder'
|
|
62
|
+
input={<textarea value={value} onChange={(e) => { setValue(e.target.value); if (error) setError(null); }} onKeyDown={(e) => { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); handleSubmit(); } }} placeholder={t('{{namespace}}.home.placeholder')} spellCheck={false} disabled={loading} rows={1} />}
|
|
64
63
|
submit={<SearchSubmitButton />}
|
|
65
64
|
error={error}
|
|
66
65
|
/>
|
|
@@ -71,7 +70,7 @@ function Home({ title, description, useCases, tags, onStart }: { title: string;
|
|
|
71
70
|
const rootEl = document.getElementById('root');
|
|
72
71
|
if (!rootEl) throw new Error('#root not found');
|
|
73
72
|
|
|
74
|
-
const skillMessages = mergeMessages(platformWidgetMessages, { 'zh-CN': zhCN as Record<string, string
|
|
73
|
+
const skillMessages = mergeMessages(platformWidgetMessages, { 'zh-CN': zhCN as Record<string, string> });
|
|
75
74
|
|
|
76
75
|
function SkillRoot() {
|
|
77
76
|
const t = useT();
|
package/templates/main.tsx.tpl
CHANGED
|
@@ -4,7 +4,6 @@ import { FlowApp, I18nProvider, mergeMessages, useT, type StepConfig, type StepR
|
|
|
4
4
|
import { AmazonSearchBar, CompletionPanel, CountrySelect, SearchSubmitButton, validateAsins, validateKeywords, platformWidgetMessages } from '@minus/platform-widgets';
|
|
5
5
|
import { Toaster } from 'sonner';
|
|
6
6
|
import zhCN from './locales/zh-CN.json';
|
|
7
|
-
import enUS from './locales/en-US.json';
|
|
8
7
|
|
|
9
8
|
type InputType = 'keyword' | 'asin';
|
|
10
9
|
|
|
@@ -17,12 +16,12 @@ type InputType = 'keyword' | 'asin';
|
|
|
17
16
|
* - 嵌套:(data.obj as Record<string, unknown> | undefined)?.field ?? fallback
|
|
18
17
|
* 框架已有 Error Boundary 兜底,但不应依赖——优先在数据层做防御。
|
|
19
18
|
*/
|
|
20
|
-
function buildSteps(t: (k: string
|
|
19
|
+
function buildSteps(t: (k: string) => string): StepConfig[] {
|
|
21
20
|
return [
|
|
22
21
|
{
|
|
23
22
|
render: ({ data }) => (
|
|
24
23
|
<div className="minus-default-step-done">
|
|
25
|
-
{(data.text as string) ?? t('{{namespace}}.step.empty'
|
|
24
|
+
{(data.text as string) ?? t('{{namespace}}.step.empty')}
|
|
26
25
|
</div>
|
|
27
26
|
),
|
|
28
27
|
},
|
|
@@ -55,7 +54,7 @@ function Home({
|
|
|
55
54
|
if (inputType === 'asin') {
|
|
56
55
|
const { asins, error: asinError } = validateAsins(value);
|
|
57
56
|
if (asinError) {
|
|
58
|
-
setError(t(asinError.key,
|
|
57
|
+
setError(t(asinError.key, undefined, asinError.vars));
|
|
59
58
|
return;
|
|
60
59
|
}
|
|
61
60
|
setError(null);
|
|
@@ -68,7 +67,7 @@ function Home({
|
|
|
68
67
|
} else {
|
|
69
68
|
const { keywords, error: kwError } = validateKeywords(value);
|
|
70
69
|
if (kwError) {
|
|
71
|
-
setError(t(kwError.key,
|
|
70
|
+
setError(t(kwError.key, undefined, kwError.vars));
|
|
72
71
|
return;
|
|
73
72
|
}
|
|
74
73
|
setError(null);
|
|
@@ -82,8 +81,8 @@ function Home({
|
|
|
82
81
|
}
|
|
83
82
|
|
|
84
83
|
const placeholder = inputType === 'keyword'
|
|
85
|
-
? t('{{namespace}}.home.placeholderKeyword'
|
|
86
|
-
: t('{{namespace}}.home.placeholderAsin'
|
|
84
|
+
? t('{{namespace}}.home.placeholderKeyword')
|
|
85
|
+
: t('{{namespace}}.home.placeholderAsin');
|
|
87
86
|
|
|
88
87
|
return (
|
|
89
88
|
<>
|
|
@@ -116,7 +115,7 @@ function Home({
|
|
|
116
115
|
marginLeft: type === 'asin' ? -1 : 0,
|
|
117
116
|
}}
|
|
118
117
|
>
|
|
119
|
-
{t(`{{namespace}}.home.tab.${type}
|
|
118
|
+
{t(`{{namespace}}.home.tab.${type}`)}
|
|
120
119
|
</button>
|
|
121
120
|
))}
|
|
122
121
|
</div>
|
|
@@ -146,7 +145,7 @@ if (!rootEl) throw new Error('#root not found');
|
|
|
146
145
|
|
|
147
146
|
const skillMessages = mergeMessages(
|
|
148
147
|
platformWidgetMessages,
|
|
149
|
-
{ 'zh-CN': zhCN as Record<string, string
|
|
148
|
+
{ 'zh-CN': zhCN as Record<string, string> },
|
|
150
149
|
);
|
|
151
150
|
|
|
152
151
|
function SkillRoot() {
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
4
|
+
# SDK 参考(纯注释)
|
|
5
|
+
#
|
|
6
|
+
# StepOutcome 三态与 ctx 能力(entry_params / previous_outputs / last_user_input /
|
|
7
|
+
# sif / llm / upload_file)的权威定义和用法,读已安装 SDK 的模块头注释与 docstring:
|
|
8
|
+
# .venv/**/minus_ai_sdk/pipeline/context.py
|
|
9
|
+
# .venv/**/minus_ai_sdk/pipeline/outcome.py
|
|
10
|
+
# 不要凭记忆写签名——以上述源码为准(SDK 会原地更新,文档随包发布永不过期)。
|
|
11
|
+
#
|
|
12
|
+
# 唯一容易写反的语义:StepOutcome.input_required 暂停后,用户提交的内容
|
|
13
|
+
# 挂在「下一步」的 ctx.last_user_input,而不是重跑本步。
|
|
14
|
+
# 凡是涉及 LLM 步骤摘要(放本步还是下一步、随哪个终态下发),判断标准和写法
|
|
15
|
+
# 一律以前端开发手册子文档 step-summary.md 为准(取法见 CLAUDE.md「开发手册(索引)」)。
|
|
16
|
+
# ──────────────────────────────────────────────────────────────────────────────
|
|
@@ -1,2 +1,15 @@
|
|
|
1
1
|
packages:
|
|
2
2
|
- frontend
|
|
3
|
+
|
|
4
|
+
# pnpm 11 的 minimumReleaseAge 门槛会拒装"发布太近"的版本,导致内部包刚发布的新版被压回旧版。
|
|
5
|
+
# @minus-ai/* 是平台内部高频迭代包,豁免门槛以保证新建项目总能拿到最新版。
|
|
6
|
+
minimumReleaseAgeExclude:
|
|
7
|
+
- '@minus-ai/*'
|
|
8
|
+
|
|
9
|
+
# pnpm 11.4 起用 allowBuilds 审批构建脚本;只写 onlyBuiltDependencies 会被忽略并报 ERR_PNPM_IGNORED_BUILDS(install exit 1)。
|
|
10
|
+
allowBuilds:
|
|
11
|
+
esbuild: true
|
|
12
|
+
|
|
13
|
+
# 兼容 pnpm 10.16+ 的旧机制,与 allowBuilds 共存无害。
|
|
14
|
+
onlyBuiltDependencies:
|
|
15
|
+
- esbuild
|
|
@@ -9,5 +9,16 @@ dependencies = [
|
|
|
9
9
|
"uvicorn[standard]",
|
|
10
10
|
]
|
|
11
11
|
|
|
12
|
+
[project.optional-dependencies]
|
|
13
|
+
dev = ["pytest", "pytest-asyncio"]
|
|
14
|
+
|
|
12
15
|
[tool.setuptools]
|
|
13
16
|
py-modules = ["pipeline", "server"]
|
|
17
|
+
|
|
18
|
+
[tool.uv]
|
|
19
|
+
# SDK wheel 可能在同一版本号下更新过,安装时绕过本地缓存强制重新下载
|
|
20
|
+
reinstall-package = ["minus-ai-sdk-python"]
|
|
21
|
+
|
|
22
|
+
[tool.pytest.ini_options]
|
|
23
|
+
asyncio_mode = "auto"
|
|
24
|
+
testpaths = ["tests"]
|
|
@@ -3,17 +3,21 @@
|
|
|
3
3
|
"description": "{{description}}",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">={{nodeMajor}}"
|
|
8
|
+
},
|
|
9
|
+
"volta": {
|
|
10
|
+
"node": "{{nodeVersion}}",
|
|
11
|
+
"pnpm": "11.4.0"
|
|
12
|
+
},
|
|
6
13
|
"workspaces": ["frontend"],
|
|
7
14
|
"scripts": {
|
|
8
|
-
"dev": "minus-dev
|
|
9
|
-
"dev:backend": "minus-dev
|
|
10
|
-
"build": "cd frontend && pnpm run build"
|
|
15
|
+
"dev": "minus-dev --port {{port}}",
|
|
16
|
+
"dev:backend": "minus-dev --port {{port}} --backend-only",
|
|
17
|
+
"build": "cd frontend && pnpm run build",
|
|
18
|
+
"test": ".venv/bin/pytest tests"
|
|
11
19
|
},
|
|
12
20
|
"devDependencies": {
|
|
13
|
-
"@minus-ai/dev-vite-plugin": "^0.1.0-beta.
|
|
14
|
-
"concurrently": "^9.1.2"
|
|
15
|
-
},
|
|
16
|
-
"pnpm": {
|
|
17
|
-
"onlyBuiltDependencies": ["esbuild"]
|
|
21
|
+
"@minus-ai/dev-vite-plugin": "^0.1.0-beta.14"
|
|
18
22
|
}
|
|
19
23
|
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""pipeline step 函数的 payload 结构测试骨架。
|
|
2
|
+
|
|
3
|
+
约定(与平台测试体系一致):
|
|
4
|
+
- 直接调用 step 函数,不起 HTTP/SSE;外部 API 用 monkeypatch mock。
|
|
5
|
+
- 断言 payload 的关键字段 + 类型,不断言会漂移的具体数据值。
|
|
6
|
+
运行:.venv/bin/pytest(需先 .venv/bin/pip install pytest pytest-asyncio)
|
|
7
|
+
"""
|
|
8
|
+
from types import SimpleNamespace
|
|
9
|
+
|
|
10
|
+
import pipeline as pl
|
|
11
|
+
from minus_ai_sdk import StepOutcome
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class FakeContext:
|
|
15
|
+
"""鸭子类型 PipelineContext:step 函数常用的成员。"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, entry_params=None, last_user_input=None, previous_outputs=None):
|
|
18
|
+
self.entry_params = entry_params or {}
|
|
19
|
+
self.last_user_input = last_user_input or {}
|
|
20
|
+
self.previous_outputs = previous_outputs or {}
|
|
21
|
+
self.sif = SimpleNamespace() # 外部 API 客户端按需 monkeypatch
|
|
22
|
+
self.emitted = []
|
|
23
|
+
|
|
24
|
+
async def emit(self, message_type, payload=None):
|
|
25
|
+
self.emitted.append((message_type, payload))
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
async def test_step_1_returns_valid_outcome():
|
|
29
|
+
ctx = FakeContext(entry_params={})
|
|
30
|
+
outcome = await pl.{{className}}.step_1(SimpleNamespace(), ctx)
|
|
31
|
+
assert isinstance(outcome, StepOutcome)
|
|
32
|
+
assert isinstance(outcome.payload, dict)
|
|
33
|
+
# TODO: 按你的 step payload 契约补充关键字段断言,例如:
|
|
34
|
+
# assert outcome.kind.value == "input_required"
|
|
35
|
+
# assert isinstance(outcome.payload["rows"], list)
|
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
"esModuleInterop": true,
|
|
10
10
|
"skipLibCheck": true,
|
|
11
11
|
"resolveJsonModule": true,
|
|
12
|
-
"noEmit": true
|
|
12
|
+
"noEmit": true,
|
|
13
|
+
"baseUrl": ".",
|
|
14
|
+
"paths": {
|
|
15
|
+
"@minus/*": ["./.minus/types/@minus/*"]
|
|
16
|
+
}
|
|
13
17
|
},
|
|
14
|
-
"include": ["src"]
|
|
18
|
+
"include": ["src", ".minus/types"]
|
|
15
19
|
}
|
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { minusDev } from '@minus-ai/dev-vite-plugin';
|
|
5
5
|
|
|
6
6
|
export default defineConfig({
|
|
7
|
-
plugins: [react(), ...minusDev({ localBackend: 'http://
|
|
7
|
+
plugins: [react(), ...minusDev({ localBackend: 'http://127.0.0.1:{{port}}' })],
|
|
8
8
|
build: {
|
|
9
9
|
target: 'esnext',
|
|
10
10
|
outDir: path.resolve(__dirname, '../static'),
|