@worca/ui 0.47.0 → 0.48.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/app/main.bundle.js +1495 -1420
- package/app/main.bundle.js.map +4 -4
- package/app/styles.css +80 -0
- package/package.json +1 -1
- package/server/templates-routes.js +70 -0
package/app/styles.css
CHANGED
|
@@ -3647,6 +3647,86 @@ sl-option.template-grouped:focus::part(suffix) {
|
|
|
3647
3647
|
line-height: 1.5;
|
|
3648
3648
|
}
|
|
3649
3649
|
|
|
3650
|
+
/* Pipeline-template header row: label on the left, Suggest button on the right. */
|
|
3651
|
+
.template-select-header {
|
|
3652
|
+
display: flex;
|
|
3653
|
+
align-items: center;
|
|
3654
|
+
justify-content: space-between;
|
|
3655
|
+
gap: 8px;
|
|
3656
|
+
margin-bottom: 4px;
|
|
3657
|
+
}
|
|
3658
|
+
|
|
3659
|
+
.template-select-header .settings-label {
|
|
3660
|
+
margin-bottom: 0;
|
|
3661
|
+
}
|
|
3662
|
+
|
|
3663
|
+
.btn-suggest-template::part(base) {
|
|
3664
|
+
font-size: 12px;
|
|
3665
|
+
}
|
|
3666
|
+
|
|
3667
|
+
/* Center the Sparkles SVG with the label. Without this, the SVG inherits
|
|
3668
|
+
* baseline alignment from inline-flow and sits ~2px below the cap line. */
|
|
3669
|
+
.btn-suggest-template::part(prefix) {
|
|
3670
|
+
display: inline-flex;
|
|
3671
|
+
align-items: center;
|
|
3672
|
+
}
|
|
3673
|
+
|
|
3674
|
+
.btn-suggest-template svg {
|
|
3675
|
+
display: block;
|
|
3676
|
+
}
|
|
3677
|
+
|
|
3678
|
+
/* Advisor dialog */
|
|
3679
|
+
.advisor-dialog::part(panel) {
|
|
3680
|
+
max-width: 560px;
|
|
3681
|
+
}
|
|
3682
|
+
|
|
3683
|
+
.advisor-dialog-body {
|
|
3684
|
+
font-size: 14px;
|
|
3685
|
+
color: inherit;
|
|
3686
|
+
line-height: 1.55;
|
|
3687
|
+
}
|
|
3688
|
+
|
|
3689
|
+
.advisor-loading {
|
|
3690
|
+
display: flex;
|
|
3691
|
+
align-items: center;
|
|
3692
|
+
gap: 10px;
|
|
3693
|
+
color: var(--muted);
|
|
3694
|
+
}
|
|
3695
|
+
|
|
3696
|
+
.advisor-error {
|
|
3697
|
+
color: var(--sl-color-danger-600, #c41e3a);
|
|
3698
|
+
}
|
|
3699
|
+
|
|
3700
|
+
.advisor-headline {
|
|
3701
|
+
display: flex;
|
|
3702
|
+
align-items: center;
|
|
3703
|
+
gap: 10px;
|
|
3704
|
+
margin-bottom: 8px;
|
|
3705
|
+
}
|
|
3706
|
+
|
|
3707
|
+
.advisor-template-name {
|
|
3708
|
+
font-size: 16px;
|
|
3709
|
+
}
|
|
3710
|
+
|
|
3711
|
+
.advisor-rationale {
|
|
3712
|
+
margin: 6px 0 12px;
|
|
3713
|
+
}
|
|
3714
|
+
|
|
3715
|
+
.advisor-alternatives ul {
|
|
3716
|
+
list-style: disc;
|
|
3717
|
+
padding-left: 20px;
|
|
3718
|
+
margin: 4px 0 0;
|
|
3719
|
+
}
|
|
3720
|
+
|
|
3721
|
+
.advisor-alternatives li {
|
|
3722
|
+
margin-bottom: 6px;
|
|
3723
|
+
}
|
|
3724
|
+
|
|
3725
|
+
.advisor-alternatives .btn-advisor-pick-alt {
|
|
3726
|
+
margin-left: 8px;
|
|
3727
|
+
vertical-align: middle;
|
|
3728
|
+
}
|
|
3729
|
+
|
|
3650
3730
|
/* Plan file autocomplete */
|
|
3651
3731
|
.plan-autocomplete {
|
|
3652
3732
|
position: relative;
|
package/package.json
CHANGED
|
@@ -746,6 +746,76 @@ export function createTemplatesRoutes() {
|
|
|
746
746
|
(req, res) => handleImportPreview(req, res),
|
|
747
747
|
);
|
|
748
748
|
|
|
749
|
+
/**
|
|
750
|
+
* POST /api/projects/:projectId/templates/advise
|
|
751
|
+
* Body: { sourceType, sourceValue?, model? }
|
|
752
|
+
*
|
|
753
|
+
* Asks the Python advisor to pick the best-fit pipeline template for
|
|
754
|
+
* the given source. `sourceType` mirrors the launcher's options
|
|
755
|
+
* ("none" | "spec" | "source" | "pr", or "prompt"/"plan" when called
|
|
756
|
+
* directly). The route forwards "none" to the Python advisor as
|
|
757
|
+
* "prompt" so the prompt textarea path works unchanged.
|
|
758
|
+
*
|
|
759
|
+
* Returns { ok: true, advice: { template_id, rationale, confidence,
|
|
760
|
+
* alternatives: [...] } } on success.
|
|
761
|
+
*/
|
|
762
|
+
router.post('/templates/advise', (req, res) => {
|
|
763
|
+
const body = req.body || {};
|
|
764
|
+
const rawSourceType = String(body.sourceType || '').trim();
|
|
765
|
+
if (!rawSourceType) {
|
|
766
|
+
return res
|
|
767
|
+
.status(400)
|
|
768
|
+
.json({ ok: false, error: 'sourceType is required' });
|
|
769
|
+
}
|
|
770
|
+
// Launcher uses "none" for the prompt textarea path. Map it to the
|
|
771
|
+
// backend's `prompt` source so the same wire shape covers both.
|
|
772
|
+
const sourceType = rawSourceType === 'none' ? 'prompt' : rawSourceType;
|
|
773
|
+
const ALLOWED = new Set(['prompt', 'spec', 'source', 'pr', 'plan']);
|
|
774
|
+
if (!ALLOWED.has(sourceType)) {
|
|
775
|
+
return res.status(400).json({
|
|
776
|
+
ok: false,
|
|
777
|
+
error: `sourceType must be one of: ${Array.from(ALLOWED).join(', ')}`,
|
|
778
|
+
});
|
|
779
|
+
}
|
|
780
|
+
const sourceValue = String(body.sourceValue || '');
|
|
781
|
+
if (sourceType !== 'prompt' && !sourceValue.trim()) {
|
|
782
|
+
return res.status(400).json({
|
|
783
|
+
ok: false,
|
|
784
|
+
error: 'sourceValue is required for non-prompt sources',
|
|
785
|
+
});
|
|
786
|
+
}
|
|
787
|
+
if (sourceType === 'prompt' && !sourceValue.trim()) {
|
|
788
|
+
return res
|
|
789
|
+
.status(400)
|
|
790
|
+
.json({ ok: false, error: 'sourceValue (prompt text) is required' });
|
|
791
|
+
}
|
|
792
|
+
const model =
|
|
793
|
+
body.model && typeof body.model === 'string' ? body.model : 'sonnet';
|
|
794
|
+
const { projectRoot } = req.project;
|
|
795
|
+
try {
|
|
796
|
+
const stdout = runWorcaTemplates(
|
|
797
|
+
projectRoot,
|
|
798
|
+
[
|
|
799
|
+
'advise',
|
|
800
|
+
'--source-type',
|
|
801
|
+
sourceType,
|
|
802
|
+
'--source-value',
|
|
803
|
+
'-',
|
|
804
|
+
'--model',
|
|
805
|
+
model,
|
|
806
|
+
],
|
|
807
|
+
{ stdin: sourceValue, timeout: 90000 },
|
|
808
|
+
);
|
|
809
|
+
const advice = JSON.parse(stdout || '{}');
|
|
810
|
+
res.json({ ok: true, advice });
|
|
811
|
+
} catch (err) {
|
|
812
|
+
const status = statusForCliCode(err.cliCode);
|
|
813
|
+
res
|
|
814
|
+
.status(status === 500 ? 500 : status)
|
|
815
|
+
.json({ ok: false, error: err.message || 'advise failed' });
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
|
|
749
819
|
/**
|
|
750
820
|
* POST /api/projects/:projectId/templates/validate
|
|
751
821
|
* Body: { config }
|