@mawaru/sdk 0.40.0 → 0.41.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_schemas/graph.ts +0 -5
- package/_schemas/human-manifest.test.ts +39 -101
- package/_schemas/human-manifest.ts +25 -152
- package/_schemas/port-spec.test.ts +33 -92
- package/_schemas/port-spec.ts +35 -44
- package/dist/_cli/login.js +15 -1
- package/dist/_cli/previewApi.d.ts +1 -1
- package/dist/_cli/previewCommand.d.ts +0 -8
- package/dist/_cli/previewCommand.js +37 -95
- package/dist/_cli/previewFiles.d.ts +1 -2
- package/dist/_cli/previewFiles.js +18 -42
- package/dist/_schemas/graph.d.ts +0 -15
- package/dist/_schemas/graph.js +0 -5
- package/dist/_schemas/human-manifest.d.ts +6 -24
- package/dist/_schemas/human-manifest.js +24 -116
- package/dist/_schemas/port-spec.d.ts +1 -4
- package/dist/_schemas/port-spec.js +25 -34
- package/dist/cli.js +1 -2
- package/dist/typegen.d.ts +2 -2
- package/dist/typegen.js +21 -21
- package/dist/validate.js +9 -7
- package/dist/views/_humanProps.d.ts +10 -0
- package/dist/views/_humanProps.js +11 -0
- package/dist/views/{index.d.ts → components.d.ts} +0 -1
- package/dist/views/components.js +12 -0
- package/dist/views/host.d.ts +8 -3
- package/dist/views/host.js +5 -5
- package/dist/views/human.d.ts +49 -68
- package/dist/views/human.js +5 -6
- package/dist/views/protocol.d.ts +13 -3
- package/dist/views/protocol.js +2 -2
- package/dist/views/runtime.d.ts +2 -3
- package/dist/views/runtime.js +24 -24
- package/dist/views/sample.d.ts +22 -0
- package/dist/views/sample.js +38 -0
- package/docs/development.md +30 -31
- package/package.json +5 -1
- package/skills/create-loop/SKILL.md +3 -2
- package/templates/CLAUDE.md +2 -0
- package/views/_humanProps.ts +27 -0
- package/views/components.ts +16 -0
- package/views/host.ts +9 -5
- package/views/human.ts +71 -79
- package/views/protocol.ts +29 -6
- package/views/runtime.tsx +36 -45
- package/views/sample.tsx +94 -0
- package/dist/_cli/openBrowser.d.ts +0 -1
- package/dist/_cli/openBrowser.js +0 -15
- package/dist/views/_approval/ApprovalBar.d.ts +0 -20
- package/dist/views/_approval/ApprovalBar.js +0 -74
- package/dist/views/_approvalActions.d.ts +0 -18
- package/dist/views/_approvalActions.js +0 -10
- package/dist/views/index.js +0 -12
- package/views/_approval/ApprovalBar.tsx +0 -239
- package/views/_approvalActions.tsx +0 -31
- package/views/index.ts +0 -16
package/views/human.ts
CHANGED
|
@@ -1,91 +1,83 @@
|
|
|
1
1
|
import type { ReactNode } from "react"
|
|
2
2
|
import type { JsonSchema } from "../_schemas/node.js"
|
|
3
|
+
import type { FileRef } from "./_parts/fileRef.js"
|
|
3
4
|
|
|
4
|
-
// human
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
// (docs/tasks/wip/承認ビューの顧客repo配置.md の「承認バーもビューが描く」)。
|
|
5
|
+
// human ノードの実装(顧客 repo の nodes/human/<dir>/index.tsx)の約束(`@mawaru/sdk/human`)。
|
|
6
|
+
// human ノードは「input を受け取り、人の操作で出口(approve・reject・追加の出口)に値を出す」。
|
|
7
|
+
// program の run(input) が { <出口ポートのkey>: <データ> } を返すのと同じ形で、human はそれを人が画面で選ぶ。
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
9
|
+
// 実装は React のコンポーネント1つで、承認画面の上の帯より下を全部描く。mawaru が esbuild でまとめ、
|
|
10
|
+
// sandbox iframe の中で動かす(mawaru のログイン・API・fetch には届かない)。
|
|
11
|
+
// 局面・担当・全員承認の進み具合は mawaru の承認画面の帯が出し、実装が描かれるのは承認待ちのときだけ。
|
|
12
12
|
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
13
|
+
// 型は mawaru typegen が config.json から作る types.d.ts(Input / Outputs / Output / Refs。program と同じ)を使う:
|
|
14
|
+
// export default defineHuman<Input, Output, Refs>(({ input, out, ctx }) => …)
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
16
|
+
export type HumanProps<
|
|
17
|
+
TInput = unknown,
|
|
18
|
+
TOutput = Record<string, unknown>,
|
|
19
|
+
TRefs = Record<string, unknown>,
|
|
20
|
+
> = {
|
|
21
|
+
/** 届いた値(in ポートの値= step の input) */
|
|
22
|
+
input: TInput
|
|
23
|
+
/**
|
|
24
|
+
* 人の操作を mawaru に渡す。`{ <出口ポートのkey>: <データ> }`(program の run の戻り値と同じ1キーのオブジェクト)。
|
|
25
|
+
* 人がボタンを押した直後の呼び出しだけを受け付ける(開いただけで決定する画面は書けない)。
|
|
26
|
+
* 送れたら承認画面は run の画面へ戻る。断られたら(担当でない・もう決まっている など)理由つきで失敗する
|
|
27
|
+
*/
|
|
28
|
+
out: (output: TOutput) => Promise<void>
|
|
29
|
+
/** input 以外の材料(program の run の ctx と同じ位置づけ) */
|
|
30
|
+
ctx: {
|
|
31
|
+
input: {
|
|
32
|
+
/** input の型(in ポートの JSON Schema) */
|
|
33
|
+
schema: JsonSchema
|
|
34
|
+
/** input に含まれるファイルを表示する URL を取る(sandbox の中からは mawaru のファイルを直接読めない) */
|
|
35
|
+
fetchFileUrl: (file: FileRef) => Promise<string>
|
|
36
|
+
}
|
|
37
|
+
output: {
|
|
38
|
+
/** 出口ごとの型(out ポートの key → JSON Schema。approve・reject・追加の出口) */
|
|
39
|
+
schema: Record<string, JsonSchema>
|
|
40
|
+
/** 出す値に入れるファイル(差し戻しの参考ファイルなど)を上げて、参照を受け取る */
|
|
41
|
+
uploadFile: (file: File) => Promise<FileRef>
|
|
42
|
+
}
|
|
43
|
+
clipboard: {
|
|
44
|
+
/** クリップボードに書く(sandbox の中では使えない navigator.clipboard.writeText の代わり) */
|
|
45
|
+
writeText: (text: string) => Promise<void>
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* ループ変数(この step が始まった時点の値。program の ctx.refs と同じ)。
|
|
49
|
+
* 予約キー `input` が run の起動入力
|
|
50
|
+
*/
|
|
51
|
+
refs: TRefs
|
|
52
|
+
step: {
|
|
53
|
+
/** 見ている人がこの step の担当か(担当でなければ out は断られる) */
|
|
54
|
+
isAssignee: boolean
|
|
55
|
+
/**
|
|
56
|
+
* 値を直してよいか:担当で、全員承認のノードでないとき true
|
|
57
|
+
* (全員承認は全員が同じ値を承認するので、届いた値のまま決める)
|
|
58
|
+
*/
|
|
59
|
+
editable: boolean
|
|
60
|
+
}
|
|
61
|
+
}
|
|
42
62
|
}
|
|
43
63
|
|
|
44
|
-
/** mawaru
|
|
45
|
-
export type
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
/** ループ変数(この step が始まった時点の写し)。予約キー `input` が run の起動入力 */
|
|
51
|
-
refs: unknown
|
|
52
|
-
/** この human ノードの名前 */
|
|
53
|
-
nodeName: string
|
|
54
|
-
/** 局面 */
|
|
55
|
-
phase: ApprovalPhase
|
|
56
|
-
/** 値を直してよい(承認待ちで、見ている人が担当。全員の承認が要るノードでは false) */
|
|
57
|
-
editable: boolean
|
|
58
|
-
/** 承認バーの材料 */
|
|
59
|
-
decision: ApprovalDecisionContext
|
|
60
|
-
}
|
|
64
|
+
/** mawaru が承認画面に描く、human ノードの画面(React のコンポーネント) */
|
|
65
|
+
export type HumanView<
|
|
66
|
+
TInput = unknown,
|
|
67
|
+
TOutput = Record<string, unknown>,
|
|
68
|
+
TRefs = Record<string, unknown>,
|
|
69
|
+
> = (props: HumanProps<TInput, TOutput, TRefs>) => ReactNode
|
|
61
70
|
|
|
62
71
|
/**
|
|
63
|
-
*
|
|
64
|
-
*
|
|
72
|
+
* human ノードの実装を定義する:input と out を受けて描く関数を渡すと、画面を返す。
|
|
73
|
+
* React では props を受けて描く関数がそのままコンポーネントなので、型を付けて返すだけ
|
|
65
74
|
*/
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
view: ApprovalView<TIn>,
|
|
74
|
-
): ApprovalView<TIn> => view
|
|
75
|
-
|
|
76
|
-
/** 手元プレビュー(npx mawaru preview human/<dir>)のケース。nodes/human/<dir>/cases.ts の export default */
|
|
77
|
-
export type ApprovalViewCase = {
|
|
78
|
-
name: string
|
|
79
|
-
/** `ctx.output`(= step の input) */
|
|
80
|
-
input: unknown
|
|
81
|
-
refs?: unknown
|
|
82
|
-
/** 省略時は name */
|
|
83
|
-
nodeName?: string
|
|
84
|
-
/** 省略時は承認待ち */
|
|
85
|
-
phase?: ApprovalPhase
|
|
86
|
-
}
|
|
75
|
+
export const defineHuman = <
|
|
76
|
+
TInput = unknown,
|
|
77
|
+
TOutput = Record<string, unknown>,
|
|
78
|
+
TRefs = Record<string, unknown>,
|
|
79
|
+
>(
|
|
80
|
+
render: (props: HumanProps<TInput, TOutput, TRefs>) => ReactNode,
|
|
81
|
+
): HumanView<TInput, TOutput, TRefs> => render
|
|
87
82
|
|
|
88
|
-
export {
|
|
89
|
-
export { type ApprovalActions, useApprovalActions } from "./_approvalActions.js"
|
|
90
|
-
// 部品キット(土台)もここから import できる
|
|
91
|
-
export * from "./index.js"
|
|
83
|
+
export type { FileRef }
|
package/views/protocol.ts
CHANGED
|
@@ -1,29 +1,52 @@
|
|
|
1
|
-
|
|
1
|
+
import type { JsonSchema } from "../_schemas/node.js"
|
|
2
|
+
|
|
3
|
+
// sandbox iframe の中の実行部分(runtime.tsx)とホスト(mawaru の承認画面)とのメッセージの形。
|
|
2
4
|
// **公開 API ではない**:iframe の中の実行部分とホストは同じデプロイの mawaru のコード(まとめるときに
|
|
3
|
-
//
|
|
5
|
+
// その時点の版を同梱する)なので、いつ変えてもよい。
|
|
4
6
|
// iframe に入る JS を小さく保つため、ここには型と定数だけを置く(ホストの検査は host.ts)
|
|
5
7
|
|
|
6
8
|
export const VIEW_MESSAGE_TAG = "mawaru:view"
|
|
7
9
|
|
|
8
10
|
export type ThemeMode = "light" | "dark"
|
|
9
11
|
|
|
12
|
+
// human ノードの実装に渡す材料のうち、JSON で送れるもの(関数はホストへの頼みごととして iframe の中で組み立てる)
|
|
13
|
+
export type HumanViewData = {
|
|
14
|
+
input: unknown
|
|
15
|
+
inputSchema: JsonSchema
|
|
16
|
+
outputSchema: Record<string, JsonSchema>
|
|
17
|
+
refs: unknown
|
|
18
|
+
isAssignee: boolean
|
|
19
|
+
editable: boolean
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// ビューからホストへの頼みごと(HumanProps の関数と、部品が裏で使う口)
|
|
23
|
+
export type HumanRequestMethod =
|
|
24
|
+
| "out"
|
|
25
|
+
| "fetchFileUrl"
|
|
26
|
+
| "uploadFile"
|
|
27
|
+
| "writeText"
|
|
28
|
+
|
|
10
29
|
// ビュー → ホスト
|
|
11
30
|
export type ViewToHost =
|
|
12
31
|
| { tag: typeof VIEW_MESSAGE_TAG; type: "ready" }
|
|
13
|
-
//
|
|
32
|
+
// 描画の例外(ホストはエラーと理由を出す)
|
|
14
33
|
| { tag: typeof VIEW_MESSAGE_TAG; type: "error"; message: string }
|
|
15
|
-
// ホストに頼む(fileUrl / uploadFile / copyText、承認ビューは decide / openRejectDialog / openExitDialog / close も)
|
|
16
34
|
| {
|
|
17
35
|
tag: typeof VIEW_MESSAGE_TAG
|
|
18
36
|
type: "request"
|
|
19
37
|
id: number
|
|
20
|
-
method:
|
|
38
|
+
method: HumanRequestMethod
|
|
21
39
|
args: unknown
|
|
22
40
|
}
|
|
23
41
|
|
|
24
42
|
// ホスト → ビュー
|
|
25
43
|
export type HostToView =
|
|
26
|
-
| {
|
|
44
|
+
| {
|
|
45
|
+
tag: typeof VIEW_MESSAGE_TAG
|
|
46
|
+
type: "data"
|
|
47
|
+
data: HumanViewData
|
|
48
|
+
mode: ThemeMode
|
|
49
|
+
}
|
|
27
50
|
| {
|
|
28
51
|
tag: typeof VIEW_MESSAGE_TAG
|
|
29
52
|
type: "response"
|
package/views/runtime.tsx
CHANGED
|
@@ -1,24 +1,19 @@
|
|
|
1
1
|
import { Component, type ReactNode } from "react"
|
|
2
2
|
import { createRoot } from "react-dom/client"
|
|
3
|
-
import {
|
|
4
|
-
type ApprovalActions,
|
|
5
|
-
ApprovalActionsProvider,
|
|
6
|
-
} from "./_approvalActions.js"
|
|
7
3
|
import { ViewBridgeProvider } from "./_bridge.js"
|
|
4
|
+
import { type HumanOps, humanPropsOf } from "./_humanProps.js"
|
|
8
5
|
import type { FileRef } from "./_parts/fileRef.js"
|
|
9
|
-
import type {
|
|
6
|
+
import type { HumanView } from "./human.js"
|
|
10
7
|
import {
|
|
11
8
|
type HostToView,
|
|
12
|
-
type
|
|
9
|
+
type HumanRequestMethod,
|
|
10
|
+
type HumanViewData,
|
|
13
11
|
VIEW_MESSAGE_TAG,
|
|
14
12
|
type ViewToHost,
|
|
15
13
|
} from "./protocol.js"
|
|
16
14
|
|
|
17
|
-
// sandbox iframe
|
|
18
|
-
//
|
|
19
|
-
// ホストとのメッセージの形は protocol.ts(公開 API ではない)
|
|
20
|
-
|
|
21
|
-
// ---- iframe の中 ----
|
|
15
|
+
// sandbox iframe の中の実行部分。mawaru が顧客 repo の human ノードの実装をまとめるとき、
|
|
16
|
+
// 入口から runHumanView を呼ぶ。ホストとのメッセージの形は protocol.ts(公開 API ではない)
|
|
22
17
|
|
|
23
18
|
// union のまま tag だけを外す(Omit は union を潰す)
|
|
24
19
|
type WithoutTag<T> = T extends unknown ? Omit<T, "tag"> : never
|
|
@@ -35,7 +30,7 @@ const pending = new Map<
|
|
|
35
30
|
number,
|
|
36
31
|
{ resolve: (value: unknown) => void; reject: (err: Error) => void }
|
|
37
32
|
>()
|
|
38
|
-
const
|
|
33
|
+
const dataListeners = new Set<(data: HumanViewData) => void>()
|
|
39
34
|
|
|
40
35
|
const onHostMessage = (event: MessageEvent) => {
|
|
41
36
|
if (event.source !== window.parent) return
|
|
@@ -49,14 +44,17 @@ const onHostMessage = (event: MessageEvent) => {
|
|
|
49
44
|
else waiting.reject(new Error(message.error))
|
|
50
45
|
return
|
|
51
46
|
}
|
|
52
|
-
if (message.type === "
|
|
47
|
+
if (message.type === "data") {
|
|
53
48
|
document.documentElement.dataset.mode = message.mode
|
|
54
|
-
for (const listener of
|
|
49
|
+
for (const listener of dataListeners) listener(message.data)
|
|
55
50
|
}
|
|
56
51
|
}
|
|
57
52
|
|
|
58
53
|
// ホストに頼む(返事が来るまで待つ)
|
|
59
|
-
|
|
54
|
+
const requestHost = <T,>(
|
|
55
|
+
method: HumanRequestMethod,
|
|
56
|
+
args: unknown,
|
|
57
|
+
): Promise<T> =>
|
|
60
58
|
new Promise<T>((resolve, reject) => {
|
|
61
59
|
const id = nextRequestId++
|
|
62
60
|
pending.set(id, {
|
|
@@ -66,13 +64,22 @@ export const requestHost = <T,>(method: string, args: unknown): Promise<T> =>
|
|
|
66
64
|
post({ type: "request", id, method, args })
|
|
67
65
|
})
|
|
68
66
|
|
|
69
|
-
const
|
|
70
|
-
|
|
67
|
+
const ops: HumanOps = {
|
|
68
|
+
out: (output) => requestHost<void>("out", { output }),
|
|
69
|
+
fetchFileUrl: (file: FileRef) =>
|
|
70
|
+
requestHost<string>("fetchFileUrl", { file }),
|
|
71
71
|
uploadFile: (file: File) => requestHost<FileRef>("uploadFile", { file }),
|
|
72
|
-
|
|
72
|
+
writeText: (text: string) => requestHost<void>("writeText", { text }),
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 部品(FilePreview・SchemaForm のファイル欄)が裏で使う口も、同じ頼みごとにつなぐ
|
|
76
|
+
const bridge = {
|
|
77
|
+
fileUrl: ops.fetchFileUrl,
|
|
78
|
+
uploadFile: ops.uploadFile,
|
|
79
|
+
copyText: ops.writeText,
|
|
73
80
|
}
|
|
74
81
|
|
|
75
|
-
//
|
|
82
|
+
// 描画の例外をホストへ知らせる(ホストはエラーと理由を出す)
|
|
76
83
|
class ReportErrors extends Component<
|
|
77
84
|
{ children: ReactNode },
|
|
78
85
|
{ failed: boolean }
|
|
@@ -89,9 +96,9 @@ class ReportErrors extends Component<
|
|
|
89
96
|
}
|
|
90
97
|
}
|
|
91
98
|
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
const
|
|
99
|
+
// 実装はコンポーネント1つ。状態(直した値など)は実装が useState で持ち、材料が届き直しても
|
|
100
|
+
// 同じコンポーネントとして描き直すので保たれる
|
|
101
|
+
export const runHumanView = (View: HumanView) => {
|
|
95
102
|
window.addEventListener("message", onHostMessage)
|
|
96
103
|
window.addEventListener("error", (event) =>
|
|
97
104
|
post({ type: "error", message: event.message }),
|
|
@@ -102,31 +109,15 @@ const startView = (render: (ctx: unknown) => ReactNode) => {
|
|
|
102
109
|
const container = document.getElementById("root")
|
|
103
110
|
if (!container) throw new Error("#root がありません")
|
|
104
111
|
const root = createRoot(container)
|
|
105
|
-
|
|
112
|
+
dataListeners.add((data) => {
|
|
113
|
+
const props = humanPropsOf(data, ops)
|
|
106
114
|
root.render(
|
|
107
115
|
<ViewBridgeProvider bridge={bridge}>
|
|
108
|
-
<ReportErrors>
|
|
116
|
+
<ReportErrors>
|
|
117
|
+
<View {...props} />
|
|
118
|
+
</ReportErrors>
|
|
109
119
|
</ViewBridgeProvider>,
|
|
110
|
-
)
|
|
111
|
-
)
|
|
120
|
+
)
|
|
121
|
+
})
|
|
112
122
|
post({ type: "ready" })
|
|
113
123
|
}
|
|
114
|
-
|
|
115
|
-
// ---- 承認ビュー ----
|
|
116
|
-
|
|
117
|
-
const actions: ApprovalActions = {
|
|
118
|
-
decide: (port, value) => requestHost<void>("decide", { port, value }),
|
|
119
|
-
openRejectDialog: () => requestHost<void>("openRejectDialog", {}),
|
|
120
|
-
openExitDialog: (key, value) =>
|
|
121
|
-
requestHost<void>("openExitDialog", { key, value }),
|
|
122
|
-
close: () => requestHost<void>("close", {}),
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// ビューはコンポーネント1つ。状態(直した値・選択など)はビューが useState で持ち、ctx が変わっても
|
|
126
|
-
// (局面・担当の更新)同じコンポーネントとして描き直すので保たれる
|
|
127
|
-
export const runApprovalView = (View: ApprovalView) =>
|
|
128
|
-
startView((ctx) => (
|
|
129
|
-
<ApprovalActionsProvider value={actions}>
|
|
130
|
-
<View ctx={ctx as ApprovalViewContext} />
|
|
131
|
-
</ApprovalActionsProvider>
|
|
132
|
-
))
|
package/views/sample.tsx
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useState } from "react"
|
|
2
|
+
import { defineHuman } from "./human.js"
|
|
3
|
+
|
|
4
|
+
// サンプル:AI が下書きした返信を、人が直してから送る(approve)か、理由を書いて差し戻す(reject)。
|
|
5
|
+
// 顧客 repo では Input / Outputs / Output は typegen が config.json から作る(nodes/human/<dir>/types.d.ts)。
|
|
6
|
+
// ここでは同じものを手で書いている
|
|
7
|
+
|
|
8
|
+
type Input = { subject: string; body: string }
|
|
9
|
+
|
|
10
|
+
type Outputs = {
|
|
11
|
+
approve: { subject: string; body: string }
|
|
12
|
+
reject: { prompt: string }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type Output =
|
|
16
|
+
| { approve: Outputs["approve"]; reject?: never }
|
|
17
|
+
| { reject: Outputs["reject"]; approve?: never }
|
|
18
|
+
|
|
19
|
+
export default defineHuman<Input, Output>(({ input, out }) => {
|
|
20
|
+
const [reply, setReply] = useState(input)
|
|
21
|
+
const [prompt, setPrompt] = useState("")
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
style={{
|
|
25
|
+
maxWidth: "720px",
|
|
26
|
+
margin: "0 auto",
|
|
27
|
+
padding: "var(--space-8) var(--space-6)",
|
|
28
|
+
display: "flex",
|
|
29
|
+
flexDirection: "column",
|
|
30
|
+
gap: "var(--space-3)",
|
|
31
|
+
}}
|
|
32
|
+
>
|
|
33
|
+
<input
|
|
34
|
+
value={reply.subject}
|
|
35
|
+
onChange={(e) => setReply({ ...reply, subject: e.target.value })}
|
|
36
|
+
style={{
|
|
37
|
+
padding: "var(--space-2) var(--space-3)",
|
|
38
|
+
border: "var(--border-w) solid var(--border)",
|
|
39
|
+
borderRadius: "var(--radius-s)",
|
|
40
|
+
background: "var(--surface)",
|
|
41
|
+
color: "var(--ink)",
|
|
42
|
+
font: "inherit",
|
|
43
|
+
}}
|
|
44
|
+
/>
|
|
45
|
+
<textarea
|
|
46
|
+
value={reply.body}
|
|
47
|
+
onChange={(e) => setReply({ ...reply, body: e.target.value })}
|
|
48
|
+
style={{
|
|
49
|
+
minHeight: "16em",
|
|
50
|
+
padding: "var(--space-2) var(--space-3)",
|
|
51
|
+
border: "var(--border-w) solid var(--border)",
|
|
52
|
+
borderRadius: "var(--radius-s)",
|
|
53
|
+
background: "var(--surface)",
|
|
54
|
+
color: "var(--ink)",
|
|
55
|
+
font: "inherit",
|
|
56
|
+
}}
|
|
57
|
+
/>
|
|
58
|
+
<div style={{ display: "flex", justifyContent: "flex-end" }}>
|
|
59
|
+
<button
|
|
60
|
+
type="button"
|
|
61
|
+
className="primary"
|
|
62
|
+
disabled={reply.body.trim() === ""}
|
|
63
|
+
onClick={() => out({ approve: reply })}
|
|
64
|
+
>
|
|
65
|
+
この内容で送信
|
|
66
|
+
</button>
|
|
67
|
+
</div>
|
|
68
|
+
<div style={{ display: "flex", gap: "var(--space-2)" }}>
|
|
69
|
+
<input
|
|
70
|
+
value={prompt}
|
|
71
|
+
placeholder="差し戻しの理由"
|
|
72
|
+
onChange={(e) => setPrompt(e.target.value)}
|
|
73
|
+
style={{
|
|
74
|
+
flex: 1,
|
|
75
|
+
padding: "var(--space-2) var(--space-3)",
|
|
76
|
+
border: "var(--border-w) solid var(--border)",
|
|
77
|
+
borderRadius: "var(--radius-s)",
|
|
78
|
+
background: "var(--surface)",
|
|
79
|
+
color: "var(--ink)",
|
|
80
|
+
font: "inherit",
|
|
81
|
+
}}
|
|
82
|
+
/>
|
|
83
|
+
<button
|
|
84
|
+
type="button"
|
|
85
|
+
className="ghost"
|
|
86
|
+
disabled={prompt.trim() === ""}
|
|
87
|
+
onClick={() => out({ reject: { prompt } })}
|
|
88
|
+
>
|
|
89
|
+
差し戻す
|
|
90
|
+
</button>
|
|
91
|
+
</div>
|
|
92
|
+
</div>
|
|
93
|
+
)
|
|
94
|
+
})
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const openBrowser: (url: string) => void;
|
package/dist/_cli/openBrowser.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import { spawn } from "node:child_process";
|
|
2
|
-
// ブラウザを開く(開けなければ URL を出して手で開いてもらう。依存は足さない)
|
|
3
|
-
export const openBrowser = (url) => {
|
|
4
|
-
const [command, args] = process.platform === "darwin"
|
|
5
|
-
? ["open", [url]]
|
|
6
|
-
: process.platform === "win32"
|
|
7
|
-
? ["cmd", ["/c", "start", "", url]]
|
|
8
|
-
: ["xdg-open", [url]];
|
|
9
|
-
try {
|
|
10
|
-
spawn(command, args, { stdio: "ignore", detached: true }).unref();
|
|
11
|
-
}
|
|
12
|
-
catch {
|
|
13
|
-
// 何もしない(URL は既に印字済み)
|
|
14
|
-
}
|
|
15
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { type ReactNode } from "react";
|
|
2
|
-
import type { ApprovalViewContext } from "../human.js";
|
|
3
|
-
/**
|
|
4
|
-
* 承認バー。書類の領域の下に貼り付く(`position: sticky; bottom: 0`)。外側を `minHeight: 100%` の縦の flex にして
|
|
5
|
-
* 中身を `flex: 1` にすると、中身が短くても下端に来る。
|
|
6
|
-
*/
|
|
7
|
-
export declare const ApprovalBar: <TOut = unknown>({ ctx, label, disabled, output, value, children, }: {
|
|
8
|
-
/** ビューが受け取った ctx */
|
|
9
|
-
ctx: ApprovalViewContext<unknown>;
|
|
10
|
-
/** 承認ボタンの文言(省略時は「承認して〈次のノード〉へ」) */
|
|
11
|
-
label?: string;
|
|
12
|
-
/** 承認ボタンを押せない */
|
|
13
|
-
disabled?: boolean;
|
|
14
|
-
/** 承認したとき approve に流す値(宣言の outputs.approve の形。省略時は value)。`<ApprovalBar<Approve> …>` で型を検査できる */
|
|
15
|
-
output?: TOut;
|
|
16
|
-
/** 今の値(ビューで直した値。in の形。省略時は ctx.output)。型の合う追加の出口にはこれが流れる */
|
|
17
|
-
value?: unknown;
|
|
18
|
-
/** バーの左側に足すもの */
|
|
19
|
-
children?: ReactNode;
|
|
20
|
-
}) => import("react").JSX.Element;
|
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { useState } from "react";
|
|
3
|
-
import { useApprovalActions } from "../_approvalActions.js";
|
|
4
|
-
// 承認バー(承認画面の下の帯)。ビューが自分で置く部品で、内蔵の標準ビューも同じものを使う
|
|
5
|
-
// (docs/tasks/wip/承認ビューの顧客repo配置.md の「承認バーもビューが描く」)。
|
|
6
|
-
// 材料は ctx.decision、操作は useApprovalActions()。送信中とエラーは decide の結果からここで持つ
|
|
7
|
-
const errorMessage = (err) => err instanceof Error ? err.message : "送信に失敗しました";
|
|
8
|
-
/**
|
|
9
|
-
* 承認バー。書類の領域の下に貼り付く(`position: sticky; bottom: 0`)。外側を `minHeight: 100%` の縦の flex にして
|
|
10
|
-
* 中身を `flex: 1` にすると、中身が短くても下端に来る。
|
|
11
|
-
*/
|
|
12
|
-
export const ApprovalBar = ({ ctx, label, disabled = false, output, value, children, }) => {
|
|
13
|
-
const actions = useApprovalActions();
|
|
14
|
-
const [sending, setSending] = useState(false);
|
|
15
|
-
const [error, setError] = useState(null);
|
|
16
|
-
const { phase, decision } = ctx;
|
|
17
|
-
const current = value === undefined ? ctx.output : value;
|
|
18
|
-
const approveValue = output === undefined ? current : output;
|
|
19
|
-
const send = (request) => {
|
|
20
|
-
setSending(true);
|
|
21
|
-
setError(null);
|
|
22
|
-
request
|
|
23
|
-
.catch((err) => setError(errorMessage(err)))
|
|
24
|
-
.finally(() => setSending(false));
|
|
25
|
-
};
|
|
26
|
-
const open = (request) => request.catch((err) => setError(errorMessage(err)));
|
|
27
|
-
return (_jsx("div", { style: {
|
|
28
|
-
position: "sticky",
|
|
29
|
-
bottom: 0,
|
|
30
|
-
zIndex: 1,
|
|
31
|
-
flex: "none",
|
|
32
|
-
display: "flex",
|
|
33
|
-
alignItems: "center",
|
|
34
|
-
gap: "var(--space-3)",
|
|
35
|
-
padding: "var(--space-3) var(--space-6)",
|
|
36
|
-
borderTop: phase === "waiting"
|
|
37
|
-
? "var(--border-w-strong) solid var(--human)"
|
|
38
|
-
: "var(--border-w) solid var(--border)",
|
|
39
|
-
background: "var(--surface)",
|
|
40
|
-
}, children: phase === "waiting" ? (_jsxs(_Fragment, { children: [decision.otherAssignees && (_jsxs("span", { style: { fontSize: "var(--text-sm)", color: "var(--muted)" }, children: [_jsx("span", { style: { color: "var(--ink)" }, children: decision.otherAssignees.join("・") }), " さんの担当です — あなたは承認・差し戻しができません"] })), children, _jsx("div", { style: { flex: 1 } }), decision.progress && (_jsxs("span", { style: {
|
|
41
|
-
fontFamily: "var(--font-mono)",
|
|
42
|
-
fontSize: "var(--text-xs)",
|
|
43
|
-
color: "var(--muted)",
|
|
44
|
-
whiteSpace: "nowrap",
|
|
45
|
-
}, children: [decision.progress.done, "/", decision.progress.total, "\u4EBA \u627F\u8A8D\u6E08\u307F"] })), error && (_jsx("span", { role: "alert", style: { color: "var(--fail)" }, children: error })), decision.exits.map((exit) => (_jsx("button", { type: "button", className: "ghost", disabled: !decision.canDecide || sending, onClick: () => exit.needsInput
|
|
46
|
-
? open(actions.openExitDialog(exit.key, current))
|
|
47
|
-
: send(actions.decide(exit.key, current)), style: { fontSize: "var(--text-sm)", whiteSpace: "nowrap" }, children: exit.name }, exit.key))), decision.rejectTargetName && (_jsx("button", { type: "button", className: "ghost", disabled: !decision.canDecide || sending, onClick: () => open(actions.openRejectDialog()), style: { fontSize: "var(--text-sm)", whiteSpace: "nowrap" }, children: "AI\u3078\u5DEE\u3057\u623B\u3059\uFF08reject\uFF09" })), _jsxs("button", { type: "button", className: "primary", disabled: !decision.canDecide || sending || disabled, onClick: () => send(actions.decide("approve", approveValue)), style: {
|
|
48
|
-
display: "flex",
|
|
49
|
-
alignItems: "center",
|
|
50
|
-
gap: "var(--space-2)",
|
|
51
|
-
paddingLeft: "var(--space-5)",
|
|
52
|
-
paddingRight: "var(--space-5)",
|
|
53
|
-
whiteSpace: "nowrap",
|
|
54
|
-
}, children: [sending
|
|
55
|
-
? "承認しています…"
|
|
56
|
-
: decision.progress?.approvedByMe
|
|
57
|
-
? "承認済み — 全員の承認待ち"
|
|
58
|
-
: (label ?? `承認して${decision.approveTargetName ?? "次"}へ`), _jsx("svg", { width: "12", height: "12", viewBox: "0 0 16 16", "aria-hidden": "true", children: _jsx("path", { d: "M4.5 3L12.5 8L4.5 13V3Z", style: { fill: "var(--on-accent)" } }) })] })] })) : phase === "rejected" ? (_jsxs(_Fragment, { children: [_jsxs("svg", { width: "14", height: "14", viewBox: "0 0 16 16", style: { flex: "none" }, "aria-hidden": "true", children: [_jsx("rect", { x: "3.5", y: "7", width: "9", height: "6", rx: "1.5", style: { fill: "none", stroke: "var(--muted)", strokeWidth: 1.5 } }), _jsx("path", { d: "M5.5 7V5.5C5.5 4 6.5 3 8 3C9.5 3 10.5 4 10.5 5.5V7", style: { fill: "none", stroke: "var(--muted)", strokeWidth: 1.5 } })] }), _jsx("span", { style: { fontSize: "var(--text-xs)", color: "var(--muted)" }, children: "\u518D\u5B9F\u884C\u304C\u5B8C\u4E86\u3057\u3066\u65B0\u3057\u3044\u4E0B\u66F8\u304D\u304C\u5C4A\u304F\u3068\u3001\u627F\u8A8D\u5F85\u3061\u306B\u623B\u308A\u901A\u77E5\u3055\u308C\u307E\u3059" }), _jsx("div", { style: { flex: 1 } }), _jsx("button", { type: "button", className: "ghost", disabled: true, style: {
|
|
59
|
-
fontSize: "var(--text-sm)",
|
|
60
|
-
opacity: 0.5,
|
|
61
|
-
whiteSpace: "nowrap",
|
|
62
|
-
}, children: "\u5DEE\u3057\u623B\u3059\uFF08reject\uFF09" }), _jsx("button", { type: "button", className: "primary", disabled: true, style: { opacity: 0.5, whiteSpace: "nowrap" }, children: "\u627F\u8A8D\u3057\u3066\u6B21\u3078" })] })) : (_jsxs(_Fragment, { children: [_jsx("span", { style: {
|
|
63
|
-
color: phase === "failed"
|
|
64
|
-
? "var(--fail)"
|
|
65
|
-
: phase === "cancelled"
|
|
66
|
-
? "var(--muted)"
|
|
67
|
-
: "var(--ok)",
|
|
68
|
-
fontSize: "var(--text-sm)",
|
|
69
|
-
}, children: phase === "failed" ? "✕" : phase === "cancelled" ? "◼" : "✓" }), _jsx("span", { style: { fontSize: "var(--text-sm)", color: "var(--muted)" }, children: phase === "failed"
|
|
70
|
-
? (decision.failedReason ?? "この step は失敗しました")
|
|
71
|
-
: phase === "cancelled"
|
|
72
|
-
? "キャンセル済み — この run は中断されました"
|
|
73
|
-
: "対応済み — 承認して次のノードへ渡しました" }), _jsx("div", { style: { flex: 1 } }), _jsx("button", { type: "button", className: "ghost", onClick: () => open(actions.close()), style: { fontSize: "var(--text-sm)", whiteSpace: "nowrap" }, children: "run \u306E\u9032\u884C\u3092\u898B\u308B \u2192" })] })) }));
|
|
74
|
-
};
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ビューから承認画面の操作を頼む口。決定の送信はホストが行う。
|
|
3
|
-
* ホストは、この step で・見ている人が決定できる(`ctx.decision.canDecide`)ときだけ受け、
|
|
4
|
-
* `decide` はさらに人がビューの中で押した直後のときだけ受ける(開いただけで決定するビューは書けない)。
|
|
5
|
-
*/
|
|
6
|
-
export type ApprovalActions = {
|
|
7
|
-
/** 決定する。`port` は `approve` / `reject` / 追加の出口の key。送り終わると承認画面は run の画面へ戻る */
|
|
8
|
-
decide: (port: string, value: unknown) => Promise<void>;
|
|
9
|
-
/** ホストの差し戻しダイアログを開く(理由と参考ファイルを入れて差し戻す) */
|
|
10
|
-
openRejectDialog: () => Promise<void>;
|
|
11
|
-
/** 追加の出口に流す値を入れるダイアログを開く(`value` が初期値)。値の型が出口と合わないときに使う */
|
|
12
|
-
openExitDialog: (key: string, value: unknown) => Promise<void>;
|
|
13
|
-
/** 承認画面を閉じて run の画面へ戻る(右上の × と同じ) */
|
|
14
|
-
close: () => Promise<void>;
|
|
15
|
-
};
|
|
16
|
-
export declare const ApprovalActionsProvider: import("react").Provider<ApprovalActions | null>;
|
|
17
|
-
/** 承認画面の操作を頼む口(承認画面の中でだけ使える) */
|
|
18
|
-
export declare const useApprovalActions: () => ApprovalActions;
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { createContext, useContext } from "react";
|
|
2
|
-
const ApprovalActionsContext = createContext(null);
|
|
3
|
-
export const ApprovalActionsProvider = ApprovalActionsContext.Provider;
|
|
4
|
-
/** 承認画面の操作を頼む口(承認画面の中でだけ使える) */
|
|
5
|
-
export const useApprovalActions = () => {
|
|
6
|
-
const actions = useContext(ApprovalActionsContext);
|
|
7
|
-
if (!actions)
|
|
8
|
-
throw new Error("承認画面の中で使ってください");
|
|
9
|
-
return actions;
|
|
10
|
-
};
|
package/dist/views/index.js
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// ビューの部品キット(`@mawaru/sdk/views`)。種類(承認・…)に依らない土台:部品・ホストに頼む口。
|
|
2
|
-
// mawaru が顧客 repo のビューをまとめるとき、mawaru の今の版のこのキットを同梱する。
|
|
3
|
-
// **公開 API**:部品の props とホストに頼む口は後方互換でしか変えない
|
|
4
|
-
// (docs/tasks/wip/承認ビューの顧客repo配置.md の「互換の管理」)
|
|
5
|
-
export { useViewBridge } from "./_bridge.js";
|
|
6
|
-
export { documentCardStyle } from "./_parts/DocumentCard.js";
|
|
7
|
-
export { FilePreview } from "./_parts/FilePreview.js";
|
|
8
|
-
export { isFileRef } from "./_parts/fileRef.js";
|
|
9
|
-
export { isUrl, Linkify, linkify } from "./_parts/Linkify.js";
|
|
10
|
-
export { isLongText, Markdown, useAutoGrow } from "./_parts/Markdown.js";
|
|
11
|
-
export { asFormSchema, SchemaForm, } from "./_parts/SchemaForm.js";
|
|
12
|
-
export { NARROW_WIDTH, useNarrow } from "./_parts/useNarrow.js";
|