Skip to content

Commit 6d86920

Browse files
authored
Avoid recursive suggestions (#105)
1 parent 8cdbf78 commit 6d86920

File tree

5 files changed

+68
-53
lines changed

5 files changed

+68
-53
lines changed

typescript/packages/lookslike-high-level/src/components/annotation.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@ const { include } = tags;
55

66
export const annotation = ({
77
query,
8+
target,
89
data,
910
}: {
1011
query: signal.Signal<string>;
12+
target: number;
1113
data: { [key: string]: signal.Signal<any> };
1214
}) => {
13-
const annotation = annotationRecipe({ "?": query, ...data });
15+
const annotation = annotationRecipe({ "?": query, ".": target, ...data });
1416

1517
// TODO: Double include is necessary because first one doesn't carry bindings
1618
return include({ content: annotation.UI });

typescript/packages/lookslike-high-level/src/components/window-manager.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { LitElement, html, css } from "lit";
22
import { customElement, property } from "lit/decorators.js";
33
import { view, tags, render } from "@commontools/common-ui";
44
import { isGem, Gem, ID } from "../recipe.js";
5-
const { binding } = view;
65
const { include } = tags;
76

87
@customElement("common-window-manager")

typescript/packages/lookslike-high-level/src/recipe.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,9 @@ export const recipe = (
4444
isSignal(value) ? value : signal.state(value),
4545
])
4646
);
47-
const outputs = impl(inputsAsSignals);
48-
return { [ID]: id++, [TYPE]: type, ...outputs };
47+
const newId = id++;
48+
const outputs = impl({ ...inputsAsSignals, id: newId });
49+
return { [ID]: newId, [TYPE]: type, ...outputs };
4950
};
5051
};
5152

typescript/packages/lookslike-high-level/src/recipes/annotation.ts

Lines changed: 60 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
recipe,
66
Recipe,
77
Gem,
8+
ID,
89
TYPE,
910
NAME,
1011
suggestions,
@@ -31,51 +32,58 @@ const { subject } = stream;
3132
* supplied data.
3233
*/
3334

