-
Notifications
You must be signed in to change notification settings - Fork 259
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Widgets] Refactor examples running #1023
Conversation
474d080
to
d5325f9
Compare
...omponents/InferenceWidget/widgets/AudioClassificationWidget/AudioClassificationWidget.svelte
Outdated
Show resolved
Hide resolved
fileUrl = src; | ||
selectedSampleUrl = src; | ||
getOutput({ isOnLoadCall: true }); | ||
const example = getWidgetExample<WidgetExampleAssetInput<WidgetExampleOutputLabels>>(model, validateExample); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am wondering if this logic shouldn't live in WidgetWrapper
instead?
Given it's the same for every widget
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think putting this logic in WidgetWrapper onMount would be confusing.
Here is the reason: all widgets except the TextGeneration & Text2Image, have same code, which is:
if (callApiOnMount && example) {
applyInputSample(example, { inferenceOpts: { isOnLoadCall: true } });
}
However, TextGeneration & Text2Image have custom logic. Which is:
const [textParam] = getSearchParams(["text"]);
if (textParam) {
setTextAreaValue(textParam);
getOutput({ useCache: true });
} else {
const example = getWidgetExample<WidgetExampleTextInput<WidgetExampleOutputText>>(model, validateExample);
if (example && callApiOnMount) {
applyInputSample(example, { inferenceOpts: { isOnLoadCall: true, useCache: true } });
}
}
Therefore, to implement it inside WidgetWrapper onMount
, we would need to check if it is TextGen or Text2Image and getSearchParams, which seems too implicit & confusing.
Wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
BTW is there some kind of Svelte component inheritance? does this concept exist?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. the closest thing in svelte is usage of slots as in
hub-docs/js/src/lib/components/InferenceWidget/shared/WidgetWrapper/WidgetWrapper.svelte
Line 118 in d2374e4
<slot name="bottom" /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe @coyotte508 can also take a quick look and let us know if you have ideas on whether to factor that logic in WidgetWrapper or not?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess we can include the onMount call in a helper and have a useExample(model, callApiOnMount, validateExample) or useExample($$props) called here.
Thought about it. I think it actually makes it look quite confusing/unclean when you have to pass 2 callback functions: validateExample
& applyInputSample
so it will look like this in that case:
helpers.ts
import { onMount } from "svelte"
...
export function customOnMount<T>(model: ModelData, isOnLoadCall: boolean, validate: func, applyInputSample: func){
onMount(() => {
const example = getExample<T>(model, validate);
if(example && isOnLoadCall){
applyInputSample(example);
}
})
}
and in XyzWidget.svelte
import { customOnMount } from "./helpers"
...
customOnMount<WidgetOutput<T>>(model, isOnLoadCall, validateExample, applyInputSample)
If you are fine, I'm happy to merge this PR as it is and iterate afterwards
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather than customOnMount
, a useXxx
name would be much better (see React-Use and Vue-use, it's a known named pattern), eg useInputSample
or useLoadExample
it actually makes it look quite confusing/unclean when you have to pass 2 callback functions: validateExample & applyInputSample
You could pass $$props
😬
customOnMount<WidgetOutput<T>>
There's probably a way to not have to specify <WidgetOutput<T>>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Made an update.
All the onMount example runs are happening inside WidgetWrapper
.
hub-docs/js/src/lib/components/InferenceWidget/shared/WidgetWrapper/WidgetWrapper.svelte
Line 70 in 37f2ccd
// run widget example |
Specifically, there are two parts:
Part 1: run one of the examples from the model card
hub-docs/js/src/lib/components/InferenceWidget/shared/WidgetWrapper/WidgetWrapper.svelte
Lines 79 to 82 in 37f2ccd
const example = getWidgetExample<TWidgetExample>(model, validateExample); | |
if (callApiOnMount && example) { | |
applyInputSample(example, { inferenceOpts: { isOnLoadCall: true } }); | |
} |
Part 2: run examples from URL query param (for widgets like TextGen widget as mentioned here)
hub-docs/js/src/lib/components/InferenceWidget/shared/WidgetWrapper/WidgetWrapper.svelte
Lines 71 to 77 in 37f2ccd
if (exampleQueryParams.length) { | |
const example = {} as TWidgetExample; | |
for (const key of exampleQueryParams) { | |
const val = getQueryParamVal(key); | |
example[key] = val; | |
} | |
applyInputSample(example); |
To activate, Part 2, widget (example: TextGen widget) needs to pass/activate optional prop in WudgetWrapper
Line 198 in 37f2ccd
exampleQueryParams={["text"]} |
@SBrandeis @coyotte508 wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
at a glance it's a lot cleaner / removes a lot of code, it's nice!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
WDYT @SBrandeis ?
Co-authored-by: Simon Brandeis <[email protected]>
Co-authored-by: Simon Brandeis <[email protected]>
find an updates in this comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO let's pull the trigger on this PR and iterate if needed
(Nice refacto!)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool stuff, I like the solution you've chosen
Way cleaner this way👍
Please address the few comments left before merging
js/src/lib/components/InferenceWidget/shared/WidgetWrapper/WidgetWrapper.svelte
Show resolved
Hide resolved
export let validateExample: (sample: WidgetExample) => sample is TWidgetExample; | ||
export let exampleQueryParams: QueryParam[] = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this be typed further?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like this for example (requires some ugly casts in onMount
but I think I'm fine with it as it makes it almost impossible to pass wrong query params)
export let exampleQueryParams: QueryParam[] = []; | |
export let exampleQueryParams: (keyof TWidgetExample)[] = []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, stronger typing is possible c89086b
// run widget example | ||
if (exampleQueryParams.length) { | ||
const example = {} as TWidgetExample; | ||
for (const key of exampleQueryParams) { | ||
const val = getQueryParamVal(key); | ||
example[key] = val; | ||
} | ||
applyInputSample(example); | ||
} else { | ||
const example = getWidgetExample<TWidgetExample>(model, validateExample); | ||
if (callApiOnMount && example) { | ||
applyInputSample(example, { inferenceOpts: { isOnLoadCall: true } }); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I'm not mistaken, getWidgetExample
here is never called when exampleQueryParams
is set in the concrete Widget component (ie most widgets)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok I tested it and I can confirm this behavior!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there anything that needs to be changed here?
If I'm not mistaken, getWidgetExample here is never called when exampleQueryParams is set in the concrete Widget component (ie most widgets)
I think this is the existing behaviour on main
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there anything that needs to be changed here?
yes
I think this is the existing behaviour on main
No
Sorry I think my wording was not clear here. I'll try to reformulate.
On main (hf.co)
When accessing for example HuggingFaceH4/zephyr-7b-alpha, The widget picksan example at random and starts generating (expected behavior).
On this branch
The widget does not try to get the example from the widgetData
and displays this error (bug?).
This is because exampleQueryParams.length
is always truthy in the TextGenerationWidget, so the getWidgetExample
call get skipped
Let me know if anything remains unclear 🤗
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
indeed, fixed the logic 7ebca52
if (callApiOnMount && example) { | ||
applyInputSample(example, { inferenceOpts: { isOnLoadCall: true } }); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure but isOnLoadCall
should prioritize the example output when specified, no?
Probably for a subsequent PR
type QueryParamVal = string | null | boolean | (string | number)[][]; | ||
export function getQueryParamVal(key: QueryParam): QueryParamVal { | ||
const searchParams = new URL(window.location.href).searchParams; | ||
const value = searchParams.get(key); | ||
if (["text", "context", "question", "query", "candidateLabels"].includes(key)) { | ||
if (["text", "context", "question", "query", "candidate_labels"].includes(key)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this will not work on existing URLs of ZeroShotClassification widget with URL query candidateLabels
rather than candidate_labels
. However, ZeroShotClassification widgets are not used widely. Therefore, I think this breaking change is fine
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think i agree for candidate_labels
(it's used in the spec for widget examples) but for structuredData
it's used that way in the spec no?
https://huggingface.co/docs/hub/models-widgets-examples#structured-data-classification
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see my comment there
return value; | ||
} else if (["table", "structuredData"].includes(key)) { | ||
} else if (["table", "structured_data"].includes(key)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same as the comment above but for structuredData
vs structured_data
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactor codes related to running widget examples
Overall, this refactor cleans some code & adds more usage of typing.
getDemoInputs
withgetWidgetExample
function as proposed in Proposal: Slightly better thangetDemoInputs
already #1021previewInputSample
functions entirely by re-usingapplyInputSample
function with newisPreview
opts. (See more here)change signature of
to
so that applyInputSample fully replaces previewInputSample when used with
opts isPreview=true
and applyInputSample with getWidgetExample replaces getDemoInputs and running example code in onMount. (in this example, I've usedAudioClassificationWidget.svelte
but the changes apply to all widgets).Order of merge operations