Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
24ae6d8
Initial plan
Copilot Dec 23, 2025
4ca5d31
Add source detail view and update SourceCard with running job link
Copilot Dec 23, 2025
f01fb20
Make SourceCard clickable to navigate to detail view and add Chinese …
Copilot Dec 23, 2025
17c9266
Fix router order to prevent route matching issues
Copilot Dec 23, 2025
d24636a
Address code review feedback: use absolute imports, add i18n, improve…
Copilot Dec 23, 2025
5b34ebb
Rename newCollectJob to collectJobForm, add config field, extract sou…
Copilot Dec 23, 2025
c03f5ca
Fix code review issues: add config field, remove redundant handler, a…
Copilot Dec 23, 2025
6bfa4c2
Extract sourceCollectJobCard component and rename latestRunningJob to…
Copilot Dec 23, 2025
0698d7a
trivias fixes on sourceForm, collectJobForm
xiaoland Dec 23, 2025
b5d3cf3
Merge branch 'copilot/add-views-sources-index' of ssh://github.com/In…
xiaoland Dec 23, 2025
2e4493c
chore(sourceCard): checkRunningJob -> checkOpenJob
xiaoland Dec 23, 2025
32f5e33
upd(sourceCollectJob): add refresh-indicator
xiaoland Dec 23, 2025
a755266
trivias
xiaoland Dec 23, 2025
60e5d58
chore: update agent docs
xiaoland Dec 23, 2025
d8ebfd1
try fix copilot-setup-steps.yml
xiaoland Dec 23, 2025
268ab68
try fix copilot-setup-steps.yml
xiaoland Dec 23, 2025
b504c3a
add pnpm-lock.yaml
xiaoland Dec 23, 2025
48a27d6
rm pnpm-lock
xiaoland Dec 23, 2025
6ab2c97
try fix
xiaoland Dec 23, 2025
40a1acd
try fix
xiaoland Dec 23, 2025
0c7e5ca
Update copilot-setup-steps.yml
xiaoland Dec 23, 2025
45c548b
Update .npmrc
xiaoland Dec 23, 2025
64609d7
Update copilot-setup-steps.yml
xiaoland Dec 23, 2025
eec3721
Update copilot-setup-steps.yml
xiaoland Dec 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions src/business/info-base/source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ export class SourceCollectJob extends Z.class({
.enum(SourceCollectJobStatus)
.default(SourceCollectJobStatus.PENDING),
state: z.looseObject({}).default(() => ({})),
config: z.looseObject({}).default(() => ({})),
}) {
static dbApi: DBAPIClient = new DBAPIClient(
"sources_collect_jobs",
Expand All @@ -186,6 +187,46 @@ export class SourceCollectJob extends Z.class({
);
}

static async getBySource(
sourceId: SourceRef,
options?: {
limit?: number;
offset?: number;
order?: "asc" | "desc";
}
): Promise<{ data: SourceCollectJob[]; count: number }> {
const { limit = 10, offset = 0, order = "desc" } = options || {};
const query = this.dbApi
.from()
.select("*", { count: "exact" })
.eq("source", sourceId)
.order("created_at", { ascending: order === "asc" })
.range(offset, offset + limit - 1);

const result = await query;
return {
data: (result.data || []).map((d) => new SourceCollectJob(d)),
count: result.count || 0,
};
}

static async getLatestRunningBySource(
sourceId: SourceRef
): Promise<SourceCollectJob | null> {
const result = await this.dbApi
.from()
.select()
.eq("source", sourceId)
.eq("status", SourceCollectJobStatus.RUNNING)
.order("created_at", { ascending: false })
.limit(1);

if (result.data && result.data.length > 0) {
return new SourceCollectJob(result.data[0]);
}
return null;
}

public async getLogs(options?: {
limit?: number;
cursor?: number;
Expand Down
26 changes: 26 additions & 0 deletions src/components/info-base/source/collectJobForm/collectJobForm.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# collectJobForm

## Rationale

Provides a form interface to configure a source collect job.

## Goals

Allow users to configure collect job settings including config object.

## Specification

- Pure form component with v-model support
- Provides config editor (InkJsonEditor)
- No create/submit buttons - just form fields
- Parent component handles submission

## Implementation

### Props

- `modelValue` (`SourceCollectJobForm`, required): The form data object

### Events

- `update:modelValue(form: SourceCollectJobForm)`: Emitted when form data changes
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.collect-job-form {
display: flex;
flex-direction: column;
gap: sys-var(space, md);
}
15 changes: 15 additions & 0 deletions src/components/info-base/source/collectJobForm/collectJobForm.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { PropType } from "vue";
import { SourceCollectJobForm } from "@/business/info-base/source";

// --- Props ---
export const collectJobFormProps = {
modelValue: {
type: Object as PropType<SourceCollectJobForm>,
required: true,
},
} as const;

// --- Emits ---
export const collectJobFormEmits = {
"update:modelValue": (form: SourceCollectJobForm) => true,
} as const;
47 changes: 47 additions & 0 deletions src/components/info-base/source/collectJobForm/collectJobForm.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<script setup lang="ts">
import { ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { InkJsonEditor } from "@inkcre/web-design";
import { collectJobFormProps, collectJobFormEmits } from "./collectJobForm";

const props = defineProps(collectJobFormProps);
const emit = defineEmits(collectJobFormEmits);
const { t } = useI18n();

// --- data ---
const configJson = ref<string>("");

// --- watchers ---
watch(
() => props.modelValue.config,
(newVal) => {
configJson.value = JSON.stringify(newVal, null, 2);
},
{ immediate: true }
);

watch(configJson, (newVal) => {
try {
const parsedConfig = JSON.parse(newVal);
emit("update:modelValue", {
...props.modelValue,
config: parsedConfig,
});
} catch (error) {
// Invalid JSON, don't update
}
});
</script>

<template>
<div class="collect-job-form">
<InkJsonEditor
v-model="configJson"
:label="t('collectJob.config')"
:placeholder="t('source.configPlaceholder')"
:rows="6"
/>
</div>
</template>

<style lang="scss" scoped src="./collectJobForm.scss" />
91 changes: 5 additions & 86 deletions src/components/info-base/source/createSource/createSource.vue
Original file line number Diff line number Diff line change
@@ -1,22 +1,12 @@
<script setup lang="ts">
import { computed, reactive, ref, watch } from "vue";
import {
InkInput,
InkJsonEditor,
InkField,
InkButton,
InkForm,
InkSwitch,
InkDropdown,
type DropdownOption,
} from "@inkcre/web-design";
import collectAtForm from "../collectAtForm/collectAtForm.vue";
import { reactive } from "vue";
import { InkButton } from "@inkcre/web-design";
import sourceForm from "../sourceForm/sourceForm.vue";
import { createSourceEmits } from "./createSource";
import { CollectAt, SourceForm, SourceType } from "@/business/info-base/source";
import { CollectAt, SourceForm } from "@/business/info-base/source";
import { refManualReset } from "@vueuse/core";

const emit = defineEmits(createSourceEmits);
const configJson = ref<string>("");

// --- data ---
const form = refManualReset(() =>
Expand All @@ -29,93 +19,22 @@ const form = refManualReset(() =>
})
)
);
const sourceTypes = ref<(DropdownOption & SourceType)[]>([]);

// --- computed ---
const toggleAutoCollect = computed({
get: () => form.value.collect_at != null,
set: (value: boolean) => {
if (value) {
if (form.value.collect_at == null) {
form.value.collect_at = CollectAt.parse({});
}
} else {
form.value.collect_at = null;
}
},
});

const currentSourceType = computed(() => {
return sourceTypes.value.find((type) => type.value === form.value.type);
});

// --- methods ---
const loadSourceTypes = async (): Promise<(DropdownOption & SourceType)[]> => {
const result = await SourceType.getAll();
return result.map((type) => ({
label: type.id,
value: type.id,
...type,
}));
};

const onCreate = () => {
form.value.config = JSON.parse(configJson.value);
form.value.create().then(() => {
emit("create", form.value);
// Reset form on success
form.reset();
});
};

// -- watchers ---
watch(
() => form.value.config,
(newVal) => {
configJson.value = JSON.stringify(newVal, null, 2);
},
{ immediate: true }
);
</script>

<template>
<div class="create-source">
<h2 class="title">Create Source</h2>

<InkForm class="form">
<InkInput v-model="form.nickname" label="Nickname" editable />

<InkDropdown
v-model="form.type"
v-model:options="sourceTypes"
:refresher="loadSourceTypes"
label="Type"
/>

<InkField label="Collect At" prop="collect_at">
<template #label-right>
<div class="flex flex-row flex-1 justify-end">
<InkSwitch v-model="toggleAutoCollect" size="xs" />
</div>
</template>
<collectAtForm
v-if="form.collect_at"
v-model="form.collect_at"
class="form__collect-at"
/>
<span v-else class="form__collect-at-placeholder"
>Auto collect off.</span
>
</InkField>

<InkJsonEditor
v-model="configJson"
:schema="currentSourceType?.config_schema"
label="Config"
placeholder="{}"
:rows="6"
/>
</InkForm>
<sourceForm v-model="form" class="form" />

<div class="footer">
<InkButton text="Create" type="primary" size="md" @click="onCreate" />
Expand Down
19 changes: 19 additions & 0 deletions src/components/info-base/source/sourceCard/sourceCard.scss
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@
width: 100%;
min-width: 280px;
max-width: 380px;
cursor: pointer;
transition: background-color 0.2s, border-color 0.2s;

&:hover {
background-color: sys-var(color, surface, hover);
border-color: sys-var(color, border, hover);
}

&__metadata {
display: flex;
Expand Down Expand Up @@ -72,6 +79,18 @@
width: 100%;
}

&__running-job {
@include apply-font(label-md);
color: sys-var(color, text, base);
text-decoration: underline;
cursor: pointer;
width: 100%;

&:hover {
color: sys-var(color, primary, base);
}
}

&__operations {
display: flex;
align-items: center;
Expand Down
Loading