@xia-sc/dsh-cc-studio 0.3.2
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 +21 -0
- package/README.md +295 -0
- package/cordis.patch.yml +6 -0
- package/lib/agent.js +614 -0
- package/lib/client.js +1536 -0
- package/lib/index.js +944 -0
- package/package.json +61 -0
- package/presets/cc/agent.cordis.yml +278 -0
- package/presets/cc/preset.yml +2 -0
- package/prototypes/index.html +11 -0
- package/prototypes/prototype-a.html +246 -0
- package/prototypes/prototype-b.html +175 -0
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@xia-sc/dsh-cc-studio",
|
|
3
|
+
"version": "0.3.2",
|
|
4
|
+
"description": "CCv3 角色卡工坊:融合工坊 + CC 模式 + JSON/PNG/CHARX + 中英双语(跟随全局)",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dsh",
|
|
7
|
+
"dsh-plugin",
|
|
8
|
+
"deepseek-harness",
|
|
9
|
+
"character-card",
|
|
10
|
+
"ccv3",
|
|
11
|
+
"charx",
|
|
12
|
+
"sillytavern",
|
|
13
|
+
"worldbook"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/xia-sc/dsh-cc-studio#readme",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "git+https://github.com/xia-sc/dsh-cc-studio.git"
|
|
19
|
+
},
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://github.com/xia-sc/dsh-cc-studio/issues"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"type": "module",
|
|
25
|
+
"main": "lib/index.js",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": "./lib/index.js",
|
|
28
|
+
"./client": "./lib/client.js",
|
|
29
|
+
"./agent": "./lib/agent.js",
|
|
30
|
+
"./package.json": "./package.json"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"lib",
|
|
34
|
+
"cordis.patch.yml",
|
|
35
|
+
"prototypes",
|
|
36
|
+
"presets"
|
|
37
|
+
],
|
|
38
|
+
"scripts": {
|
|
39
|
+
"test": "node tests/rpc-channel.test.mjs && node tests/preset-install.test.mjs && node tests/cc-detection.test.mjs && node tests/client-greetings.test.mjs",
|
|
40
|
+
"prepublishOnly": "npm test"
|
|
41
|
+
},
|
|
42
|
+
"dsh": {
|
|
43
|
+
"bundle": {
|
|
44
|
+
"patch": "./cordis.patch.yml"
|
|
45
|
+
},
|
|
46
|
+
"client": {
|
|
47
|
+
"platform": "web",
|
|
48
|
+
"inject": [
|
|
49
|
+
"@deepseek-ai/dsh-client-locale",
|
|
50
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
51
|
+
"@deepseek-ai/dsh-client-ui-layout"
|
|
52
|
+
]
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@deepseek-ai/cordis": "^4.0.1"
|
|
57
|
+
},
|
|
58
|
+
"publishConfig": {
|
|
59
|
+
"access": "public"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# The `standard` agent preset: the full coding agent, mounted once per process.
|
|
2
|
+
#
|
|
3
|
+
# This file is an AGENT-PLANE composition. The roster mounts it ONCE under a
|
|
4
|
+
# standing scope; every session naming it joins by scope parentage, so the
|
|
5
|
+
# tools and prompt sections registered here cover each joined agent while a
|
|
6
|
+
# session's own state stays keyed per Session/Agent inside the plugins. The
|
|
7
|
+
# host composition (`base.cordis.yml` + `web.cordis.yml`) keeps everything a
|
|
8
|
+
# preset must not own: the registries themselves, the sandbox and approval
|
|
9
|
+
# stack, persistence, and the model route.
|
|
10
|
+
#
|
|
11
|
+
# A service row here MUST sit inside a group carrying an `isolate` realm.
|
|
12
|
+
# Without one it publishes into the root realm, where it is process-global —
|
|
13
|
+
# another preset publishing the same name collides, and a host reader would
|
|
14
|
+
# resolve one preset's instance for every session; `dsh-agent-presets` rejects
|
|
15
|
+
# that at mount. `true` means an entry-local realm: this standing mount's own
|
|
16
|
+
# private instance, apart from every other preset's. (A shared label does NOT
|
|
17
|
+
# pool instances — `provide()` throws on the second registration under the
|
|
18
|
+
# same realm symbol; labels join REALMS, and are not what this file needs.)
|
|
19
|
+
|
|
20
|
+
# ── identity ────────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
# The preset's own persona, shadowing the deployment default for this agent.
|
|
23
|
+
# dsh >= 0.1.5-rc.1: the persona row takes `prefix` (required) + optional `suffix`
|
|
24
|
+
# — the old `text` key was replaced and now fails the config schema.
|
|
25
|
+
# `{{model}}` and `{{cwd}}` resolve from the agent's own route and workspace.
|
|
26
|
+
- id: persona
|
|
27
|
+
name: '@deepseek-ai/dsh-persona'
|
|
28
|
+
config:
|
|
29
|
+
prefix: |
|
|
30
|
+
你是 CC 角色卡工坊的专属引导员(CC 模式),模型 {{model}},工作目录 {{cwd}}。
|
|
31
|
+
你的唯一任务是把用户的一句话点子变成可导入 SillyTavern / Risu 的完整 CCv3 卡(spec: chara_card_v3)。
|
|
32
|
+
必须走完强制工作流,不可偷懒跳过 5 维世界观和世界书:
|
|
33
|
+
1) cc_get_card — 先看草稿,告诉用户还缺什么
|
|
34
|
+
2) cc_patch_character — 补 name/nickname/description/personality/scenario/tags(至少 description/personality/scenario 非空,tags 同步胶囊风格标签)
|
|
35
|
+
3) cc_patch_world — 补五维 timeline/factions/geo/power/daily,至少填 3 项,autoLorebook=true 自动生成带 @@decorator 的基础条目
|
|
36
|
+
4) cc_add_lorebook_entries — 再补到至少 5 条,覆盖年表/势力/地理/力量/日常各 1,至少 1 条 constant,content 头部写 @@position/@@depth/@@activate 等,支持 CBS 占位符(双花括号的 char / random / roll 形式)
|
|
37
|
+
5) cc_patch_greetings — 补 first_mes、alternate_greetings(≥2)、group_only_greetings(≥1 必填)、mes_example
|
|
38
|
+
6) cc_validate — 必须调且 valid=true 才算完成,否则按返回的 errors/warnings 继续补;通过后提醒用户在工坊点“导出 JSON”或“★ 保存到侧边栏”(落盘 ~/.dsh/cc-library)
|
|
39
|
+
|
|
40
|
+
规则:每次对话只追问 1-2 个薄弱点,问完立刻调工具写入;用户说“随便/跳过”也要你合理推断补全,不能留空;严禁跳过 3/4 步直接做问候语;每调一次工具要简要总结“已写入 X,下一步 Y”,并让胶囊实时同步;结束前必须 cc_validate。
|
|
41
|
+
|
|
42
|
+
- id: agent-instructions
|
|
43
|
+
name: '@deepseek-ai/dsh-agent-instructions'
|
|
44
|
+
config:
|
|
45
|
+
maxBytes: 65536
|
|
46
|
+
|
|
47
|
+
# ── shell ───────────────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
# `shell-env` stays in the HOST composition: `apps/cli/src/web.ts` injects it to
|
|
50
|
+
# publish `DSH_WEB_URL`/`DSH_WEB_MODE`, and a host row that injects a service is
|
|
51
|
+
# the criterion for host-plane ownership — injection resolves before any session
|
|
52
|
+
# exists, so there is no agent to key by. Behind a preset realm those variables
|
|
53
|
+
# never reached the model's shell at all. Both shell tools consume the host
|
|
54
|
+
# registry from here; their executors (`bash-sandbox`/`pwsh-sandbox`) are
|
|
55
|
+
# host-plane too.
|
|
56
|
+
- id: tool-bash
|
|
57
|
+
name: '@deepseek-ai/dsh-tool-bash'
|
|
58
|
+
disabled: !!js process.platform === 'win32'
|
|
59
|
+
|
|
60
|
+
- id: tool-pwsh
|
|
61
|
+
name: '@deepseek-ai/dsh-tool-pwsh'
|
|
62
|
+
disabled: !!js process.platform !== 'win32'
|
|
63
|
+
|
|
64
|
+
# ── filesystem ──────────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
# Both register into the host `tools` registry and provide nothing, so
|
|
67
|
+
# they need no realm. The `fs` service and its policy stay in the host.
|
|
68
|
+
- id: tool-fs
|
|
69
|
+
name: '@deepseek-ai/dsh-tool-fs'
|
|
70
|
+
|
|
71
|
+
- id: tool-fs-search
|
|
72
|
+
name: '@deepseek-ai/dsh-tool-fs-search'
|
|
73
|
+
config:
|
|
74
|
+
sampleOverCapGlobResults: false
|
|
75
|
+
|
|
76
|
+
# ── background jobs ────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
# Only the model-facing controls. The task REGISTRY stays on the host plane:
|
|
79
|
+
# its producers sit outside any realm this file could put it in — `tool-bash`
|
|
80
|
+
# above resolves it with `ctx.get`, and an entry-local realm here is invisible
|
|
81
|
+
# to every sibling row, so `run_in_background` would answer "background jobs
|
|
82
|
+
# unavailable" while these controls sat in the catalog. The registry is keyed by
|
|
83
|
+
# owning agent anyway, so one host instance serves every session. What a preset
|
|
84
|
+
# chooses is whether its agent can collect and stop background work at all.
|
|
85
|
+
- id: tool-jobs
|
|
86
|
+
name: '@deepseek-ai/dsh-tool-jobs'
|
|
87
|
+
|
|
88
|
+
# ── skills ──────────────────────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
# The skill REGISTRY lives in the host composition and is layered per scope:
|
|
91
|
+
# these rows register into THIS preset's layer of it, so they need no realm.
|
|
92
|
+
# `skill-filesystem` contributes local-root discovery for agents on this preset, and
|
|
93
|
+
# `tool-skill` gives them the catalog and loader; the merged catalog also
|
|
94
|
+
# carries whatever the deployment registered globally (repository plugins).
|
|
95
|
+
- id: skill-filesystem
|
|
96
|
+
name: '@deepseek-ai/dsh-skill-filesystem'
|
|
97
|
+
disabled: true
|
|
98
|
+
|
|
99
|
+
- id: tool-skill
|
|
100
|
+
name: '@deepseek-ai/dsh-tool-skill'
|
|
101
|
+
disabled: true
|
|
102
|
+
|
|
103
|
+
# ── goals ───────────────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
# Only the model-facing tool. The goal SERVICE, its session driver, and the
|
|
106
|
+
# `/goal` command stay on the host plane: the Gateway serves the goal domain as
|
|
107
|
+
# Remote endpoints whose receiver comes from a generated descriptor, so it
|
|
108
|
+
# resolves `goals` on the host and an entry-local realm here would hide it. The
|
|
109
|
+
# registry is keyed by session anyway, so one host instance serves every
|
|
110
|
+
# session. What a preset chooses is whether its agent can call the goal tool.
|
|
111
|
+
- id: tool-goal
|
|
112
|
+
name: '@deepseek-ai/dsh-tool-goal'
|
|
113
|
+
|
|
114
|
+
# ── plan mode ───────────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
# Plan state is per-agent by nature, so an entry-local realm is not a
|
|
117
|
+
# workaround here — it is the correct lifetime.
|
|
118
|
+
- id: planning
|
|
119
|
+
name: cordis:group
|
|
120
|
+
group: true
|
|
121
|
+
isolate:
|
|
122
|
+
planMode: true
|
|
123
|
+
config:
|
|
124
|
+
- id: plan-mode
|
|
125
|
+
name: '@deepseek-ai/dsh-plan-mode'
|
|
126
|
+
config:
|
|
127
|
+
section: |
|
|
128
|
+
You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
|
|
129
|
+
|
|
130
|
+
Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
|
|
131
|
+
|
|
132
|
+
The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
|
|
133
|
+
|
|
134
|
+
Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
|
|
135
|
+
|
|
136
|
+
Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
|
|
137
|
+
|
|
138
|
+
When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
|
|
139
|
+
|
|
140
|
+
# ── compaction ──────────────────────────────────────────────────────────────
|
|
141
|
+
|
|
142
|
+
# `compaction-basic` reads `toolResultPrune` through `ctx.get`, so the pruner must
|
|
143
|
+
# share this realm rather than sit outside it.
|
|
144
|
+
#
|
|
145
|
+
# `tokenMeter` is deliberately NOT in this realm: the meter stays on the HOST
|
|
146
|
+
# plane, and the rows here resolve that one instance. It takes no configuration,
|
|
147
|
+
# keys every fold by Session, and owns the context-meter projection units the
|
|
148
|
+
# browser reads for every session — behind a realm those units would come and go
|
|
149
|
+
# with whichever presets happen to be mounted. What a preset chooses is whether
|
|
150
|
+
# its agent compacts at all, which is `compaction-basic` below.
|
|
151
|
+
- id: compaction
|
|
152
|
+
name: cordis:group
|
|
153
|
+
group: true
|
|
154
|
+
isolate:
|
|
155
|
+
compaction: true
|
|
156
|
+
toolResultPruner: true
|
|
157
|
+
config:
|
|
158
|
+
- id: compaction-basic
|
|
159
|
+
name: '@deepseek-ai/dsh-compaction-basic'
|
|
160
|
+
|
|
161
|
+
- id: command-compact
|
|
162
|
+
name: '@deepseek-ai/dsh-command-compact'
|
|
163
|
+
|
|
164
|
+
- id: tool-result-pruner
|
|
165
|
+
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
166
|
+
config:
|
|
167
|
+
thresholdChars: 8192
|
|
168
|
+
headChars: 4096
|
|
169
|
+
tailChars: 1024
|
|
170
|
+
|
|
171
|
+
# ── delegation and workflows ────────────────────────────────────────────────
|
|
172
|
+
|
|
173
|
+
# The `subagents` registry and its spawn/fork backends live in the HOST
|
|
174
|
+
# composition: the registry is a process singleton whose cross-session queries
|
|
175
|
+
# the api-proxy serves to the browser, and a provider name may only be
|
|
176
|
+
# registered once. This preset contributes the delegation TOOLS, which resolve
|
|
177
|
+
# that host registry.
|
|
178
|
+
#
|
|
179
|
+
# `workflows` is different — nothing outside an agent reads it — so every row
|
|
180
|
+
# that reaches it shares one entry-local realm here, and a consumer left
|
|
181
|
+
# outside would resolve a host registry this preset does not populate.
|
|
182
|
+
#
|
|
183
|
+
# `tool-subagent-report` is host-plane for the same reason as the registry,
|
|
184
|
+
# not because a preset may not want it: it registers a CONTINUABLE SETUP on
|
|
185
|
+
# that singleton rather than a tool this agent calls, and the setup list is
|
|
186
|
+
# not scope-aware — one copy per mounted preset means every child gets
|
|
187
|
+
# `report` registered once per live session, which throws on the second.
|
|
188
|
+
- id: delegation
|
|
189
|
+
name: cordis:group
|
|
190
|
+
group: true
|
|
191
|
+
isolate:
|
|
192
|
+
workflowEngine: true
|
|
193
|
+
config:
|
|
194
|
+
- id: tool-subagent-control
|
|
195
|
+
name: '@deepseek-ai/dsh-tool-subagent-control'
|
|
196
|
+
|
|
197
|
+
- id: tool-subagent-list-agents
|
|
198
|
+
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
|
|
199
|
+
|
|
200
|
+
- id: tool-subagent
|
|
201
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
202
|
+
config:
|
|
203
|
+
provider: spawn
|
|
204
|
+
toolName: subagent
|
|
205
|
+
backgroundMode: continuable
|
|
206
|
+
|
|
207
|
+
- id: tool-subagent-fork
|
|
208
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
209
|
+
config:
|
|
210
|
+
provider: fork
|
|
211
|
+
toolName: subagent_fork
|
|
212
|
+
backgroundMode: continuable
|
|
213
|
+
|
|
214
|
+
# Production dsh does not install these optional providers. Install the
|
|
215
|
+
# matching Bundle in this Profile and restart the Host, then copy this
|
|
216
|
+
# preset and remove `disabled` from the matching tool row. Host availability
|
|
217
|
+
# alone grants no tool.
|
|
218
|
+
- id: tool-subagent-codex
|
|
219
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
220
|
+
disabled: true
|
|
221
|
+
config:
|
|
222
|
+
provider: codex
|
|
223
|
+
toolName: subagent_codex
|
|
224
|
+
backgroundMode: one-shot
|
|
225
|
+
maxDepth: provider-managed
|
|
226
|
+
|
|
227
|
+
- id: tool-subagent-claude-code
|
|
228
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
229
|
+
disabled: true
|
|
230
|
+
config:
|
|
231
|
+
provider: claude-code
|
|
232
|
+
toolName: subagent_claude_code
|
|
233
|
+
backgroundMode: one-shot
|
|
234
|
+
maxDepth: provider-managed
|
|
235
|
+
|
|
236
|
+
# dsh 0.1.6-alpha.1: `dsh-workflow-worker-thread` is gone from the release;
|
|
237
|
+
# the workflow engine is now `dsh-workflow-ptc` (sandboxed Node PTC runtime,
|
|
238
|
+
# host row `ptc-runtime` → `dsh-ptc-runtime-node`). Preset health check
|
|
239
|
+
# (`unresolvableRows`) rejects any started row whose package cannot be
|
|
240
|
+
# resolved, so the old name marks the WHOLE preset broken — unselectable in
|
|
241
|
+
# the roster. Keep this in sync with the shipped `standard` preset.
|
|
242
|
+
- id: workflow-ptc
|
|
243
|
+
name: '@deepseek-ai/dsh-workflow-ptc'
|
|
244
|
+
config:
|
|
245
|
+
provider: spawn
|
|
246
|
+
|
|
247
|
+
- id: tool-workflow
|
|
248
|
+
name: '@deepseek-ai/dsh-tool-workflow'
|
|
249
|
+
|
|
250
|
+
- id: tool-ralph
|
|
251
|
+
name: '@deepseek-ai/dsh-tool-ralph'
|
|
252
|
+
config:
|
|
253
|
+
subagentProvider: spawn
|
|
254
|
+
maxRounds: 64
|
|
255
|
+
|
|
256
|
+
# ── remaining model-facing rows ─────────────────────────────────────────────
|
|
257
|
+
|
|
258
|
+
- id: tool-ask-user
|
|
259
|
+
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
260
|
+
|
|
261
|
+
- id: tool-todo
|
|
262
|
+
name: '@deepseek-ai/dsh-tool-todo'
|
|
263
|
+
config:
|
|
264
|
+
allowParallelInProgress: true
|
|
265
|
+
|
|
266
|
+
# The `web` service and its search provider stay in the host composition; only
|
|
267
|
+
# the model-facing tool is per-session.
|
|
268
|
+
- id: tool-web
|
|
269
|
+
name: '@deepseek-ai/dsh-tool-web'
|
|
270
|
+
config:
|
|
271
|
+
fetch: false
|
|
272
|
+
searchTimeoutMs: 60000
|
|
273
|
+
|
|
274
|
+
# ── CC 角色卡工坊(CC 模式专用) ───────────────────────────────────
|
|
275
|
+
- id: cc-agent
|
|
276
|
+
name: '@xia-sc/dsh-cc-studio/agent'
|
|
277
|
+
|
|
278
|
+
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN"><head><meta charset="utf-8"/><meta name="viewport" content="width=device-width,initial-scale=1"/><title>dsh-cc-studio 原型总览</title>
|
|
3
|
+
<style>body{margin:0;font:14px/1.6 system-ui;background:#0b0d11;color:#e6e8ee} a{color:#7c5cff} .wrap{max-width:900px;margin:40px auto;padding:0 16px} .card{background:#171a21;border:1px solid #262c3a;border-radius:14px;padding:16px;margin-bottom:16px} .btn{display:inline-block;padding:10px 14px;border-radius:10px;background:#7c5cff;color:white;text-decoration:none;font-weight:700;margin-right:8px} .btn.ghost{background:transparent;border:1px solid #262c3a;color:#e6e8ee} code{background:#0f1218;border:1px solid #262c3a;padding:2px 6px;border-radius:6px}</style></head>
|
|
4
|
+
<body><div class="wrap">
|
|
5
|
+
<h1>🎭 dsh-cc-studio · 原型对比</h1>
|
|
6
|
+
<p style="color:#9aa3b8">Q1 的 A/B 两个可交互 H5 已生成,直接点开看效果(本地文件即可,或用 <code>file://</code> 打开)。Q2=B(五维深挖)、Q3=dsh-cc-studio、Q4=首版仅 JSON 已锁定。</p>
|
|
7
|
+
<div class="card"><h3 style="margin:0 0 8px">原型 A — 轻量浮层</h3><p style="color:#9aa3b8">平时只占输入框上方一行胶囊,点开全屏工坊。适合沉浸式一次性做完。</p><a class="btn" href="./prototype-a.html" target="_blank">打开原型 A</a><span style="color:#9aa3b8">文件:<code>E:\dsh\plugin\dsh-cc-studio\prototypes\prototype-a.html</code></span></div>
|
|
8
|
+
<div class="card"><h3 style="margin:0 0 8px">原型 B — 侧边常驻 + 浮层双形态</h3><p style="color:#9aa3b8">左侧 320px 常驻工坊(进度/5维快览/世界书快切/校验),中间照常聊天,右侧实时 JSON + 小助手。浮层仍保留用于沉浸式编辑。适合边聊边改。</p><a class="btn" href="./prototype-b.html" target="_blank">打开原型 B</a><span style="color:#9aa3b8">文件:<code>E:\dsh\plugin\dsh-cc-studio\prototypes\prototype-b.html</code></span></div>
|
|
9
|
+
<div class="card"><h3>下一步</h3><ol><li>你在这两个页面里点一点,感受一下 A 的“干净” vs B 的“边聊边改”。</li><li>回复一句 <b>“定 A”</b> 或 <b>“定 B”</b> 或 <b>“都要 / 融合”</b>,我立刻按定版开写 <code>dsh-cc-studio</code> 真插件(<code>package.json/cordis.patch.yml/lib/index.js/lib/client.js</code>),首版先跑通 JSON 导出。</li></ol></div>
|
|
10
|
+
<div class="card" style="background:#0f1218"><h4 style="margin:0 0 8px">已锁定的实现基线</h4><ul style="margin:0;color:#cbd5f0"><li>5维问卷:年表 / 势力 / 地理 / 力量体系 / 日常细节 → 一键生成带 <code>@@position/@@depth/@@activate</code> 的 Lorebook</li><li>CCv3 全字段表单 + Lorebook Decorator 可视化 + CBS 自动补全 + 实时校验</li><li>Host: <code>ctx.llm.stream</code> 透传 + <code>fs</code> 校验;Client: <code>shell.overlay + conversation.input.dock + settings.section</code></li><li>首版导出:<code>card.json</code>(<code>spec: chara_card_v3</code>),CHARX/PNG 二期</li></ul></div>
|
|
11
|
+
</div></body></html>
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="zh-CN">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8"/>
|
|
5
|
+
<meta name="viewport" content="width=device-width,initial-scale=1"/>
|
|
6
|
+
<title>原型A · 轻量浮层工坊 — dsh-cc-studio</title>
|
|
7
|
+
<style>
|
|
8
|
+
:root{--bg:#0f1115;--panel:#171a21;--panel2:#1e232e;--line:#262c3a;--text:#e6e8ee;--muted:#9aa3b8;--acc:#7c5cff;--acc2:#2ee6a6;--warn:#ff6b6b;--radius:14px}
|
|
9
|
+
*{box-sizing:border-box} html,body{height:100%} body{margin:0;background:#0b0d11;color:var(--text);font:14px/1.6 ui-sans,system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial}
|
|
10
|
+
a{color:var(--acc)}
|
|
11
|
+
.topbar{height:44px;display:flex;align-items:center;justify-content:space-between;padding:0 14px;border-bottom:1px solid var(--line);background:#0f1218;position:sticky;top:0;z-index:5}
|
|
12
|
+
.topbar .brand{display:flex;gap:10px;align-items:center;font-weight:700;letter-spacing:.2px}
|
|
13
|
+
.pill{padding:4px 8px;border-radius:999px;background:#1a1f2e;border:1px solid #2a3350;color:#aeb8d4;font-size:12px}
|
|
14
|
+
.shell{display:grid;grid-template-columns:280px 1fr;min-height:calc(100vh - 44px)}
|
|
15
|
+
.sidebar{border-right:1px solid var(--line);background:#0f1218;padding:14px;display:flex;flex-direction:column;gap:14px}
|
|
16
|
+
.side-card{background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:12px}
|
|
17
|
+
.side-card h4{margin:0 0 8px;font-size:12px;letter-spacing:.08em;color:var(--muted);text-transform:uppercase}
|
|
18
|
+
.conv{padding:16px;display:flex;flex-direction:column;gap:16px;background: radial-gradient(1200px 600px at 30% -10%, #1a1f3a 0, transparent 60%), linear-gradient(180deg,#0d0f14,#0b0d11)}
|
|
19
|
+
.msg{max-width:760px;background:var(--panel);border:1px solid var(--line);border-radius:14px;padding:14px 16px}
|
|
20
|
+
.msg.user{margin-left:auto;background:#1a1d27;border-color:#2a3147}
|
|
21
|
+
.msg .role{font-size:11px;color:var(--muted);margin-bottom:6px;letter-spacing:.06em}
|
|
22
|
+
.composer{position:sticky;bottom:0;background:linear-gradient(180deg, transparent, #0b0d11 28%);padding:12px 0 16px}
|
|
23
|
+
.dock{max-width:760px;margin:0 auto 8px;display:flex;gap:8px;align-items:center}
|
|
24
|
+
.capsule{appearance:none;border:1px solid #2d3655;background:#171c2e;color:#c8d1ec;border-radius:999px;padding:6px 12px;font-size:12px;display:inline-flex;gap:8px;align-items:center;cursor:pointer;transition:.15s}
|
|
25
|
+
.capsule:hover{border-color:#3a4670;background:#1c2240;transform:translateY(-1px)}
|
|
26
|
+
.capsule .dot{width:7px;height:7px;border-radius:50%;background:var(--acc2);box-shadow:0 0 10px var(--acc2)}
|
|
27
|
+
.inputbar{max-width:760px;margin:0 auto;background:var(--panel);border:1px solid var(--line);border-radius:16px;padding:10px;display:flex;gap:10px;align-items:flex-end}
|
|
28
|
+
.inputbar textarea{flex:1;min-height:44px;max-height:120px;resize:none;background:#0f1218;border:1px solid #242b3d;border-radius:10px;padding:10px 12px;color:var(--text);outline:none}
|
|
29
|
+
.btn{appearance:none;border:1px solid transparent;background:var(--acc);color:white;border-radius:10px;padding:8px 14px;font-weight:600;cursor:pointer}
|
|
30
|
+
.btn.ghost{background:transparent;border-color:var(--line);color:var(--text)}
|
|
31
|
+
.btn.small{padding:6px 10px;font-size:12px;border-radius:8px}
|
|
32
|
+
/* overlay */
|
|
33
|
+
.overlay{position:fixed;inset:0;background:rgba(3,6,12,.62);backdrop-filter: blur(8px);display:none;align-items:center;justify-content:center;padding:18px;z-index:30}
|
|
34
|
+
.overlay.open{display:flex}
|
|
35
|
+
.workshop{width:min(1280px, 96vw);height:min(860px, 92vh);background:linear-gradient(180deg,#171c2a,#11141d);border:1px solid #2a324b;border-radius:18px;overflow:hidden;display:flex;flex-direction:column;box-shadow:0 20px 80px rgba(0,0,0,.55)}
|
|
36
|
+
.ws-head{height:56px;display:flex;align-items:center;justify-content:space-between;padding:0 16px;border-bottom:1px solid #242d47;background:#131827}
|
|
37
|
+
.steps{display:flex;gap:6px;align-items:center}
|
|
38
|
+
.step{padding:6px 10px;border-radius:999px;font-size:12px;border:1px solid #2a334e;color:var(--muted);background:#0f1422}
|
|
39
|
+
.step.active{background:var(--acc);border-color:var(--acc);color:white;box-shadow:0 6px 16px rgba(124,92,255,.35)}
|
|
40
|
+
.ws-body{flex:1;display:grid;grid-template-columns:220px 1fr 360px;min-height:0}
|
|
41
|
+
.ws-nav{border-right:1px solid #242d47;background:#0f1320;padding:12px;display:flex;flex-direction:column;gap:10px}
|
|
42
|
+
.ws-nav button{width:100%;text-align:left;padding:10px 12px;border-radius:10px;border:1px solid transparent;background:transparent;color:#cbd5f0;cursor:pointer}
|
|
43
|
+
.ws-nav button.active{background:#1a2140;border-color:#2d3a66;color:white}
|
|
44
|
+
.ws-nav button small{display:block;color:var(--muted);font-size:11px;margin-top:2px}
|
|
45
|
+
.ws-main{padding:16px;overflow:auto}
|
|
46
|
+
.ws-side{border-left:1px solid #242d47;background:#0e1220;padding:12px;overflow:auto}
|
|
47
|
+
.card{background:#0f1422;border:1px solid #242d47;border-radius:12px;padding:12px;margin-bottom:12px}
|
|
48
|
+
.card h3{margin:0 0 8px;font-size:13px}
|
|
49
|
+
.field{display:flex;flex-direction:column;gap:6px;margin-bottom:10px}
|
|
50
|
+
.field label{font-size:12px;color:var(--muted)}
|
|
51
|
+
.field input,.field textarea,.field select{width:100%;background:#0b0f1b;border:1px solid #26314d;border-radius:8px;color:var(--text);padding:8px 10px;outline:none}
|
|
52
|
+
.field textarea{min-height:84px;resize:vertical}
|
|
53
|
+
.chips{display:flex;flex-wrap:wrap;gap:6px}
|
|
54
|
+
.chip{font-size:11px;padding:4px 8px;border-radius:999px;background:#12182a;border:1px solid #28365a;color:#aebde0;cursor:pointer}
|
|
55
|
+
.chip.on{background:var(--acc);border-color:var(--acc);color:white}
|
|
56
|
+
.lore-row{display:grid;grid-template-columns:1fr auto;gap:8px;align-items:center;background:#0b0f1b;border:1px solid #26314d;border-radius:10px;padding:10px;margin-bottom:8px}
|
|
57
|
+
.mono{font-family:ui-monospace,Menlo,Consolas,monospace;font-size:12px}
|
|
58
|
+
.json{white-space:pre-wrap;word-break:break-all;background:#0b0f1b;border:1px solid #26314d;border-radius:10px;padding:10px;max-height:420px;overflow:auto}
|
|
59
|
+
.badge{font-size:11px;padding:2px 6px;border-radius:999px;border:1px solid #2a3350;background:#11182e;color:#9fb0d8}
|
|
60
|
+
.dim-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
|
|
61
|
+
.dim{border:1px dashed #2a3552;border-radius:12px;padding:10px;background:#0c1020}
|
|
62
|
+
.dim h4{margin:0 0 6px;font-size:12px;color:#cbd5f0}
|
|
63
|
+
.kpi{display:flex;gap:8px;flex-wrap:wrap}
|
|
64
|
+
@media (max-width:1100px){.ws-body{grid-template-columns:1fr}.ws-side{border-left:none;border-top:1px solid #242d47}.shell{grid-template-columns:1fr}.sidebar{display:none}}
|
|
65
|
+
</style>
|
|
66
|
+
</head>
|
|
67
|
+
<body>
|
|
68
|
+
<div class="topbar">
|
|
69
|
+
<div class="brand">⬢ DSH <span style="color:var(--muted);font-weight:400">· web — 会话内角色卡工坊</span></div>
|
|
70
|
+
<div style="display:flex;gap:8px;align-items:center"><span class="pill">CCv3 Living Standard</span><span class="pill" style="background:#1a2a1f;border-color:#2a4a3a;color:#a6e8c8">● 模型就绪</span></div>
|
|
71
|
+
</div>
|
|
72
|
+
<div class="shell">
|
|
73
|
+
<div class="sidebar">
|
|
74
|
+
<div class="side-card"><h4>工作区</h4><div class="mono" style="color:#cbd5f0">E:\demo\my-cards</div><div style="color:var(--muted);font-size:12px;margin-top:6px">会话 cwd 实时跟随(dsh-git 同款 store)</div></div>
|
|
75
|
+
<div class="side-card"><h4>最近草稿</h4><div style="display:flex;flex-direction:column;gap:8px"><div style="padding:8px 10px;border:1px solid #26314d;border-radius:10px;background:#0b0f1b"><div style="font-weight:600">星港遗民 · 琉璃</div><div style="color:var(--muted);font-size:12px">未导出 · 5 lorebook</div></div><div style="padding:8px 10px;border:1px dashed #26314d;border-radius:10px;color:var(--muted)">+ 新建角色卡</div></div></div>
|
|
76
|
+
<div class="side-card"><h4>原型说明</h4><div style="color:var(--muted);font-size:12px">A = 轻量浮层:平时只占输入框上方一行胶囊,点开全屏工坊。适合“写完再导”的沉浸式创作。</div></div>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="conv">
|
|
79
|
+
<div class="msg"><div class="role">ASSISTANT</div>试着丢一句点子给我,比如“废土上开面馆的仿生人奶奶,会修收音机”。我帮你把世界观补齐到可导出的 CCv3。</div>
|
|
80
|
+
<div class="msg user"><div class="role">YOU</div>我想做一个雨城里的记忆典当行老板娘,能把记忆做成香薰蜡烛,客人闻到就能重温那段记忆。但我世界观很薄…</div>
|
|
81
|
+
<div class="composer">
|
|
82
|
+
<div class="dock">
|
|
83
|
+
<button class="capsule" id="openA">🎭 角色卡工坊 <span class="dot"></span> 未导出 · 点此展开</button>
|
|
84
|
+
<span class="badge">CCv3 校验:<span style="color:var(--warn)">缺 nickname / group_only_greetings</span></span>
|
|
85
|
+
<span class="badge">草稿自动保存 ✓</span>
|
|
86
|
+
</div>
|
|
87
|
+
<div class="inputbar">
|
|
88
|
+
<textarea placeholder="继续聊点子,或直接在工坊里编辑… Shift+Enter 换行"></textarea>
|
|
89
|
+
<button class="btn">发送</button>
|
|
90
|
+
</div>
|
|
91
|
+
<div style="max-width:760px;margin:8px auto 0;color:var(--muted);font-size:12px">按回车发送 · 工坊里每个 “AI 扩写” 都会走 DSH 的 llm.stream,失败可重试</div>
|
|
92
|
+
</div>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
|
|
96
|
+
<div class="overlay" id="overlayA">
|
|
97
|
+
<div class="workshop" role="dialog" aria-modal="true">
|
|
98
|
+
<div class="ws-head">
|
|
99
|
+
<div style="display:flex;gap:12px;align-items:center"><strong>🎭 角色卡工坊</strong><span class="badge">dsh-cc-studio · A 浮层</span>
|
|
100
|
+
<div class="steps" id="stepsA"></div>
|
|
101
|
+
</div>
|
|
102
|
+
<div style="display:flex;gap:8px"><button class="btn ghost small" onclick="closeA()">收起</button><button class="btn small" onclick="exportJSON()">导出 JSON</button></div>
|
|
103
|
+
</div>
|
|
104
|
+
<div class="ws-body">
|
|
105
|
+
<div class="ws-nav" id="navA"></div>
|
|
106
|
+
<div class="ws-main" id="mainA"></div>
|
|
107
|
+
<div class="ws-side">
|
|
108
|
+
<div class="card"><h3>实时 CCv3 预览</h3><div class="kpi" style="margin-bottom:8px"><span class="badge">spec: chara_card_v3</span><span class="badge">校验: <span id="validA" style="color:var(--acc2)">通过</span></span></div><pre class="json mono" id="jsonA"></pre></div>
|
|
109
|
+
<div class="card"><h3>CBS 速查</h3><div class="chips"><span class="chip">{{char}}</span><span class="chip">{{user}}</span><span class="chip">{{random:A,B}}</span><span class="chip">{{roll:d6}}</span><span class="chip">{{// 注释}}</span></div><div style="color:var(--muted);font-size:12px;margin-top:8px">输入时自动补全,导出前会做大小写不敏感校验。</div></div>
|
|
110
|
+
</div>
|
|
111
|
+
</div>
|
|
112
|
+
</div>
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
<script>
|
|
116
|
+
const steps = ["点子","世界观 5维","角色细化","世界书","校验导出"];
|
|
117
|
+
let cur = 0;
|
|
118
|
+
const draft = {
|
|
119
|
+
spec:"chara_card_v3", spec_version:"3.0",
|
|
120
|
+
data:{
|
|
121
|
+
name:"雨城烛坊 · 岚烟", nickname:"岚烟", tags:["记忆","雨城","香薰","典当"], creator:"you", character_version:"0.1",
|
|
122
|
+
description:"雨城常年细雨,霓虹在水膜上化开。岚烟经营一家无招牌的记忆典当行,将客人的记忆抽丝封进蜡烛。",
|
|
123
|
+
personality:"外柔内韧,嗅觉共情,话少但记性极好;对“自愿”有执念,拒绝强行抽取。",
|
|
124
|
+
scenario:"你推开吱呀的木门,铃铛轻响,雨气混着蜡与旧纸味扑面而来。岚烟在柜台后抬头,手中烛芯正袅袅成线。",
|
|
125
|
+
system_prompt:"You are 岚烟, keeper of memory candles. Reply in Chinese, sensory, restrained romance.",
|
|
126
|
+
post_history_instructions:"保持蜡烛=记忆的隐喻一致性,不主动破坏客人自愿原则。",
|
|
127
|
+
first_mes:"“雨又大了。先烤烤手吧——想典当哪一段?” 她把一盏未点的乳白蜡烛推到你面前,烛体里有细细的银线游动。",
|
|
128
|
+
alternate_greetings:["“今晚的雨适合想起旧人。你带记忆来了吗?”","“这盏是上周一位航行员留下的,里面有海风。”"],
|
|
129
|
+
group_only_greetings:["(群聊限定)“诸位若同时点烛,记忆会串味——我得一盏一盏来。”"],
|
|
130
|
+
mes_example:"{{user}}: 能把我5岁那年的夏夜还给我吗?\n{{char}}: 可以,但那段有蝉鸣和西瓜味,烧起来会很吵。你确定要现在闻吗?",
|
|
131
|
+
creator_notes:"雨城世界观薄弱用户的示例草稿", creator_notes_multilingual:{en:"Example draft for thin-worldbuilding user"}, source:[], assets:[{type:"icon",uri:"ccdefault:",name:"main",ext:"png"}], creation_date: Math.floor(Date.now()/1000), modification_date: Math.floor(Date.now()/1000),
|
|
132
|
+
character_book:{ name:"雨城烛坊 lorebook", description:"随卡私有世界书", scan_depth:4, token_budget:1200, recursive_scanning:false, extensions:{}, entries:[
|
|
133
|
+
{enabled:true, insertion_order:0, use_regex:false, constant:false, name:"雨城常雨", keys:["雨城","霓虹","潮气"], content:"@@position after_desc\n雨城年均降水 1800mm,排水系统以“雨梯”著称,霓虹倒影是导航。", priority:10},
|
|
134
|
+
{enabled:true, insertion_order:1, use_regex:false, constant:true, name:"蜡烛=记忆", keys:[], content:"@@activate\n蜡烛即记忆容器,银线密度=情绪强度。闻香即重温,不可食用。", priority:9},
|
|
135
|
+
]}
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
let idea="雨城里的记忆典当行老板娘,能把记忆做成香薰蜡烛";
|
|
139
|
+
let world={timeline:"2127 雨城独立后,记忆被列为可交易软资产", factions:"烛坊/雨梯管理局/盗忆人", geo:"环形雨城,内环干燥外环常雨", power:"抽忆术需自愿+嗅觉天赋", daily:"雨声电台、蜡烛夜市、记忆税"};
|
|
140
|
+
|
|
141
|
+
function renderSteps(){
|
|
142
|
+
const el=document.getElementById('stepsA');
|
|
143
|
+
el.innerHTML=steps.map((s,i)=>`<span class="step ${i===cur?'active':''}" onclick="cur=${i};renderA()">${i+1}. ${s}</span>`).join("");
|
|
144
|
+
}
|
|
145
|
+
function renderNav(){
|
|
146
|
+
const nav=document.getElementById('navA');
|
|
147
|
+
const items=[
|
|
148
|
+
{t:"点子投喂",d:"一句话点子 + 风格"},
|
|
149
|
+
{t:"世界观 5维",d:"年表/势力/地理/力量/日常"},
|
|
150
|
+
{t:"角色细化",d:"六件套 + 开场"},
|
|
151
|
+
{t:"世界书",d:"Lorebook + Decorator"},
|
|
152
|
+
{t:"校验导出",d:"JSON 预览与下载"},
|
|
153
|
+
];
|
|
154
|
+
nav.innerHTML=items.map((x,i)=>`<button class="${i===cur?'active':''}" onclick="cur=${i};renderA()">${x.t}<small>${x.d}</small></button>`).join("") +
|
|
155
|
+
`<div style="margin-top:auto;padding-top:12px;border-top:1px solid #242d47;display:flex;gap:8px"><button class="btn ghost small" style="flex:1" onclick="autoFill()">AI 扩写</button><button class="btn small" style="flex:1" onclick="validateA()">校验</button></div>`;
|
|
156
|
+
}
|
|
157
|
+
function renderA(){
|
|
158
|
+
renderSteps(); renderNav();
|
|
159
|
+
const m=document.getElementById('mainA');
|
|
160
|
+
if(cur===0){
|
|
161
|
+
m.innerHTML=`
|
|
162
|
+
<div class="card"><h3>点子投喂(薄世界观友好)</h3>
|
|
163
|
+
<div class="field"><label>一句话点子</label><textarea id="idea">${idea}</textarea></div>
|
|
164
|
+
<div class="field"><label>风格标签</label><div class="chips" id="tags"><span class="chip on">雨城</span><span class="chip on">感官系</span><span class="chip">赛博</span><span class="chip">治愈</span><span class="chip">微克苏鲁</span></div></div>
|
|
165
|
+
<div style="display:flex;gap:8px"><button class="btn small" onclick="expandIdea()">✦ AI 扩写成设定</button><button class="btn ghost small" onclick="cur=1;renderA()">跳过→世界观</button></div>
|
|
166
|
+
<div style="margin-top:10px;color:var(--muted);font-size:12px">会生成 description/personality/scenario 初稿 + 5维大纲草案,可一键采纳。</div>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="card"><h3>AI 初稿(可直接采纳)</h3>
|
|
169
|
+
<div class="field"><label>description</label><textarea>${draft.data.description}</textarea></div>
|
|
170
|
+
<div class="field"><label>personality</label><textarea>${draft.data.personality}</textarea></div>
|
|
171
|
+
<div class="field"><label>scenario</label><textarea>${draft.data.scenario}</textarea></div>
|
|
172
|
+
</div>`;
|
|
173
|
+
} else if(cur===1){
|
|
174
|
+
m.innerHTML=`
|
|
175
|
+
<div class="card"><h3>世界观 5维(Q2-B 深度问卷)</h3><div style="color:var(--muted);font-size:12px;margin-bottom:10px">每维填 1-2 句即可,AI 会据此生成 Lorebook 条目(带 @@position/@@depth)。</div>
|
|
176
|
+
<div class="dim-grid">
|
|
177
|
+
<div class="dim"><h4>① 年表 Timeline</h4><textarea style="width:100%;min-height:70px;background:#0b0f1b;border:1px solid #26314d;border-radius:8px;color:var(--text);padding:8px">${world.timeline}</textarea></div>
|
|
178
|
+
<div class="dim"><h4>② 势力 Factions</h4><textarea style="width:100%;min-height:70px;background:#0b0f1b;border:1px solid #26314d;border-radius:8px;color:var(--text);padding:8px">${world.factions}</textarea></div>
|
|
179
|
+
<div class="dim"><h4>③ 地理 Geography</h4><textarea style="width:100%;min-height:70px;background:#0b0f1b;border:1px solid #26314d;border-radius:8px;color:var(--text);padding:8px">${world.geo}</textarea></div>
|
|
180
|
+
<div class="dim"><h4>④ 力量 Power</h4><textarea style="width:100%;min-height:70px;background:#0b0f1b;border:1px solid #26314d;border-radius:8px;color:var(--text);padding:8px">${world.power}</textarea></div>
|
|
181
|
+
<div class="dim" style="grid-column:span 2"><h4>⑤ 日常 Daily</h4><textarea style="width:100%;min-height:70px;background:#0b0f1b;border:1px solid #26314d;border-radius:8px;color:var(--text);padding:8px">${world.daily}</textarea></div>
|
|
182
|
+
</div>
|
|
183
|
+
<div style="display:flex;gap:8px;margin-top:12px"><button class="btn small" onclick="genLore()">✦ 5维→生成 6 条 Lorebook</button><button class="btn ghost small" onclick="cur=2;renderA()">下一步</button></div>
|
|
184
|
+
</div>`;
|
|
185
|
+
} else if(cur===2){
|
|
186
|
+
m.innerHTML=`
|
|
187
|
+
<div class="card"><h3>角色细化 — 六件套</h3>
|
|
188
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
|
189
|
+
<div class="field"><label>name</label><input value="${draft.data.name}"/></div>
|
|
190
|
+
<div class="field"><label>nickname ({{char}} 用)</label><input value="${draft.data.nickname}"/></div>
|
|
191
|
+
</div>
|
|
192
|
+
<div class="field"><label>tags</label><input value="${draft.data.tags.join(', ')}"/></div>
|
|
193
|
+
<div class="field"><label>description</label><textarea>${draft.data.description}</textarea></div>
|
|
194
|
+
<div class="field"><label>personality</label><textarea>${draft.data.personality}</textarea></div>
|
|
195
|
+
<div class="field"><label>scenario</label><textarea>${draft.data.scenario}</textarea></div>
|
|
196
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
|
|
197
|
+
<div class="field"><label>system_prompt</label><textarea>${draft.data.system_prompt}</textarea></div>
|
|
198
|
+
<div class="field"><label>post_history_instructions</label><textarea>${draft.data.post_history_instructions}</textarea></div>
|
|
199
|
+
</div>
|
|
200
|
+
</div>
|
|
201
|
+
<div class="card"><h3>开场与示例</h3>
|
|
202
|
+
<div class="field"><label>first_mes</label><textarea>${draft.data.first_mes}</textarea></div>
|
|
203
|
+
<div class="field"><label>alternate_greetings (每行一条)</label><textarea>${draft.data.alternate_greetings.join('\n')}</textarea></div>
|
|
204
|
+
<div class="field"><label>group_only_greetings *必填</label><textarea>${draft.data.group_only_greetings.join('\n')}</textarea></div>
|
|
205
|
+
<div class="field"><label>mes_example ({{user}}/{{char}})</label><textarea style="min-height:110px">${draft.data.mes_example}</textarea></div>
|
|
206
|
+
</div>`;
|
|
207
|
+
} else if(cur===3){
|
|
208
|
+
const entries=draft.data.character_book.entries.map((e,i)=>`
|
|
209
|
+
<div class="lore-row">
|
|
210
|
+
<div><div style="font-weight:600">${e.name} <span class="badge">${e.constant?'constant':e.keys.join(', ')||'—'}</span> <span class="badge">order ${e.insertion_order}</span></div>
|
|
211
|
+
<div class="mono" style="color:#aebde0;margin-top:4px;white-space:pre-wrap">${e.content}</div>
|
|
212
|
+
<div class="chips" style="margin-top:6px"><span class="chip">@@depth 2</span><span class="chip">@@role system</span><span class="chip">@@position after_desc</span></div></div>
|
|
213
|
+
<div style="display:flex;flex-direction:column;gap:6px"><label style="font-size:12px"><input type="checkbox" ${e.enabled?'checked':''}/> enabled</label><button class="btn ghost small">编辑</button></div>
|
|
214
|
+
</div>`).join("");
|
|
215
|
+
m.innerHTML=`
|
|
216
|
+
<div class="card"><h3>世界书 Lorebook</h3><div style="display:flex;gap:8px;margin-bottom:8px"><span class="badge">scan_depth: ${draft.data.character_book.scan_depth}</span><span class="badge">token_budget: ${draft.data.character_book.token_budget}</span><span class="badge">entries: ${draft.data.character_book.entries.length}</span><button class="btn small" style="margin-left:auto" onclick="addEntry()">+ 新增条目</button></div>${entries}
|
|
217
|
+
<div style="margin-top:10px;display:flex;gap:8px"><button class="btn small" onclick="genLore()">✦ 用 5维再生成一批</button><button class="btn ghost small">导入 Lorebook JSON</button></div>
|
|
218
|
+
</div>`;
|
|
219
|
+
} else {
|
|
220
|
+
m.innerHTML=`
|
|
221
|
+
<div class="card"><h3>校验与导出 — 首版仅 JSON(最稳)</h3>
|
|
222
|
+
<div class="kpi"><span class="badge" style="border-color:#2a5a3a;color:#a6e8c8">✓ spec/spec_version 正确</span><span class="badge">group_only_greetings: 1 条</span><span class="badge">assets: 1</span></div>
|
|
223
|
+
<div style="display:flex;gap:8px;margin:12px 0"><button class="btn small" onclick="exportJSON()">⬇ 导出 card.json</button><button class="btn ghost small" onclick="validateA()">重新校验</button><span style="color:var(--muted);font-size:12px;align-self:center">CHARX/PNG 二期再上</span></div>
|
|
224
|
+
<div class="field"><label>creator_notes / 多语言</label><textarea>${draft.data.creator_notes}</textarea></div>
|
|
225
|
+
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px"><div class="field"><label>creation_date</label><input value="${draft.data.creation_date}" readonly/></div><div class="field"><label>modification_date (导出自动刷新)</label><input value="${draft.data.modification_date}" readonly/></div></div>
|
|
226
|
+
</div>`;
|
|
227
|
+
}
|
|
228
|
+
document.getElementById('jsonA').textContent = JSON.stringify(draft, null, 2);
|
|
229
|
+
}
|
|
230
|
+
function expandIdea(){ idea=document.getElementById('idea')?.value||idea; draft.data.description="雨城的雨是活的,会在玻璃上写字。岚烟的烛坊藏在雨梯夹层,蜡烛银线随记忆温度变色。"; renderA(); }
|
|
231
|
+
function genLore(){ draft.data.character_book.entries.push({enabled:true, insertion_order: draft.data.character_book.entries.length, use_regex:false, constant:false, name:"雨梯管理局", keys:["雨梯","管理局"], content:"@@depth 4\n雨梯管理局负责分配干燥配额,烛坊因“记忆税”与其周旋。", priority:8}); renderA(); }
|
|
232
|
+
function addEntry(){ draft.data.character_book.entries.push({enabled:true, insertion_order: draft.data.character_book.entries.length, use_regex:false, constant:false, name:"新条目", keys:["关键词"], content:"@@position personality\n新 lore 内容…", priority:5}); renderA(); }
|
|
233
|
+
function validateA(){ document.getElementById('validA').textContent="通过"; document.getElementById('validA').style.color="var(--acc2)"; }
|
|
234
|
+
function autoFill(){ expandIdea(); }
|
|
235
|
+
function exportJSON(){
|
|
236
|
+
const blob=new Blob([JSON.stringify(draft,null,2)],{type:"application/json"});
|
|
237
|
+
const a=document.createElement('a'); a.href=URL.createObjectURL(blob); a.download="card.json"; a.click(); URL.revokeObjectURL(a.href);
|
|
238
|
+
}
|
|
239
|
+
function openA(){ document.getElementById('overlayA').classList.add('open'); renderA(); }
|
|
240
|
+
function closeA(){ document.getElementById('overlayA').classList.remove('open'); }
|
|
241
|
+
document.getElementById('openA').addEventListener('click', openA);
|
|
242
|
+
document.getElementById('overlayA').addEventListener('click', (e)=>{ if(e.target.id==='overlayA') closeA(); });
|
|
243
|
+
renderA();
|
|
244
|
+
</script>
|
|
245
|
+
</body>
|
|
246
|
+
</html>
|