34-
export const annotation = recipe("annotation", ({ "?": query, ...data }) => {
35-
const suggestion: Signal<Result | undefined> = state(undefined);
36-
37-
effect([dataGems, query], async (dataGems, query: string) => {
38-
if (dataGems.length === 0) return;
39-
const guess = await findSuggestion(
40-
dataGems,
41-
suggestions,
42-
query,
43-
Object.keys(data),
35+
export const annotation = recipe(
36+
"annotation",
37+
({ "?": query, ".": target, ...data }) => {
38+
const suggestion = state<Result | undefined>(undefined);
39+
40+
effect(
41+
[dataGems, query, target],
42+
async (dataGems, query: string, target: number) => {
43+
if (dataGems.length === 0) return;
44+
const guess = await findSuggestion(
45+
dataGems,
46+
suggestions,
47+
query,
48+
Object.keys(data),
49+
[target]
50+
);
51+
suggestion.send(guess);
52+
}
4453
);
45-
suggestion.send(guess);
46-
});
47-
48-
const acceptSuggestion = subject<any>();
49-
const acceptedSuggestion = state<Result | undefined>(undefined);
50-
acceptSuggestion.sink({
51-
send: () => {
52-
acceptedSuggestion.send(suggestion.get());
53-
},
54-
});
55-
56-
const UI = computed(
57-
[suggestion, acceptedSuggestion],
58-
(suggestion, acceptedSuggestion) => {
59-
if (acceptedSuggestion) {
60-
const acceptedRecipe = acceptedSuggestion.recipe;
61-
const accepted = acceptedRecipe({
62-
...data,
63-
...acceptedSuggestion.boundGems,
64-
});
65-
return include({ content: accepted.UI });
66-
} else if (suggestion) {
67-
return tags.suggestions({
68-
suggestions: [{ id: 1, title: suggestion.description }],
69-
"@select-suggestion": acceptSuggestion,
70-
});
71-
} else {
72-
return undefined;
54+
55+
const acceptSuggestion = subject<any>();
56+
const acceptedSuggestion = state<Result | undefined>(undefined);
57+
acceptSuggestion.sink({
58+
send: () => {
59+
acceptedSuggestion.send(suggestion.get());
60+
},
61+
});
62+
63+
const UI = computed(
64+
[suggestion, acceptedSuggestion],
65+
(suggestion, acceptedSuggestion) => {
66+
if (acceptedSuggestion) {
67+
const acceptedRecipe = acceptedSuggestion.recipe;
68+
const accepted = acceptedRecipe({
69+
...data,
70+
...acceptedSuggestion.boundGems,
71+
});
72+
return include({ content: accepted.UI });
73+
} else if (suggestion) {
74+
return tags.suggestions({
75+
suggestions: [{ id: 1, title: suggestion.description }],
76+
"@select-suggestion": acceptSuggestion,
77+
});
78+
} else {
79+
return undefined;
80+
}
7381
}
74-
},
75-
);
82+
);
7683

77-
return { UI };
78-
});
84+
return { UI };
85+
}
86+
);
7987

8088
type Result = {
8189
recipe: Recipe;
@@ -88,19 +96,20 @@ async function findSuggestion(
8896
suggestions: Suggestion[],
8997
query: string,
9098
data: string[],
99+
ignoreList: number[]
91100
): Promise<Result | undefined> {
92101
// Use LLM to match query to data gems
93-
const matchedGems = await matchGemsWithLLM(dataGems, query);
102+
const matchedGems = await matchGemsWithLLM(dataGems, query, ignoreList);
94103

95104
// Filter to only compatible suggestions
96105
const suggestion = suggestions.find(
97106
(suggestion) =>
98107
Object.values(suggestion.dataGems).every((type) =>
99-
matchedGems.find((gem) => gem[TYPE] === type),
108+
matchedGems.find((gem) => gem[TYPE] === type)
100109
) &&
101110
Object.values(suggestion.bindings).every((binding) =>
102-
data.includes(binding),
103-
),
111+
data.includes(binding)
112+
)
104113
);
105114

106115
if (suggestion) {
@@ -110,7 +119,7 @@ async function findSuggestion(
110119
]) as [[string, Gem]];
111120

112121
const nameBindings = Object.fromEntries(
113-
bindings.map(([key, gem]) => [key, getNameFromGem(gem)]),
122+
bindings.map(([key, gem]) => [key, getNameFromGem(gem)])
114123
);
115124
const gemBindings = Object.fromEntries(bindings);
116125

@@ -134,7 +143,10 @@ type LLMSuggestion = {
134143
async function matchGemsWithLLM(
135144
dataGems: Gem[],
136145
query: string,
146+
ignoreList: number[] = []
137147
): Promise<Gem[]> {
148+
dataGems = dataGems.filter((gem) => !ignoreList.includes(gem[ID]));
149+
138150
const gemInfo = dataGems.map((gem) => ({
139151
name: getNameFromGem(gem),
140152
type: gem[TYPE],
@@ -174,7 +186,7 @@ notalk;justgo
174186
"Suggestion",
175187
query,
176188
matchedIndices,
177-
matchedIndices.map((item) => dataGems[item.index]),
189+
matchedIndices.map((item) => dataGems[item.index])
178190
);
179191
} catch (error) {
180192
console.error("Failed to parse LLM response:", error);

typescript/packages/lookslike-high-level/src/recipes/todo-list.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export function makeTodoItem(title: string, done: boolean = false): TodoItem {
1919
};
2020
}
2121

22-
export const todoList = recipe("todo list", ({ title, items }) => {
22+
export const todoList = recipe("todo list", ({ id, title, items }) => {
2323
const newTasks = subject<{ detail: { message: string } }>();
2424
newTasks.sink({
2525
send: (event) => {
@@ -50,6 +50,7 @@ export const todoList = recipe("todo list", ({ title, items }) => {
5050
[
5151
annotation({
5252
query: item.title,
53+
target: id,
5354
data: { items, done: item.done, title: item.title },
5455
}),
5556
]

0 commit comments

Comments
 (0)