Skip to content

Commit 07b918f

Browse files
committed
refactor signed-in state loading flow
1 parent 7cb16a8 commit 07b918f

File tree

17 files changed

+270
-116
lines changed

17 files changed

+270
-116
lines changed

spx-gui/src/components/agent-copilot/CopilotProvider.vue

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export function useAgentCopilotCtx() {
8484
* Handles initialization of Copilot, MCP connections, and UI rendering.
8585
*/
8686
import { reactive, inject, ref, provide, onBeforeUnmount, computed, type Ref, watch } from 'vue'
87-
import { computedShallowReactive, untilNotNull } from '@/utils/utils'
87+
import { computedShallowReactive } from '@/utils/utils'
8888
import { useI18n } from '@/utils/i18n'
8989
import { Copilot } from './copilot'
9090
import { CopilotController, type ICopilot } from './index'
@@ -95,7 +95,8 @@ import { ToolRegistry } from './mcp/registry'
9595
import { Collector } from './mcp/collector'
9696
import CopilotUI from './CopilotUI.vue'
9797
import { z } from 'zod'
98-
import { isSignedIn, useSignedInUser } from '@/stores/user'
98+
import { untilLoaded } from '@/utils/query'
99+
import { useSignedInStateQuery } from '@/stores/user'
99100
import { createProjectToolDescription, CreateProjectArgsSchema } from './mcp/definitions'
100101
import { getProject, Visibility } from '@/apis/project'
101102
import { useRouter } from 'vue-router'
@@ -114,7 +115,7 @@ const visible = ref(false)
114115
const mcpDebuggerVisible = ref(false)
115116
const showEnvPanel = ref(false)
116117
const router = useRouter()
117-
const signedInUser = useSignedInUser()
118+
const signedInStateQuery = useSignedInStateQuery()
118119
119120
const toggleEnvPanel = () => {
120121
showEnvPanel.value = !showEnvPanel.value
@@ -180,15 +181,15 @@ const initBasicTools = async () => {
180181
async function createProject(options: CreateProjectOptions) {
181182
const projectName = options.projectName
182183
183-
// Check if user is signed in
184-
if (!isSignedIn()) {
184+
const signedInState = await untilLoaded(signedInStateQuery)
185+
if (!signedInState.isSignedIn) {
185186
return {
186187
success: false,
187188
message: 'Please sign in to create a project'
188189
}
189190
}
190191
191-
const { username } = await untilNotNull(signedInUser)
192+
const { username } = signedInState.user
192193
193194
try {
194195
// Check if project already exists

spx-gui/src/components/asset/gen/sprite/SpriteGen.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { useI18n } from '@/utils/i18n'
44
import { useNetwork } from '@/utils/network'
55
import type { SpriteGen } from '@/models/spx/gen/sprite-gen'
66
import type { Sprite } from '@/models/spx/sprite'
7-
import { useSignedInUser } from '@/stores/user'
7+
import { useSignedInStateQuery } from '@/stores/user'
88
import { cloudHelpers } from '@/models/common/cloud'
99
import { provideLocalEditorCtx } from '@/components/editor/EditorContextProvider.vue'
1010
import { EditorState } from '@/components/editor/editor-state'
@@ -23,7 +23,7 @@ const emit = defineEmits<{
2323
}>()
2424
2525
const i18n = useI18n()
26-
const signedInUser = useSignedInUser()
26+
const signedInStateQuery = useSignedInStateQuery()
2727
const { isOnline } = useNetwork()
2828
// Local cache is not really used in sprite gen, so a dummy implementation is sufficient.
2929
const localCache: ILocalCache = {
@@ -33,7 +33,7 @@ const localCache: ILocalCache = {
3333
}
3434
// We should override the state to avoid history operations caused by animation / costume changes within sprite gen.
3535
const editorStateInGen = useComputedDisposable(
36-
() => new EditorState(i18n, props.gen.previewProject, isOnline, signedInUser, cloudHelpers, localCache)
36+
() => new EditorState(i18n, props.gen.previewProject, isOnline, signedInStateQuery, cloudHelpers, localCache)
3737
)
3838
const editorCtxInGen = computedShallowReactive(() => ({
3939
project: props.gen.previewProject,

spx-gui/src/components/asset/library/AssetSaveModal.vue

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import type { AssetModel, PartialAssetData } from '@/models/spx/common/asset'
2323
import { backdrop2Asset, sound2Asset, sprite2Asset } from '@/models/spx/common/asset'
2424
import { useI18n } from '@/utils/i18n'
2525
import { useQuery } from '@/utils/query'
26-
import { useSignedInUser } from '@/stores/user'
26+
import { useSignedInStateQuery } from '@/stores/user'
2727
import { categoryAll, getAssetCategories } from './category'
2828
import BackdropPreview from './BackdropPreview.vue'
2929
import SpritePreview from './SpritePreview.vue'
@@ -39,10 +39,10 @@ const emit = defineEmits<{
3939
resolved: []
4040
}>()
4141
42-
const signedInUser = useSignedInUser()
42+
const signedInStateQuery = useSignedInStateQuery()
4343
4444
const user = computed(() => {
45-
const u = signedInUser.value
45+
const u = signedInStateQuery.data.value?.user
4646
if (u == null) throw new Error('user is not signed in')
4747
return u
4848
})

spx-gui/src/components/copilot/CopilotRoot.vue

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import * as highlightLink from './custom-elements/HighlightLink.vue'
2828
import * as codeLink from './custom-elements/CodeLink'
2929
import * as codeChange from './custom-elements/CodeChange.vue'
3030
import { codeFilePathSchema, parseProjectIdentifier, projectIdentifierSchema } from './common'
31-
import { isSignedIn, useSignedInUser, type SignedInUser } from '@/stores/user'
31+
import { useSignedInStateQuery, type SignedInState } from '@/stores/user'
3232
import { userSessionStorageRef } from '@/utils/user-storage'
3333
import { cloudHelpers, type CloudHelpers } from '@/models/common/cloud'
3434
import { provideCopilot } from './context'
@@ -298,15 +298,15 @@ If there's an API References UI in code editor, encourage the user to insert cod
298298
}
299299
300300
class UserContextProvider implements ICopilotContextProvider {
301-
constructor(private signedInUser: WatchSource<SignedInUser | null>) {}
301+
constructor(private signedInState: WatchSource<SignedInState | null>) {}
302302
303303
provideContext(): string {
304-
const signedInUsername = toValue(this.signedInUser)?.username
304+
const signedInState = toValue(this.signedInState)
305305
const userInfo =
306-
signedInUsername != null
307-
? `Now the user is signed in with name "${signedInUsername}"`
308-
: isSignedIn()
309-
? 'The user is signed in, but the user info is still loading'
306+
signedInState == null
307+
? 'The signed-in state is still loading'
308+
: signedInState.isSignedIn
309+
? `Now the user is signed in with name "${signedInState.user.username}"`
310310
: 'The user is not signed in'
311311
return `# Current user
312312
${userInfo}`
@@ -413,7 +413,7 @@ const modalEvents = useModalEvents()
413413
const messageEvents = useMessageEvents()
414414
const editorCtxRef = useEditorCtxRef()
415415
const codeEditorRef = useCodeEditorRef()
416-
const signedInUser = useSignedInUser()
416+
const signedInStateQuery = useSignedInStateQuery()
417417
const retriever = new Retriever(editorCtxRef, cloudHelpers)
418418
const copilot = new Copilot()
419419
onUnmounted(() => copilot.dispose())
@@ -455,7 +455,7 @@ copilot.registerCustomElement({
455455
456456
copilot.registerTool(new GetUINodeTextContentTool(radar))
457457
copilot.registerContextProvider(new UIContextProvider(radar, i18n))
458-
copilot.registerContextProvider(new UserContextProvider(signedInUser))
458+
copilot.registerContextProvider(new UserContextProvider(signedInStateQuery.data))
459459
copilot.registerContextProvider(new LocationContextProvider(router))
460460
copilot.registerContextProvider(new ProjectContextProvider(editorCtxRef))
461461
copilot.registerContextProvider(new SpriteContextProvider(editorCtxRef))

spx-gui/src/components/copilot/CopilotUI.vue

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ import { useRouter } from 'vue-router'
2929
3030
import { isRectIntersecting, useBottomSticky, useContentSize } from '@/utils/dom'
3131
import { assertNever, localStorageRef, timeout, untilNotNull } from '@/utils/utils'
32+
import { untilLoaded } from '@/utils/query'
3233
import { useMessageHandle } from '@/utils/exception'
33-
import { isSignedIn, useSignedInUser } from '@/stores/user'
34+
import { isSignedIn, useSignedInStateQuery } from '@/stores/user'
3435
import { useDraggable, type Offset } from '@/utils/draggable'
3536
import { providePopupContainer, UIButton, UITooltip } from '@/components/ui'
3637
import CopilotInput from './CopilotInput.vue'
@@ -397,7 +398,7 @@ onBeforeUnmount(
397398
)
398399
399400
// Copilot open handling, implemented here due to high business logic coupling
400-
const signedInUser = useSignedInUser()
401+
const signedInStateQuery = useSignedInStateQuery()
401402
const usedCopilotUsersRef = localStorageRef<string[]>('spx-gui-used-copilot-users', [])
402403
watch(
403404
router.currentRoute,
@@ -411,12 +412,17 @@ watch(
411412
immediate: true
412413
}
413414
)
414-
/**
415-
* Records that the current user has used Copilot when the specified condition is met.
416-
*/
415+
/** Whether the current user has not used Copilot. */
416+
const copilotNotUsed = computed(() => {
417+
const signedInState = signedInStateQuery.data.value
418+
if (signedInState == null) return false
419+
return !signedInState.isSignedIn || !usedCopilotUsersRef.value.includes(signedInState.user.username)
420+
})
421+
/** Records that the current user has used Copilot when the specified condition is met. */
417422
async function recordCopilotUsage(shouldRecord: () => boolean) {
418-
if (!isSignedIn()) return
419-
const { username } = await untilNotNull(signedInUser)
423+
const signedInState = await untilLoaded(signedInStateQuery)
424+
if (!signedInState.isSignedIn) return
425+
const { username } = signedInState.user
420426
const userUsedCopilot = usedCopilotUsersRef.value
421427
// Skip if condition is not met or user has already been recorded
422428
if (!shouldRecord() || userUsedCopilot.includes(username)) return
@@ -434,9 +440,9 @@ watch(
434440
)
435441
/** Open copilot for signed-in users on their first copilot use */
436442
onMounted(async () => {
437-
if (!isSignedIn()) return
438-
const user = await untilNotNull(signedInUser)
439-
if (usedCopilotUsersRef.value.includes(user.username)) return
443+
const signedInState = await untilLoaded(signedInStateQuery)
444+
if (!signedInState.isSignedIn) return
445+
if (usedCopilotUsersRef.value.includes(signedInState.user.username)) return
440446
copilot.open()
441447
})
442448
</script>
@@ -540,7 +546,7 @@ onMounted(async () => {
540546
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
541547
<path d="M12 12.6667V3.33333" stroke-width="1.33333" stroke-linecap="round" stroke-linejoin="round" />
542548
<path
543-
:class="{ arrow: signedInUser == null || !usedCopilotUsersRef.includes(signedInUser.username) }"
549+
:class="{ animating: copilotNotUsed }"
544550
d="M3.33301 3.33334L8.66634 8L3.33301 12.6667"
545551
stroke-width="1.33333"
546552
stroke-linecap="round"
@@ -831,7 +837,7 @@ $toColor: #c390ff;
831837
}
832838
}
833839
834-
.arrow {
840+
.animating {
835841
animation: nudge 0.5s ease-in-out infinite alternate;
836842
}
837843

0 commit comments

Comments
 (0)