@worca/ui 0.46.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/styles.css CHANGED
@@ -2371,6 +2371,16 @@ sl-input [slot="prefix"] {
2371
2371
  margin-top: 2px;
2372
2372
  }
2373
2373
 
2374
+ .settings-field-hint--warn {
2375
+ color: var(--warning, #b8860b);
2376
+ }
2377
+
2378
+ .settings-field-hint--warn code {
2379
+ color: inherit;
2380
+ background: transparent;
2381
+ padding: 0;
2382
+ }
2383
+
2374
2384
  /* Export-mode picker (standalone vs delta): roomy rows, with each option's
2375
2385
  description on its own line aligned under the option title (not the radio). */
2376
2386
  .export-mode-group sl-radio::part(base) {
@@ -3637,6 +3647,86 @@ sl-option.template-grouped:focus::part(suffix) {
3637
3647
  line-height: 1.5;
3638
3648
  }
3639
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
+
3640
3730
  /* Plan file autocomplete */
3641
3731
  .plan-autocomplete {
3642
3732
  position: relative;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@worca/ui",
3
- "version": "0.46.0",
3
+ "version": "0.48.0",
4
4
  "description": "Pipeline monitoring UI for worca-cc",
5
5
  "license": "MIT",
6
6
  "author": "Sinisha Djukic",
@@ -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 }