接入 UI 文档引用代码上下文生成
Direct Codex 引用 ui-design-doc 时生成带文档的 JS 文件 生成成功或失败信息追加到当前 prompt 补充成功、失败与 ui-prototype 隔离测试
This commit is contained in:
@@ -1,4 +1,8 @@
|
||||
use super::*;
|
||||
use crate::ui_editor::persistence::{
|
||||
generate_ui_design_code_at, GenerateUiDesignCodeInput, UI_DESIGN_DOC_ASSET_KIND,
|
||||
UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
};
|
||||
|
||||
pub(crate) const MAX_DIRECT_CODEX_REFERENCES: usize = 32;
|
||||
const MAX_DIRECT_CODEX_REFERENCE_ID_CHARS: usize = 200;
|
||||
@@ -138,19 +142,30 @@ fn validate_resource_reference_id(value: &str) -> Result<String, String> {
|
||||
/// Keep this separate from the generic resource renderer so UI-specific
|
||||
/// instructions/metadata can evolve without changing other asset kinds.
|
||||
fn render_ui_design_reference_line(
|
||||
root: &Path,
|
||||
manifest: &GameCreationAppManifest,
|
||||
asset: &GameCreationAppAssetManifestEntry,
|
||||
resource_id: &str,
|
||||
label: &str,
|
||||
local_path: &str,
|
||||
source: &str,
|
||||
) -> String {
|
||||
let context = match generate_ui_design_code_at(GenerateUiDesignCodeInput {
|
||||
project_path: root.to_string_lossy().into_owned(),
|
||||
expected_project_id: manifest.project_id.clone(),
|
||||
asset_id: resource_id.to_string(),
|
||||
}) {
|
||||
Ok(result) => format!("请先阅读生成的带有文档的代码片段: {}", result.relative_path),
|
||||
Err(error) => format!("生成代码遇到错误{error}"),
|
||||
};
|
||||
format!(
|
||||
"- 素材 ID:{resource_id};名称:{label};类型:{};媒体类型:{};项目路径:{local_path};来源:{source}",
|
||||
asset.kind, asset.media_type
|
||||
)
|
||||
) + "\n" + &context
|
||||
}
|
||||
|
||||
fn render_resource_reference_line(
|
||||
root: &Path,
|
||||
manifest: &GameCreationAppManifest,
|
||||
reference: &DirectCodexResourceReference,
|
||||
) -> Result<String, String> {
|
||||
@@ -166,12 +181,12 @@ fn render_resource_reference_line(
|
||||
.unwrap_or_else(|| asset_display_label(asset));
|
||||
let source = sanitize_reference_source(reference.source.as_deref())
|
||||
.unwrap_or_else(|| "unknown".to_string());
|
||||
let is_ui_design = matches!(
|
||||
asset.kind.to_ascii_lowercase().as_str(),
|
||||
"ui" | "ui-design" | "ui-prototype"
|
||||
);
|
||||
let is_ui_design =
|
||||
asset.kind == UI_DESIGN_DOC_ASSET_KIND && asset.media_type == UI_DESIGN_DOC_MEDIA_TYPE;
|
||||
if is_ui_design {
|
||||
return Ok(render_ui_design_reference_line(
|
||||
root,
|
||||
manifest,
|
||||
asset,
|
||||
&resource_id,
|
||||
&label,
|
||||
@@ -264,7 +279,7 @@ pub(crate) fn render_direct_codex_references_section(
|
||||
for reference in references {
|
||||
lines.push(match reference {
|
||||
DirectCodexTurnReference::Resource(reference) => {
|
||||
render_resource_reference_line(&manifest, reference)?
|
||||
render_resource_reference_line(root, &manifest, reference)?
|
||||
}
|
||||
DirectCodexTurnReference::RuntimeRegion(reference) => {
|
||||
render_runtime_region_reference_line(&manifest, reference)?
|
||||
@@ -282,6 +297,7 @@ pub(crate) fn render_direct_codex_references_section(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ui_editor::persistence::initialize_ui_design_state_at;
|
||||
|
||||
fn fixture_project() -> tempfile::TempDir {
|
||||
let directory = tempfile::tempdir().expect("temp project");
|
||||
@@ -314,6 +330,128 @@ mod tests {
|
||||
directory
|
||||
}
|
||||
|
||||
fn add_ui_design_doc(project: &tempfile::TempDir) -> String {
|
||||
let root = project.path();
|
||||
let relative_path = "ui/design.json";
|
||||
let absolute_path = root.join(relative_path);
|
||||
std::fs::create_dir_all(absolute_path.parent().expect("ui parent"))
|
||||
.expect("create ui parent");
|
||||
std::fs::write(&absolute_path, b"").expect("create ui document");
|
||||
let asset = register_local_asset_at(
|
||||
root,
|
||||
relative_path,
|
||||
UI_DESIGN_DOC_ASSET_KIND,
|
||||
UI_DESIGN_DOC_MEDIA_TYPE,
|
||||
"test",
|
||||
GameCreationAppAssetSource {
|
||||
kind: GameCreationAppAssetSourceKind::Generated,
|
||||
canvas_project_id: None,
|
||||
resource_id: Some("ui:test".to_string()),
|
||||
asset_object_id: None,
|
||||
task_id: None,
|
||||
prompt: None,
|
||||
model: None,
|
||||
generation_route: None,
|
||||
generation_kind: None,
|
||||
reference_resource_ids: Vec::new(),
|
||||
},
|
||||
)
|
||||
.expect("register ui design doc");
|
||||
initialize_ui_design_state_at(root, "project-1", &asset.id)
|
||||
.expect("initialize ui design doc");
|
||||
asset.id
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ui_design_doc_reference_generates_documented_code_context() {
|
||||
let project = fixture_project();
|
||||
let asset_id = add_ui_design_doc(&project);
|
||||
let reference = DirectCodexTurnReference::Resource(DirectCodexResourceReference {
|
||||
resource_id: asset_id,
|
||||
label: Some("界面设计".to_string()),
|
||||
source: Some("asset-picker".to_string()),
|
||||
});
|
||||
let section = render_direct_codex_references_section(project.path(), &[reference])
|
||||
.expect("render")
|
||||
.expect("section");
|
||||
assert!(section.contains("请先阅读生成的带有文档的代码片段: ui/generated-"));
|
||||
let generated = std::fs::read_dir(project.path().join("ui"))
|
||||
.expect("read ui dir")
|
||||
.filter_map(Result::ok)
|
||||
.find(|entry| {
|
||||
entry
|
||||
.file_name()
|
||||
.to_string_lossy()
|
||||
.starts_with("generated-")
|
||||
})
|
||||
.expect("generated code");
|
||||
let code = std::fs::read_to_string(generated.path()).expect("read generated code");
|
||||
assert!(code.contains("这是从用户手动调整过的UI设计文档生成的html片段"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ui_design_doc_generation_errors_stay_in_prompt_and_do_not_abort_section() {
|
||||
let project = fixture_project();
|
||||
let asset_id = add_ui_design_doc(&project);
|
||||
let manifest = read_manifest_for_project(project.path()).expect("read manifest");
|
||||
let asset = manifest
|
||||
.assets
|
||||
.iter()
|
||||
.find(|asset| asset.id == asset_id)
|
||||
.unwrap();
|
||||
std::fs::write(project.path().join(&asset.local_path), b"not-json")
|
||||
.expect("corrupt ui document");
|
||||
let reference = DirectCodexTurnReference::Resource(DirectCodexResourceReference {
|
||||
resource_id: asset_id,
|
||||
label: None,
|
||||
source: None,
|
||||
});
|
||||
let section = render_direct_codex_references_section(project.path(), &[reference])
|
||||
.expect("render")
|
||||
.expect("section");
|
||||
assert!(section.contains("素材 ID:"));
|
||||
assert!(section.contains("生成代码遇到错误"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ui_prototype_image_does_not_trigger_code_generation() {
|
||||
let project = fixture_project();
|
||||
let root = project.path();
|
||||
let relative_path = "assets/ui-prototype.png";
|
||||
std::fs::create_dir_all(root.join("assets")).expect("assets dir");
|
||||
std::fs::write(root.join(relative_path), b"png").expect("prototype image");
|
||||
let asset = register_local_asset_at(
|
||||
root,
|
||||
relative_path,
|
||||
"ui-prototype",
|
||||
"image/png",
|
||||
"test",
|
||||
GameCreationAppAssetSource {
|
||||
kind: GameCreationAppAssetSourceKind::Uploaded,
|
||||
canvas_project_id: None,
|
||||
resource_id: None,
|
||||
asset_object_id: None,
|
||||
task_id: None,
|
||||
prompt: None,
|
||||
model: None,
|
||||
generation_route: None,
|
||||
generation_kind: None,
|
||||
reference_resource_ids: Vec::new(),
|
||||
},
|
||||
)
|
||||
.expect("register prototype");
|
||||
let reference = DirectCodexTurnReference::Resource(DirectCodexResourceReference {
|
||||
resource_id: asset.id,
|
||||
label: None,
|
||||
source: None,
|
||||
});
|
||||
let section = render_direct_codex_references_section(root, &[reference])
|
||||
.expect("render")
|
||||
.expect("section");
|
||||
assert!(!section.contains("请先阅读生成的带有文档的代码片段"));
|
||||
assert!(!root.join("ui").exists());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resource_reference_uses_manifest_identity_and_never_accepts_client_paths() {
|
||||
let project = fixture_project();
|
||||
|
||||
Reference in New Issue
Block a user