hand recorder: log in from the Upload page, three-step page, no terminal

The Upload page is now three numbered steps: choose the export, log in to
Hugging Face, upload. Log in runs hub.py login, huggingface_hub's browser
login (OAuth device code, as hf auth login does): the link opens in the
browser and the page shows the code to enter, with Copy code and Cancel.
hub.py saves the token; the window never sees one, and nobody pastes one.

The terminal upload and the login command are gone from the page, and
UPLOAD.md is now a short 'About uploading' under the steps.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
DeeJanuzandClaude Opus 5.5 committed 2026-10-03 13:01:08 -06:00
1 parent e62ebb8669
commit ea483f7ce3
6 files changed
+440 -335

No files matched your search

+252 -229
View File
@@ -1494,7 +1494,7 @@ Kirigami.ApplicationWindow {
if (backend.uploading) return ""
if (backend.hubDryRun) return ""
if (!backend.uploadAllowed) return "Contributions aren't open yet"
if (!loggedIn) return "Log in to Hugging Face first (below)"
if (!loggedIn) return "Log in first (step 2)"
return ""
}
@@ -1555,263 +1555,286 @@ Kirigami.ApplicationWindow {
}
}
Kirigami.FormLayout {
ColumnLayout {
Layout.fillWidth: true
visible: uploadView.exported.length > 0
Controls.ComboBox {
id: exportBox
Kirigami.FormData.label: "Export:"
model: uploadView.exported
textRole: "label"
valueRole: "id"
enabled: !backend.uploading
Component.onCompleted: currentIndex = Math.max(0, indexOfValue(root.chosenSession))
onActivated: root.chosenSession = currentValue
}
spacing: Kirigami.Units.largeSpacing
Controls.Label {
Kirigami.FormData.label: "Size:"
text: uploadView.chosen ? uploadView.chosen.exportText : ""
}
Controls.Label {
Kirigami.FormData.label: "Goes to:"
Layout.fillWidth: true
wrapMode: Text.Wrap
textFormat: Text.StyledText
text: "<a href=\"" + backend.datasetUrl + "\">" + backend.dataset + "</a>, as a pull request"
text: "Your export goes to <a href=\"" + backend.datasetUrl + "\">" + backend.dataset + "</a> as a "
+ "pull request from your own Hugging Face account. Nothing is published until the "
+ "maintainer has checked it."
onLinkActivated: link => Qt.openUrlExternally(link)
}
// 1. The export
Kirigami.Heading { level: 3; text: "1. Choose the export" }
RowLayout {
Kirigami.FormData.label: "Hugging Face:"
Controls.Label {
Layout.fillWidth: true
Controls.ComboBox {
id: exportBox
Layout.fillWidth: true
Layout.maximumWidth: Kirigami.Units.gridUnit * 22
model: uploadView.exported
textRole: "label"
valueRole: "id"
enabled: !backend.uploading
Component.onCompleted: currentIndex = Math.max(0, indexOfValue(root.chosenSession))
onActivated: root.chosenSession = currentValue
}
Controls.Label {
opacity: 0.7
text: uploadView.chosen ? uploadView.chosen.exportText : ""
}
}
Kirigami.InlineMessage {
Layout.fillWidth: true
visible: uploadView.chosen !== null && uploadView.chosen.export_stale
type: Kirigami.MessageType.Warning
text: "This session changed since it was exported. Export it again so the upload has your latest deletions."
}
Kirigami.InlineMessage {
Layout.fillWidth: true
visible: uploadView.session !== "" && uploadView.info.previous.export_sha !== undefined
type: Kirigami.MessageType.Information
text: "This export was uploaded on " + (uploadView.info.previous.uploaded || "") + ": "
+ (uploadView.info.previous.pr_url || "") + ". There's no need to upload it again."
}
// 2. The login: Log in opens Hugging Face in the browser, where the person approves
// it. The token goes from there to hub.py, never into this window.
Kirigami.Heading { level: 3; text: "2. Log in to Hugging Face" }
RowLayout {
Layout.fillWidth: true
visible: loginState !== "waiting"
readonly property string loginState: backend.login.state
Kirigami.Icon {
visible: parent.loginState === "ok"
source: "checkmark"
implicitWidth: Kirigami.Units.iconSizes.small
implicitHeight: implicitWidth
}
Controls.BusyIndicator {
visible: ["checking", "starting", "unknown"].indexOf(parent.loginState) >= 0
running: visible
implicitWidth: Kirigami.Units.gridUnit * 1.5
implicitHeight: implicitWidth
}
Controls.Label {
Layout.fillWidth: true
visible: parent.loginState !== "waiting"
wrapMode: Text.Wrap
text: backend.login.state === "dry" ? "not checked in a dry run"
: backend.login.state === "unknown" ? "not checked yet"
: backend.login.state === "none" ? "not logged in"
text: parent.loginState === "dry" ? "Not needed for a dry run."
: parent.loginState === "unknown" ? "Checking your login"
: parent.loginState === "none"
? "Press Log in. Hugging Face opens in your browser: sign in, or make a free "
+ "account, then enter the code this page shows."
: backend.login.text || ""
}
Controls.Button {
visible: backend.login.state !== "dry"
visible: ["none", "read", "error", "ok"].indexOf(parent.loginState) >= 0
text: parent.loginState === "ok" ? "Use another account" : "Log in"
icon.name: parent.loginState === "ok" ? "system-switch-user" : "im-user-online"
flat: parent.loginState === "ok"
enabled: !backend.uploading
onClicked: backend.logIn()
}
Controls.Button {
visible: parent.loginState === "error"
text: "Check again"
icon.name: "view-refresh"
enabled: backend.login.state !== "checking"
onClicked: backend.checkLogin()
}
}
}
Kirigami.InlineMessage {
Layout.fillWidth: true
visible: uploadView.chosen !== null && uploadView.chosen.export_stale
type: Kirigami.MessageType.Warning
text: "This session changed since it was exported. Export it again so the upload has your latest deletions."
}
// Not logged in: how to log in, in a terminal. The token never goes into this window.
Kirigami.InlineMessage {
Layout.fillWidth: true
visible: uploadView.session !== "" && ["none", "read", "error", "missing"].indexOf(backend.login.state) >= 0
type: backend.login.state === "error" ? Kirigami.MessageType.Error : Kirigami.MessageType.Warning
text: backend.login.state === "none"
? "You're not logged in to Hugging Face. Open a terminal (Konsole) and run the command below. "
+ "Paste a token with the Write role when it asks (steps 1 to 3 below say how to make one). "
+ "The token goes only into that terminal, never into this window. Then press Check again."
: backend.login.text || ""
actions: backend.login.link ? [linkAction] : []
Kirigami.Action {
id: linkAction
text: "Open"
icon.name: "internet-services"
onTriggered: Qt.openUrlExternally(backend.login.link)
}
}
RowLayout {
Layout.fillWidth: true
visible: uploadView.session !== "" && ["none", "read"].indexOf(backend.login.state) >= 0
Controls.TextField {
id: loginCommand
Kirigami.InlineMessage {
Layout.fillWidth: true
readOnly: true
font.family: "monospace"
text: backend.loginCommand + (backend.login.state === "read" ? " --force" : "")
}
Controls.Button {
text: "Copy command"
icon.name: "edit-copy"
onClicked: backend.copy(loginCommand.text)
}
}
Kirigami.InlineMessage {
Layout.fillWidth: true
visible: uploadView.session !== "" && uploadView.info.previous.export_sha !== undefined
type: Kirigami.MessageType.Information
text: "This export was uploaded on " + (uploadView.info.previous.uploaded || "") + ": "
+ (uploadView.info.previous.pr_url || "") + ". There's no need to upload it again."
}
RowLayout {
visible: uploadView.session !== ""
Controls.Button {
text: backend.uploading ? "Uploading…" : backend.hubDryRun ? "Upload (dry run)" : "Upload"
icon.name: "cloud-upload"
enabled: uploadView.blocked === "" && !backend.uploading
onClicked: uploadView.startUpload()
}
Controls.BusyIndicator {
visible: backend.uploading
running: visible
implicitWidth: Kirigami.Units.gridUnit * 1.5
implicitHeight: implicitWidth
}
Controls.Button {
visible: backend.uploading
text: "Cancel"
icon.name: "dialog-cancel"
onClicked: backend.cancelUpload()
}
Controls.Label {
visible: !backend.uploading && uploadView.blocked !== ""
opacity: 0.7
text: uploadView.blocked
}
}
// Progress: a share while the export is checked, a sweeping bar while it's sent
// (huggingface_hub doesn't report progress). Drawn here, as on the Export page.
Rectangle {
id: track
Layout.preferredWidth: Kirigami.Units.gridUnit * 20
implicitHeight: Kirigami.Units.smallSpacing * 2
visible: backend.uploading
radius: height / 2
clip: true
color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.15)
readonly property bool unknown: uploadView.up.fraction === undefined || uploadView.up.fraction < 0
Rectangle {
id: fill
height: parent.height
radius: parent.radius
color: Kirigami.Theme.highlightColor
width: track.unknown ? parent.width / 4 : parent.width * Math.max(0, Math.min(1, uploadView.up.fraction))
x: 0
SequentialAnimation on x {
running: track.visible && track.unknown
loops: Animation.Infinite
onRunningChanged: if (!running) fill.x = 0
NumberAnimation { from: -fill.width; to: track.width; duration: 1600 }
visible: backend.login.state === "waiting"
type: Kirigami.MessageType.Information
text: "Hugging Face is open in your browser (" + (backend.login.url || "") + "). Sign in, or "
+ "make a free account, enter the code below and approve the login. If it asks about "
+ "organizations, leave them unticked. The code works for "
+ Math.round((backend.login.expires_in || 300) / 60) + " minutes; this page carries on "
+ "by itself once you've approved."
actions: [openLogin, copyLogin, cancelLogin]
Kirigami.Action {
id: openLogin
text: "Open again"
icon.name: "internet-services"
onTriggered: Qt.openUrlExternally(backend.login.url)
}
Kirigami.Action {
id: copyLogin
text: "Copy code"
icon.name: "edit-copy"
onTriggered: backend.copy(backend.login.code)
}
Kirigami.Action {
id: cancelLogin
text: "Cancel"
icon.name: "dialog-cancel"
onTriggered: backend.cancelLogin()
}
}
}
Controls.Label {
Layout.fillWidth: true
visible: uploadView.mine && (uploadView.up.text || "") !== ""
&& (backend.uploading || uploadView.up.phase === "failed" && uploadView.upError.kind === "cancelled")
wrapMode: Text.Wrap
text: uploadView.up.text || ""
}
Controls.Label {
visible: backend.login.state === "waiting"
Layout.leftMargin: Kirigami.Units.gridUnit
text: backend.login.code || ""
font.family: "monospace"
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 2.5
font.letterSpacing: 4
}
// The pull request is open and the files are on their way: time to plug in.
Kirigami.InlineMessage {
Layout.fillWidth: true
visible: backend.uploading && uploadView.mine && (uploadView.up.pr_url || "") !== ""
type: Kirigami.MessageType.Positive
text: "Your pull request is open: " + (uploadView.up.pr_url || "") + ". The files are uploading "
+ "to it now, which can take a while. Plug in the headset and leave it plugged in until "
+ "this page says Uploaded. You can take the headset off: it stays awake until the upload "
+ "is done. Keep the Hand Recorder open."
actions: [openOpenedPr, copyOpenedPr]
Kirigami.Action {
id: openOpenedPr
text: "Open"
icon.name: "internet-services"
onTriggered: Qt.openUrlExternally(uploadView.up.pr_url)
}
Kirigami.Action {
id: copyOpenedPr
text: "Copy link"
icon.name: "edit-copy"
onTriggered: backend.copy(uploadView.up.pr_url)
}
}
Kirigami.InlineMessage {
Layout.fillWidth: true
visible: !backend.uploading && uploadView.mine && uploadView.up.phase === "done"
type: Kirigami.MessageType.Positive
text: uploadView.upResult.dry_run
? "Dry run: the export passed its checks. It would go to " + uploadView.upResult.repo + "/"
+ uploadView.upResult.path_in_repo + " (" + uploadView.upResult.files + " files). Nothing was sent."
: "Uploaded. Your pull request: " + (uploadView.upResult.pr_url || "")
+ ". The maintainer reviews it before it joins the dataset."
actions: uploadView.upResult.pr_url ? [openPr, copyPr] : []
Kirigami.Action {
id: openPr
text: "Open"
icon.name: "internet-services"
onTriggered: Qt.openUrlExternally(uploadView.upResult.pr_url)
}
Kirigami.Action {
id: copyPr
text: "Copy link"
icon.name: "edit-copy"
onTriggered: backend.copy(uploadView.upResult.pr_url)
}
}
Kirigami.InlineMessage {
Layout.fillWidth: true
visible: !backend.uploading && uploadView.mine && uploadView.up.phase === "failed"
&& uploadView.upError.kind !== "cancelled"
type: Kirigami.MessageType.Error
text: (uploadView.upError.text || "")
+ ((uploadView.upError.errors || []).length ? "\n\n• " + uploadView.upError.errors.join("\n• ") : "")
actions: uploadView.upError.link ? [openErrorLink] : []
Kirigami.Action {
id: openErrorLink
text: "Open"
icon.name: "internet-services"
onTriggered: Qt.openUrlExternally(uploadView.upError.link)
}
}
Controls.TextArea {
Layout.fillWidth: true
visible: uploadView.mine && (uploadView.up.log || "") !== ""
readOnly: true
selectByMouse: true
wrapMode: Text.WrapAnywhere
font.family: "monospace"
text: uploadView.up.log || ""
}
Kirigami.Separator {
Layout.fillWidth: true
visible: uploadView.session !== ""
}
Kirigami.Heading {
level: 3
visible: uploadView.session !== ""
text: "Or upload from a terminal"
}
RowLayout {
Layout.fillWidth: true
visible: uploadView.session !== ""
Controls.TextArea {
id: command
// 3. The upload
Kirigami.Heading { level: 3; text: "3. Upload" }
Controls.Label {
Layout.fillWidth: true
wrapMode: Text.Wrap
textFormat: Text.StyledText
text: "The first time, accept the dataset's terms on <a href=\"" + backend.datasetUrl
+ "\">its page</a>. Upload checks the export, opens your pull request, then sends the files."
onLinkActivated: link => Qt.openUrlExternally(link)
}
RowLayout {
Controls.Button {
text: backend.uploading ? "Uploading…" : backend.hubDryRun ? "Upload (dry run)" : "Upload"
icon.name: "cloud-upload"
enabled: uploadView.blocked === "" && !backend.uploading
onClicked: uploadView.startUpload()
}
Controls.BusyIndicator {
visible: backend.uploading
running: visible
implicitWidth: Kirigami.Units.gridUnit * 1.5
implicitHeight: implicitWidth
}
Controls.Button {
visible: backend.uploading
text: "Cancel"
icon.name: "dialog-cancel"
onClicked: backend.cancelUpload()
}
Controls.Label {
visible: !backend.uploading && uploadView.blocked !== ""
opacity: 0.7
text: uploadView.blocked
}
}
// Progress: a share while the export is checked, a sweeping bar while it's sent
// (huggingface_hub doesn't report progress). Drawn here, as on the Export page.
Rectangle {
id: track
Layout.preferredWidth: Kirigami.Units.gridUnit * 20
implicitHeight: Kirigami.Units.smallSpacing * 2
visible: backend.uploading
radius: height / 2
clip: true
color: Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.15)
readonly property bool unknown: uploadView.up.fraction === undefined || uploadView.up.fraction < 0
Rectangle {
id: fill
height: parent.height
radius: parent.radius
color: Kirigami.Theme.highlightColor
width: track.unknown ? parent.width / 4 : parent.width * Math.max(0, Math.min(1, uploadView.up.fraction))
x: 0
SequentialAnimation on x {
running: track.visible && track.unknown
loops: Animation.Infinite
onRunningChanged: if (!running) fill.x = 0
NumberAnimation { from: -fill.width; to: track.width; duration: 1600 }
}
}
}
Controls.Label {
Layout.fillWidth: true
visible: uploadView.mine && (uploadView.up.text || "") !== ""
&& (backend.uploading || uploadView.up.phase === "failed" && uploadView.upError.kind === "cancelled")
wrapMode: Text.Wrap
text: uploadView.up.text || ""
}
// The pull request is open and the files are on their way: time to plug in.
Kirigami.InlineMessage {
Layout.fillWidth: true
visible: backend.uploading && uploadView.mine && (uploadView.up.pr_url || "") !== ""
type: Kirigami.MessageType.Positive
text: "Your pull request is open: " + (uploadView.up.pr_url || "") + ". The files are uploading "
+ "to it now, which can take a while. Plug in the headset and leave it plugged in until "
+ "this page says Uploaded. You can take the headset off: it stays awake until the upload "
+ "is done. Keep the Hand Recorder open."
actions: [openOpenedPr, copyOpenedPr]
Kirigami.Action {
id: openOpenedPr
text: "Open"
icon.name: "internet-services"
onTriggered: Qt.openUrlExternally(uploadView.up.pr_url)
}
Kirigami.Action {
id: copyOpenedPr
text: "Copy link"
icon.name: "edit-copy"
onTriggered: backend.copy(uploadView.up.pr_url)
}
}
Kirigami.InlineMessage {
Layout.fillWidth: true
visible: !backend.uploading && uploadView.mine && uploadView.up.phase === "done"
type: Kirigami.MessageType.Positive
text: uploadView.upResult.dry_run
? "Dry run: the export passed its checks. It would go to " + uploadView.upResult.repo + "/"
+ uploadView.upResult.path_in_repo + " (" + uploadView.upResult.files + " files). Nothing was sent."
: "Uploaded. Your pull request: " + (uploadView.upResult.pr_url || "")
+ ". The maintainer reviews it before it joins the dataset."
actions: uploadView.upResult.pr_url ? [openPr, copyPr] : []
Kirigami.Action {
id: openPr
text: "Open"
icon.name: "internet-services"
onTriggered: Qt.openUrlExternally(uploadView.upResult.pr_url)
}
Kirigami.Action {
id: copyPr
text: "Copy link"
icon.name: "edit-copy"
onTriggered: backend.copy(uploadView.upResult.pr_url)
}
}
Kirigami.InlineMessage {
Layout.fillWidth: true
visible: !backend.uploading && uploadView.mine && uploadView.up.phase === "failed"
&& uploadView.upError.kind !== "cancelled"
type: Kirigami.MessageType.Error
text: (uploadView.upError.text || "")
+ ((uploadView.upError.errors || []).length ? "\n\n• " + uploadView.upError.errors.join("\n• ") : "")
actions: uploadView.upError.link ? [openErrorLink] : []
Kirigami.Action {
id: openErrorLink
text: "Open"
icon.name: "internet-services"
onTriggered: Qt.openUrlExternally(uploadView.upError.link)
}
}
Controls.TextArea {
Layout.fillWidth: true
visible: uploadView.mine && (uploadView.up.log || "") !== ""
readOnly: true
selectByMouse: true
wrapMode: Text.WrapAnywhere
font.family: "monospace"
text: backend.uploadCommand(uploadView.session)
text: uploadView.up.log || ""
}
Controls.Button {
text: "Copy command"
icon.name: "edit-copy"
onClicked: backend.copy(command.text)
Kirigami.Separator { Layout.fillWidth: true; visible: uploadView.session !== "" }
MarkdownText {
Layout.fillWidth: true
visible: uploadView.session !== ""
markdown: backend.uploadText(uploadView.session)
}
}
MarkdownText {
Layout.fillWidth: true
visible: uploadView.session !== ""
markdown: backend.uploadText(uploadView.session)
}
}
}
}